mirror of
https://github.com/ChristianLight/tutor.git
synced 2024-12-12 06:07:56 +00:00
Run code formatting on the entire code base
This commit is contained in:
parent
4ac7dff06a
commit
f2f714ad23
@ -7,51 +7,59 @@ from . import opts
|
||||
from . import utils
|
||||
|
||||
|
||||
@click.group(
|
||||
help="Build an Android app for your Open edX platform [BETA FEATURE]"
|
||||
)
|
||||
@click.group(help="Build an Android app for your Open edX platform [BETA FEATURE]")
|
||||
def android():
|
||||
pass
|
||||
|
||||
@click.group(
|
||||
help="Build the application"
|
||||
)
|
||||
|
||||
@click.group(help="Build the application")
|
||||
def build():
|
||||
pass
|
||||
|
||||
@click.command(
|
||||
help="Build the application in debug mode"
|
||||
)
|
||||
|
||||
@click.command(help="Build the application in debug mode")
|
||||
@opts.root
|
||||
def debug(root):
|
||||
docker_run(root)
|
||||
click.echo(fmt.info("The debuggable APK file is available in {}".format(tutor_env.data_path(root, "android"))))
|
||||
click.echo(
|
||||
fmt.info(
|
||||
"The debuggable APK file is available in {}".format(
|
||||
tutor_env.data_path(root, "android")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@click.command(
|
||||
help="Build the application in release mode"
|
||||
)
|
||||
|
||||
@click.command(help="Build the application in release mode")
|
||||
@opts.root
|
||||
def release(root):
|
||||
docker_run(root, "./gradlew", "assembleProdRelease")
|
||||
click.echo(fmt.info("The production APK file is available in {}".format(tutor_env.data_path(root, "android"))))
|
||||
click.echo(
|
||||
fmt.info(
|
||||
"The production APK file is available in {}".format(
|
||||
tutor_env.data_path(root, "android")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@click.command(
|
||||
help="Pull the docker image"
|
||||
)
|
||||
|
||||
@click.command(help="Pull the docker image")
|
||||
@opts.root
|
||||
def pullimage(root):
|
||||
config = tutor_config.load(root)
|
||||
utils.execute("docker", "pull", config['DOCKER_IMAGE_ANDROID'])
|
||||
utils.execute("docker", "pull", config["DOCKER_IMAGE_ANDROID"])
|
||||
|
||||
|
||||
def docker_run(root, *command):
|
||||
config = tutor_config.load(root)
|
||||
utils.docker_run(
|
||||
"--volume={}:/openedx/config/".format(tutor_env.pathjoin(root, "android")),
|
||||
"--volume={}:/openedx/data/".format(tutor_env.data_path(root, "android")),
|
||||
config['DOCKER_IMAGE_ANDROID'],
|
||||
config["DOCKER_IMAGE_ANDROID"],
|
||||
*command
|
||||
)
|
||||
|
||||
|
||||
build.add_command(debug)
|
||||
build.add_command(release)
|
||||
android.add_command(build)
|
||||
|
@ -25,16 +25,13 @@ def main():
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@click.group(context_settings={'help_option_names': ['-h', '--help', 'help']})
|
||||
@click.group(context_settings={"help_option_names": ["-h", "--help", "help"]})
|
||||
@click.version_option(version=__version__)
|
||||
def cli():
|
||||
pass
|
||||
|
||||
|
||||
@click.command(
|
||||
help="Print this help",
|
||||
name="help",
|
||||
)
|
||||
@click.command(help="Print this help", name="help")
|
||||
def print_help():
|
||||
with click.Context(cli) as context:
|
||||
click.echo(cli.get_help(context))
|
||||
|
161
tutor/config.py
161
tutor/config.py
@ -15,7 +15,7 @@ from .__about__ import __version__
|
||||
|
||||
@click.group(
|
||||
short_help="Configure Open edX",
|
||||
help="""Configure Open edX and store configuration values in $TUTOR_ROOT/config.yml"""
|
||||
help="""Configure Open edX and store configuration values in $TUTOR_ROOT/config.yml""",
|
||||
)
|
||||
def config_command():
|
||||
pass
|
||||
@ -44,9 +44,7 @@ def save(root, silent=False, keyvalues=None):
|
||||
save_env(root, config)
|
||||
|
||||
|
||||
@click.command(
|
||||
help="Print the project root",
|
||||
)
|
||||
@click.command(help="Print the project root")
|
||||
@opts.root
|
||||
def printroot(root):
|
||||
click.echo(root)
|
||||
@ -93,19 +91,25 @@ def pre_upgrade_announcement(root):
|
||||
Inform the user that the current environment is not up-to-date. Crash if running in
|
||||
non-interactive mode.
|
||||
"""
|
||||
click.echo(fmt.alert(
|
||||
"The current environment stored at {} is not up-to-date: it is at "
|
||||
"v{} while the 'tutor' binary is at v{}.".format(
|
||||
env.base_dir(root), env.version(root), __version__
|
||||
click.echo(
|
||||
fmt.alert(
|
||||
"The current environment stored at {} is not up-to-date: it is at "
|
||||
"v{} while the 'tutor' binary is at v{}.".format(
|
||||
env.base_dir(root), env.version(root), __version__
|
||||
)
|
||||
)
|
||||
))
|
||||
)
|
||||
if os.isatty(sys.stdin.fileno()):
|
||||
# Interactive mode: ask the user permission to proceed
|
||||
click.confirm(fmt.question(
|
||||
# every patch you take, every change you make, I'll be watching you
|
||||
"Would you like to upgrade the environment? If you do, any change you"
|
||||
" might have made will be overwritten."
|
||||
), default=True, abort=True)
|
||||
click.confirm(
|
||||
fmt.question(
|
||||
# every patch you take, every change you make, I'll be watching you
|
||||
"Would you like to upgrade the environment? If you do, any change you"
|
||||
" might have made will be overwritten."
|
||||
),
|
||||
default=True,
|
||||
abort=True,
|
||||
)
|
||||
else:
|
||||
# Non-interactive mode with no authorization: abort
|
||||
raise exceptions.TutorError(
|
||||
@ -171,29 +175,105 @@ def load_interactive(config):
|
||||
ask("Your public contact email address", "CONTACT_EMAIL", config)
|
||||
ask_choice(
|
||||
"The default language code for the platform",
|
||||
"LANGUAGE_CODE", config,
|
||||
['en', 'am', 'ar', 'az', 'bg-bg', 'bn-bd', 'bn-in', 'bs', 'ca',
|
||||
'ca@valencia', 'cs', 'cy', 'da', 'de-de', 'el', 'en-uk', 'en@lolcat',
|
||||
'en@pirate', 'es-419', 'es-ar', 'es-ec', 'es-es', 'es-mx', 'es-pe',
|
||||
'et-ee', 'eu-es', 'fa', 'fa-ir', 'fi-fi', 'fil', 'fr', 'gl', 'gu',
|
||||
'he', 'hi', 'hr', 'hu', 'hy-am', 'id', 'it-it', 'ja-jp', 'kk-kz',
|
||||
'km-kh', 'kn', 'ko-kr', 'lt-lt', 'ml', 'mn', 'mr', 'ms', 'nb', 'ne',
|
||||
'nl-nl', 'or', 'pl', 'pt-br', 'pt-pt', 'ro', 'ru', 'si', 'sk', 'sl',
|
||||
'sq', 'sr', 'sv', 'sw', 'ta', 'te', 'th', 'tr-tr', 'uk', 'ur', 'vi',
|
||||
'uz', 'zh-cn', 'zh-hk', 'zh-tw'],
|
||||
"LANGUAGE_CODE",
|
||||
config,
|
||||
[
|
||||
"en",
|
||||
"am",
|
||||
"ar",
|
||||
"az",
|
||||
"bg-bg",
|
||||
"bn-bd",
|
||||
"bn-in",
|
||||
"bs",
|
||||
"ca",
|
||||
"ca@valencia",
|
||||
"cs",
|
||||
"cy",
|
||||
"da",
|
||||
"de-de",
|
||||
"el",
|
||||
"en-uk",
|
||||
"en@lolcat",
|
||||
"en@pirate",
|
||||
"es-419",
|
||||
"es-ar",
|
||||
"es-ec",
|
||||
"es-es",
|
||||
"es-mx",
|
||||
"es-pe",
|
||||
"et-ee",
|
||||
"eu-es",
|
||||
"fa",
|
||||
"fa-ir",
|
||||
"fi-fi",
|
||||
"fil",
|
||||
"fr",
|
||||
"gl",
|
||||
"gu",
|
||||
"he",
|
||||
"hi",
|
||||
"hr",
|
||||
"hu",
|
||||
"hy-am",
|
||||
"id",
|
||||
"it-it",
|
||||
"ja-jp",
|
||||
"kk-kz",
|
||||
"km-kh",
|
||||
"kn",
|
||||
"ko-kr",
|
||||
"lt-lt",
|
||||
"ml",
|
||||
"mn",
|
||||
"mr",
|
||||
"ms",
|
||||
"nb",
|
||||
"ne",
|
||||
"nl-nl",
|
||||
"or",
|
||||
"pl",
|
||||
"pt-br",
|
||||
"pt-pt",
|
||||
"ro",
|
||||
"ru",
|
||||
"si",
|
||||
"sk",
|
||||
"sl",
|
||||
"sq",
|
||||
"sr",
|
||||
"sv",
|
||||
"sw",
|
||||
"ta",
|
||||
"te",
|
||||
"th",
|
||||
"tr-tr",
|
||||
"uk",
|
||||
"ur",
|
||||
"vi",
|
||||
"uz",
|
||||
"zh-cn",
|
||||
"zh-hk",
|
||||
"zh-tw",
|
||||
],
|
||||
)
|
||||
ask_bool(
|
||||
("Activate SSL/TLS certificates for HTTPS access? Important note:"
|
||||
"this will NOT work in a development environment."),
|
||||
"ACTIVATE_HTTPS", config
|
||||
(
|
||||
"Activate SSL/TLS certificates for HTTPS access? Important note:"
|
||||
"this will NOT work in a development environment."
|
||||
),
|
||||
"ACTIVATE_HTTPS",
|
||||
config,
|
||||
)
|
||||
ask_bool(
|
||||
"Activate Student Notes service (https://open.edx.org/features/student-notes)?",
|
||||
"ACTIVATE_NOTES", config
|
||||
"ACTIVATE_NOTES",
|
||||
config,
|
||||
)
|
||||
ask_bool(
|
||||
"Activate Xqueue for external grader services (https://github.com/edx/xqueue)?",
|
||||
"ACTIVATE_XQUEUE", config
|
||||
"ACTIVATE_XQUEUE",
|
||||
config,
|
||||
)
|
||||
|
||||
|
||||
@ -204,24 +284,21 @@ def load_defaults(config):
|
||||
config[k] = v
|
||||
|
||||
# Add extra configuration parameters that need to be computed separately
|
||||
config["lms_cms_common_domain"] = utils.common_domain(config["LMS_HOST"], config["CMS_HOST"])
|
||||
config["lms_cms_common_domain"] = utils.common_domain(
|
||||
config["LMS_HOST"], config["CMS_HOST"]
|
||||
)
|
||||
config["lms_host_reverse"] = ".".join(config["LMS_HOST"].split(".")[::-1])
|
||||
|
||||
|
||||
def ask(question, key, config):
|
||||
default = env.render_str(config, config[key])
|
||||
config[key] = click.prompt(
|
||||
fmt.question(question),
|
||||
prompt_suffix=" ", default=default, show_default=True,
|
||||
fmt.question(question), prompt_suffix=" ", default=default, show_default=True
|
||||
)
|
||||
|
||||
|
||||
def ask_bool(question, key, config):
|
||||
return click.confirm(
|
||||
fmt.question(question),
|
||||
prompt_suffix=' ',
|
||||
default=config[key],
|
||||
)
|
||||
return click.confirm(fmt.question(question), prompt_suffix=" ", default=config[key])
|
||||
|
||||
|
||||
def ask_choice(question, key, config, choices):
|
||||
@ -242,13 +319,19 @@ def convert_json2yml(root):
|
||||
return
|
||||
if os.path.exists(config_path(root)):
|
||||
raise exceptions.TutorError(
|
||||
"Both config.json and config.yml exist in {}: only one of these files must exist to continue".format(root)
|
||||
"Both config.json and config.yml exist in {}: only one of these files must exist to continue".format(
|
||||
root
|
||||
)
|
||||
)
|
||||
with open(json_path) as fi:
|
||||
config = json.load(fi)
|
||||
save_config(root, config)
|
||||
os.remove(json_path)
|
||||
click.echo(fmt.info("File config.json detected in {} and converted to config.yml".format(root)))
|
||||
click.echo(
|
||||
fmt.info(
|
||||
"File config.json detected in {} and converted to config.yml".format(root)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def save_config(root, config):
|
||||
|
67
tutor/dev.py
67
tutor/dev.py
@ -7,9 +7,7 @@ from . import opts
|
||||
from . import utils
|
||||
|
||||
|
||||
@click.group(
|
||||
help="Run Open edX platform with development settings",
|
||||
)
|
||||
@click.group(help="Run Open edX platform with development settings")
|
||||
def dev():
|
||||
pass
|
||||
|
||||
@ -36,9 +34,7 @@ def run(root, edx_platform_path, edx_platform_settings, service, command, args):
|
||||
)
|
||||
|
||||
|
||||
@click.command(
|
||||
help="Run a development server",
|
||||
)
|
||||
@click.command(help="Run a development server")
|
||||
@opts.root
|
||||
@opts.edx_platform_path
|
||||
@opts.edx_platform_settings
|
||||
@ -46,47 +42,70 @@ def run(root, edx_platform_path, edx_platform_settings, service, command, args):
|
||||
def runserver(root, edx_platform_path, edx_platform_settings, service):
|
||||
port = service_port(service)
|
||||
docker_compose_run_with_port(
|
||||
root, edx_platform_path, edx_platform_settings, port,
|
||||
service, "./manage.py", service, "runserver", "0.0.0.0:{}".format(port),
|
||||
root,
|
||||
edx_platform_path,
|
||||
edx_platform_settings,
|
||||
port,
|
||||
service,
|
||||
"./manage.py",
|
||||
service,
|
||||
"runserver",
|
||||
"0.0.0.0:{}".format(port),
|
||||
)
|
||||
|
||||
|
||||
@click.command(help="Stop a running development platform",)
|
||||
@click.command(help="Stop a running development platform")
|
||||
@opts.root
|
||||
def stop(root):
|
||||
docker_compose(root, "rm", "--stop", "--force")
|
||||
|
||||
|
||||
@click.command(
|
||||
help="Watch for changes in your themes and recompile assets when needed"
|
||||
)
|
||||
@click.command(help="Watch for changes in your themes and recompile assets when needed")
|
||||
@opts.root
|
||||
@opts.edx_platform_path
|
||||
@opts.edx_platform_settings
|
||||
def watchthemes(root, edx_platform_path, edx_platform_settings):
|
||||
docker_compose_run(
|
||||
root, edx_platform_path, edx_platform_settings,
|
||||
"--no-deps", "lms", "openedx-assets", "watch-themes", "--env", "dev"
|
||||
root,
|
||||
edx_platform_path,
|
||||
edx_platform_settings,
|
||||
"--no-deps",
|
||||
"lms",
|
||||
"openedx-assets",
|
||||
"watch-themes",
|
||||
"--env",
|
||||
"dev",
|
||||
)
|
||||
|
||||
|
||||
def docker_compose_run_with_port(root, edx_platform_path, edx_platform_settings, port, *command):
|
||||
def docker_compose_run_with_port(
|
||||
root, edx_platform_path, edx_platform_settings, port, *command
|
||||
):
|
||||
docker_compose_run(
|
||||
root, edx_platform_path, edx_platform_settings,
|
||||
"-p", "{port}:{port}".format(port=port), *command
|
||||
root,
|
||||
edx_platform_path,
|
||||
edx_platform_settings,
|
||||
"-p",
|
||||
"{port}:{port}".format(port=port),
|
||||
*command
|
||||
)
|
||||
|
||||
|
||||
def docker_compose_run(root, edx_platform_path, edx_platform_settings, *command):
|
||||
run_command = [
|
||||
"run", "--rm",
|
||||
"-e", "SETTINGS={}".format(edx_platform_settings),
|
||||
"--volume={}:/openedx/themes".format(tutor_env.pathjoin(root, "build", "openedx", "themes")),
|
||||
"run",
|
||||
"--rm",
|
||||
"-e",
|
||||
"SETTINGS={}".format(edx_platform_settings),
|
||||
"--volume={}:/openedx/themes".format(
|
||||
tutor_env.pathjoin(root, "build", "openedx", "themes")
|
||||
),
|
||||
]
|
||||
if edx_platform_path:
|
||||
run_command += [
|
||||
"--volume={}:/openedx/edx-platform".format(edx_platform_path),
|
||||
"-e", "USERID={}".format(subprocess.check_output(["id", "-u"]).strip().decode())
|
||||
"-e",
|
||||
"USERID={}".format(subprocess.check_output(["id", "-u"]).strip().decode()),
|
||||
]
|
||||
run_command += command
|
||||
docker_compose(root, *run_command)
|
||||
@ -94,8 +113,10 @@ def docker_compose_run(root, edx_platform_path, edx_platform_settings, *command)
|
||||
|
||||
def docker_compose(root, *command):
|
||||
return utils.docker_compose(
|
||||
"-f", tutor_env.pathjoin(root, "local", "docker-compose.yml"),
|
||||
"--project-name", "tutor_dev",
|
||||
"-f",
|
||||
tutor_env.pathjoin(root, "local", "docker-compose.yml"),
|
||||
"--project-name",
|
||||
"tutor_dev",
|
||||
*command
|
||||
)
|
||||
|
||||
|
15
tutor/env.py
15
tutor/env.py
@ -20,7 +20,7 @@ def render_full(root, config):
|
||||
for target in ["android", "apps", "k8s", "local", "webui"]:
|
||||
render_target(root, config, target)
|
||||
copy_target(root, "build")
|
||||
with open(pathjoin(root, VERSION_FILENAME), 'w') as f:
|
||||
with open(pathjoin(root, VERSION_FILENAME), "w") as f:
|
||||
f.write(__version__)
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ def render_target(root, config, target):
|
||||
|
||||
|
||||
def render_file(config, path):
|
||||
with codecs.open(path, encoding='utf-8') as fi:
|
||||
with codecs.open(path, encoding="utf-8") as fi:
|
||||
try:
|
||||
return render_str(config, fi.read())
|
||||
except jinja2.exceptions.TemplateError:
|
||||
@ -77,9 +77,7 @@ def render_str(config, text):
|
||||
template = jinja2.Template(text, undefined=jinja2.StrictUndefined)
|
||||
try:
|
||||
return template.render(
|
||||
RAND8=utils.random_string(8),
|
||||
RAND24=utils.random_string(24),
|
||||
**config
|
||||
RAND8=utils.random_string(8), RAND24=utils.random_string(24), **config
|
||||
)
|
||||
except jinja2.exceptions.UndefinedError as e:
|
||||
raise exceptions.TutorError("Missing configuration value: {}".format(e.args[0]))
|
||||
@ -113,7 +111,7 @@ def read(*path):
|
||||
Read template content located at `path`.
|
||||
"""
|
||||
src = template_path(*path)
|
||||
with codecs.open(src, encoding='utf-8') as fi:
|
||||
with codecs.open(src, encoding="utf-8") as fi:
|
||||
return fi.read()
|
||||
|
||||
|
||||
@ -129,10 +127,7 @@ def walk_templates(root, target):
|
||||
for dirpath, _, filenames in os.walk(target_root):
|
||||
if not is_part_of_env(dirpath):
|
||||
continue
|
||||
dst_dir = pathjoin(
|
||||
root, target,
|
||||
os.path.relpath(dirpath, target_root)
|
||||
)
|
||||
dst_dir = pathjoin(root, target, os.path.relpath(dirpath, target_root))
|
||||
if not os.path.exists(dst_dir):
|
||||
os.makedirs(dst_dir)
|
||||
for filename in filenames:
|
||||
|
10
tutor/fmt.py
10
tutor/fmt.py
@ -1,26 +1,30 @@
|
||||
import click
|
||||
|
||||
|
||||
def title(text):
|
||||
indent = 8
|
||||
separator = "=" * (len(text) + 2 * indent)
|
||||
message = "{separator}\n{indent}{text}\n{separator}".format(
|
||||
separator=separator,
|
||||
indent=" " * indent,
|
||||
text=text,
|
||||
separator=separator, indent=" " * indent, text=text
|
||||
)
|
||||
return click.style(message, fg="green")
|
||||
|
||||
|
||||
def info(text):
|
||||
return click.style(text, fg="blue")
|
||||
|
||||
|
||||
def error(text):
|
||||
return click.style(text, fg="red")
|
||||
|
||||
|
||||
def command(text):
|
||||
return click.style(text, fg="magenta")
|
||||
|
||||
|
||||
def question(text):
|
||||
return click.style(text, fg="yellow")
|
||||
|
||||
|
||||
def alert(text):
|
||||
return click.style("⚠️ " + text, fg="yellow", bold=True)
|
||||
|
@ -13,38 +13,43 @@ def images_command():
|
||||
|
||||
|
||||
OPENEDX_IMAGES = ["openedx", "forum", "notes", "xqueue", "android"]
|
||||
VENDOR_IMAGES = ["elasticsearch", "memcached", "mongodb", "mysql", "nginx", "rabbitmq", "smtp"]
|
||||
VENDOR_IMAGES = [
|
||||
"elasticsearch",
|
||||
"memcached",
|
||||
"mongodb",
|
||||
"mysql",
|
||||
"nginx",
|
||||
"rabbitmq",
|
||||
"smtp",
|
||||
]
|
||||
argument_openedx_image = click.argument(
|
||||
"image", type=click.Choice(["all"] + OPENEDX_IMAGES),
|
||||
"image", type=click.Choice(["all"] + OPENEDX_IMAGES)
|
||||
)
|
||||
argument_image = click.argument(
|
||||
"image", type=click.Choice(["all"] + OPENEDX_IMAGES + VENDOR_IMAGES),
|
||||
"image", type=click.Choice(["all"] + OPENEDX_IMAGES + VENDOR_IMAGES)
|
||||
)
|
||||
|
||||
|
||||
@click.command(
|
||||
short_help="Build docker images",
|
||||
help="Build the docker images necessary for an Open edX platform."
|
||||
help="Build the docker images necessary for an Open edX platform.",
|
||||
)
|
||||
@opts.root
|
||||
@argument_openedx_image
|
||||
@click.option(
|
||||
"-a", "--build-arg", multiple=True,
|
||||
help="Set build-time docker ARGS in the form 'myarg=value'. This option may be specified multiple times."
|
||||
"-a",
|
||||
"--build-arg",
|
||||
multiple=True,
|
||||
help="Set build-time docker ARGS in the form 'myarg=value'. This option may be specified multiple times.",
|
||||
)
|
||||
def build(root, image, build_arg):
|
||||
config = tutor_config.load(root)
|
||||
for img in openedx_image_names(config, image):
|
||||
tag = get_tag(config, img)
|
||||
click.echo(fmt.info("Building image {}".format(tag)))
|
||||
command = [
|
||||
"build", "-t", tag,
|
||||
tutor_env.pathjoin(root, "build", img)
|
||||
]
|
||||
command = ["build", "-t", tag, tutor_env.pathjoin(root, "build", img)]
|
||||
for arg in build_arg:
|
||||
command += [
|
||||
"--build-arg", arg
|
||||
]
|
||||
command += ["--build-arg", arg]
|
||||
utils.docker(*command)
|
||||
|
||||
|
||||
@ -71,10 +76,7 @@ def push(root, image):
|
||||
|
||||
def get_tag(config, name):
|
||||
image = config["DOCKER_IMAGE_" + name.upper()]
|
||||
return "{registry}{image}".format(
|
||||
registry=config["DOCKER_REGISTRY"],
|
||||
image=image,
|
||||
)
|
||||
return "{registry}{image}".format(registry=config["DOCKER_REGISTRY"], image=image)
|
||||
|
||||
|
||||
def image_names(config, image):
|
||||
@ -89,10 +91,10 @@ def openedx_image_tags(config, image):
|
||||
def openedx_image_names(config, image):
|
||||
if image == "all":
|
||||
images = OPENEDX_IMAGES[:]
|
||||
if not config['ACTIVATE_XQUEUE']:
|
||||
images.remove('xqueue')
|
||||
if not config['ACTIVATE_NOTES']:
|
||||
images.remove('notes')
|
||||
if not config["ACTIVATE_XQUEUE"]:
|
||||
images.remove("xqueue")
|
||||
if not config["ACTIVATE_NOTES"]:
|
||||
images.remove("notes")
|
||||
return images
|
||||
return [image]
|
||||
|
||||
@ -100,16 +102,16 @@ def openedx_image_names(config, image):
|
||||
def vendor_image_names(config, image):
|
||||
if image == "all":
|
||||
images = VENDOR_IMAGES[:]
|
||||
if not config['ACTIVATE_ELASTICSEARCH']:
|
||||
images.remove('elasticsearch')
|
||||
if not config['ACTIVATE_MEMCACHED']:
|
||||
images.remove('memcached')
|
||||
if not config['ACTIVATE_MONGODB']:
|
||||
images.remove('mongodb')
|
||||
if not config['ACTIVATE_MYSQL']:
|
||||
images.remove('mysql')
|
||||
if not config['ACTIVATE_RABBITMQ']:
|
||||
images.remove('rabbitmq')
|
||||
if not config["ACTIVATE_ELASTICSEARCH"]:
|
||||
images.remove("elasticsearch")
|
||||
if not config["ACTIVATE_MEMCACHED"]:
|
||||
images.remove("memcached")
|
||||
if not config["ACTIVATE_MONGODB"]:
|
||||
images.remove("mongodb")
|
||||
if not config["ACTIVATE_MYSQL"]:
|
||||
images.remove("mysql")
|
||||
if not config["ACTIVATE_RABBITMQ"]:
|
||||
images.remove("rabbitmq")
|
||||
return images
|
||||
return [image]
|
||||
|
||||
|
96
tutor/k8s.py
96
tutor/k8s.py
@ -14,9 +14,7 @@ def k8s():
|
||||
pass
|
||||
|
||||
|
||||
@click.command(
|
||||
help="Configure and run Open edX from scratch"
|
||||
)
|
||||
@click.command(help="Configure and run Open edX from scratch")
|
||||
@opts.root
|
||||
def quickstart(root):
|
||||
click.echo(fmt.title("Interactive platform configuration"))
|
||||
@ -25,7 +23,11 @@ def quickstart(root):
|
||||
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 k8s databases' later"))
|
||||
click.echo(
|
||||
fmt.title(
|
||||
"Running migrations. NOTE: this might fail. If it does, please retry 'tutor k8s databases' later"
|
||||
)
|
||||
)
|
||||
databases.callback(root)
|
||||
|
||||
|
||||
@ -35,12 +37,42 @@ def start(root):
|
||||
config = tutor_config.load(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"))
|
||||
if config['ACTIVATE_MYSQL']:
|
||||
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",
|
||||
"configmap",
|
||||
"nginx-config",
|
||||
"--from-file",
|
||||
tutor_env.pathjoin(root, "apps", "nginx"),
|
||||
)
|
||||
if config["ACTIVATE_MYSQL"]:
|
||||
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"))
|
||||
@ -57,13 +89,14 @@ def stop():
|
||||
@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)
|
||||
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",
|
||||
)
|
||||
@click.command(help="Create databases and run database migrations")
|
||||
@opts.root
|
||||
def databases(root):
|
||||
scripts.migrate(root, run_sh)
|
||||
@ -96,9 +129,7 @@ def indexcourses(root):
|
||||
scripts.index_courses(root, run_sh)
|
||||
|
||||
|
||||
@click.command(
|
||||
help="Launch a shell in LMS or CMS",
|
||||
)
|
||||
@click.command(help="Launch a shell in LMS or CMS")
|
||||
@click.argument("service", type=click.Choice(["lms", "cms"]))
|
||||
def shell(service):
|
||||
K8s().execute(service, "bash")
|
||||
@ -121,9 +152,7 @@ def kubectl(*command):
|
||||
ignored, to avoid stopping on "AlreadyExists" errors.
|
||||
"""
|
||||
args = list(command)
|
||||
args += [
|
||||
"--namespace", K8s.NAMESPACE
|
||||
]
|
||||
args += ["--namespace", K8s.NAMESPACE]
|
||||
kubectl_no_fail(*args)
|
||||
|
||||
|
||||
@ -150,6 +179,7 @@ class K8s:
|
||||
if self.CLIENT is None:
|
||||
# Import moved here for performance reasons
|
||||
import kubernetes
|
||||
|
||||
kubernetes.config.load_kube_config()
|
||||
self.CLIENT = kubernetes.client.CoreV1Api()
|
||||
return self.CLIENT
|
||||
@ -157,23 +187,37 @@ class K8s:
|
||||
def pod_name(self, app):
|
||||
selector = "app=" + app
|
||||
try:
|
||||
return self.client.list_namespaced_pod("openedx", label_selector=selector).items[0].metadata.name
|
||||
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.")
|
||||
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")
|
||||
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"]
|
||||
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)
|
||||
kubectl_no_fail(
|
||||
"exec", "--namespace", self.NAMESPACE, "-it", podname, "--", *command
|
||||
)
|
||||
|
||||
|
||||
def run_sh(root, service, command): # pylint: disable=unused-argument
|
||||
|
131
tutor/local.py
131
tutor/local.py
@ -23,7 +23,9 @@ def local():
|
||||
|
||||
|
||||
@click.command(help="Configure and run Open edX from scratch")
|
||||
@click.option("-p", "--pullimages", "pullimages_", is_flag=True, help="Update docker images")
|
||||
@click.option(
|
||||
"-p", "--pullimages", "pullimages_", is_flag=True, help="Update docker images"
|
||||
)
|
||||
@opts.root
|
||||
def quickstart(pullimages_, root):
|
||||
click.echo(fmt.title("Interactive platform configuration"))
|
||||
@ -64,30 +66,38 @@ def start(root, detach):
|
||||
http = "https" if config["ACTIVATE_HTTPS"] else "http"
|
||||
urls = []
|
||||
if not config["ACTIVATE_HTTPS"] and not config["WEB_PROXY"]:
|
||||
urls += [
|
||||
"http://localhost",
|
||||
"http://studio.localhost",
|
||||
]
|
||||
urls.append("{http}://{lms_host}".format(http=http, lms_host=config["LMS_HOST"]))
|
||||
urls.append("{http}://{cms_host}".format(http=http, cms_host=config["CMS_HOST"]))
|
||||
click.echo(fmt.info("""Your Open edX platform is ready and can be accessed at the following urls:
|
||||
urls += ["http://localhost", "http://studio.localhost"]
|
||||
urls.append(
|
||||
"{http}://{lms_host}".format(http=http, lms_host=config["LMS_HOST"])
|
||||
)
|
||||
urls.append(
|
||||
"{http}://{cms_host}".format(http=http, cms_host=config["CMS_HOST"])
|
||||
)
|
||||
click.echo(
|
||||
fmt.info(
|
||||
"""Your Open edX platform is ready and can be accessed at the following urls:
|
||||
|
||||
{}""".format("\n ".join(urls))))
|
||||
{}""".format(
|
||||
"\n ".join(urls)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@click.command(help="Stop a running platform",)
|
||||
@click.command(help="Stop a running platform")
|
||||
@opts.root
|
||||
def stop(root):
|
||||
config = tutor_config.load(root)
|
||||
docker_compose(root, config, "rm", "--stop", "--force")
|
||||
|
||||
|
||||
@click.command(
|
||||
help="""Restart some components from a running platform.
|
||||
You may specify 'openedx' to restart the lms, cms and workers, or 'all' to
|
||||
restart all services.""",
|
||||
restart all services."""
|
||||
)
|
||||
@opts.root
|
||||
@click.argument('service')
|
||||
@click.argument("service")
|
||||
def restart(root, service):
|
||||
config = tutor_config.load(root)
|
||||
command = ["restart"]
|
||||
@ -110,11 +120,7 @@ def restart(root, service):
|
||||
@click.argument("command", default=None, required=False)
|
||||
@click.argument("args", nargs=-1, required=False)
|
||||
def run(root, service, command, args):
|
||||
run_command = [
|
||||
"run",
|
||||
"--rm",
|
||||
service
|
||||
]
|
||||
run_command = ["run", "--rm", service]
|
||||
if command:
|
||||
run_command.append(command)
|
||||
if args:
|
||||
@ -123,9 +129,7 @@ def run(root, service, command, args):
|
||||
docker_compose(root, config, *run_command)
|
||||
|
||||
|
||||
@click.command(
|
||||
help="Create databases and run database migrations",
|
||||
)
|
||||
@click.command(help="Create databases and run database migrations")
|
||||
@opts.root
|
||||
def databases(root):
|
||||
init_mysql(root)
|
||||
@ -134,7 +138,7 @@ def databases(root):
|
||||
|
||||
def init_mysql(root):
|
||||
config = tutor_config.load(root)
|
||||
if not config['ACTIVATE_MYSQL']:
|
||||
if not config["ACTIVATE_MYSQL"]:
|
||||
return
|
||||
mysql_data_path = tutor_env.data_path(root, "mysql", "mysql")
|
||||
if os.path.exists(mysql_data_path):
|
||||
@ -145,10 +149,17 @@ def init_mysql(root):
|
||||
click.echo(fmt.info(" waiting for mysql initialization"))
|
||||
# TODO this is duplicate code with the docker_compose function. We
|
||||
# should rely on a dedicated function in utils module.
|
||||
mysql_logs = subprocess.check_output([
|
||||
"docker-compose", "-f", tutor_env.pathjoin(root, "local", "docker-compose.yml"),
|
||||
"--project-name", config["LOCAL_PROJECT_NAME"], "logs", "mysql",
|
||||
])
|
||||
mysql_logs = subprocess.check_output(
|
||||
[
|
||||
"docker-compose",
|
||||
"-f",
|
||||
tutor_env.pathjoin(root, "local", "docker-compose.yml"),
|
||||
"--project-name",
|
||||
config["LOCAL_PROJECT_NAME"],
|
||||
"logs",
|
||||
"mysql",
|
||||
]
|
||||
)
|
||||
# pylint: disable=unsupported-membership-test
|
||||
if b"MySQL init process done. Ready for start up." in mysql_logs:
|
||||
click.echo(fmt.info("MySQL database initialized"))
|
||||
@ -173,28 +184,38 @@ def https_create(root):
|
||||
2. On certificate renewal, nginx is not reloaded
|
||||
"""
|
||||
config = tutor_config.load(root)
|
||||
if not config['ACTIVATE_HTTPS']:
|
||||
if not config["ACTIVATE_HTTPS"]:
|
||||
click.echo(fmt.info("HTTPS is not activated: certificate generation skipped"))
|
||||
return
|
||||
|
||||
script = scripts.render_template(config, 'https_create.sh')
|
||||
script = scripts.render_template(config, "https_create.sh")
|
||||
|
||||
if config['WEB_PROXY']:
|
||||
click.echo(fmt.info("""You are running Tutor behind a web proxy (WEB_PROXY=true): SSL/TLS
|
||||
if config["WEB_PROXY"]:
|
||||
click.echo(
|
||||
fmt.info(
|
||||
"""You are running Tutor behind a web proxy (WEB_PROXY=true): SSL/TLS
|
||||
certificates must be generated on the host. For instance, to generate
|
||||
certificates with Let's Encrypt, run:
|
||||
|
||||
{}
|
||||
|
||||
See the official certbot documentation for your platform: https://certbot.eff.org/""".format(indent(script, " "))))
|
||||
See the official certbot documentation for your platform: https://certbot.eff.org/""".format(
|
||||
indent(script, " ")
|
||||
)
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
utils.docker_run(
|
||||
"--volume", "{}:/etc/letsencrypt/".format(tutor_env.data_path(root, "letsencrypt")),
|
||||
"-p", "80:80",
|
||||
"--volume",
|
||||
"{}:/etc/letsencrypt/".format(tutor_env.data_path(root, "letsencrypt")),
|
||||
"-p",
|
||||
"80:80",
|
||||
"--entrypoint=sh",
|
||||
"certbot/certbot:latest",
|
||||
"-e", "-c", script,
|
||||
"-e",
|
||||
"-c",
|
||||
script,
|
||||
)
|
||||
|
||||
|
||||
@ -202,22 +223,29 @@ See the official certbot documentation for your platform: https://certbot.eff.or
|
||||
@opts.root
|
||||
def https_renew(root):
|
||||
config = tutor_config.load(root)
|
||||
if not config['ACTIVATE_HTTPS']:
|
||||
if not config["ACTIVATE_HTTPS"]:
|
||||
click.echo(fmt.info("HTTPS is not activated: certificate renewal skipped"))
|
||||
return
|
||||
if config['WEB_PROXY']:
|
||||
click.echo(fmt.info("""You are running Tutor behind a web proxy (WEB_PROXY=true): SSL/TLS
|
||||
if config["WEB_PROXY"]:
|
||||
click.echo(
|
||||
fmt.info(
|
||||
"""You are running Tutor behind a web proxy (WEB_PROXY=true): SSL/TLS
|
||||
certificates must be renewed on the host. For instance, to renew Let's Encrypt
|
||||
certificates, run:
|
||||
|
||||
certbot renew
|
||||
|
||||
See the official certbot documentation for your platform: https://certbot.eff.org/"""))
|
||||
See the official certbot documentation for your platform: https://certbot.eff.org/"""
|
||||
)
|
||||
)
|
||||
return
|
||||
docker_run = [
|
||||
"--volume", "{}:/etc/letsencrypt/".format(tutor_env.data_path(root, "letsencrypt")),
|
||||
"-p", "80:80",
|
||||
"certbot/certbot:latest", "renew"
|
||||
"--volume",
|
||||
"{}:/etc/letsencrypt/".format(tutor_env.data_path(root, "letsencrypt")),
|
||||
"-p",
|
||||
"80:80",
|
||||
"certbot/certbot:latest",
|
||||
"renew",
|
||||
]
|
||||
utils.docker_run(*docker_run)
|
||||
|
||||
@ -274,22 +302,31 @@ def indexcourses(root):
|
||||
short_help="Run Portainer, a UI for container supervision",
|
||||
)
|
||||
@opts.root
|
||||
@click.option("-p", "--port", type=int, default=9000, show_default=True, help="Bind port")
|
||||
@click.option(
|
||||
"-p", "--port", type=int, default=9000, show_default=True, help="Bind port"
|
||||
)
|
||||
def portainer(root, port):
|
||||
docker_run = [
|
||||
"--volume=/var/run/docker.sock:/var/run/docker.sock",
|
||||
"--volume={}:/data".format(tutor_env.data_path(root, "portainer")),
|
||||
"-p", "{port}:{port}".format(port=port),
|
||||
"-p",
|
||||
"{port}:{port}".format(port=port),
|
||||
"portainer/portainer:latest",
|
||||
"--bind=:{}".format(port),
|
||||
]
|
||||
click.echo(fmt.info("View the Portainer UI at http://localhost:{port}".format(port=port)))
|
||||
click.echo(
|
||||
fmt.info("View the Portainer UI at http://localhost:{port}".format(port=port))
|
||||
)
|
||||
utils.docker_run(*docker_run)
|
||||
|
||||
|
||||
def check_service_is_activated(config, service):
|
||||
if not config["ACTIVATE_" + service.upper()]:
|
||||
raise exceptions.TutorError("This command may only be executed on the server where the {} is running".format(service))
|
||||
raise exceptions.TutorError(
|
||||
"This command may only be executed on the server where the {} is running".format(
|
||||
service
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def run_sh(root, service, command):
|
||||
@ -299,8 +336,10 @@ def run_sh(root, service, command):
|
||||
|
||||
def docker_compose(root, config, *command):
|
||||
return utils.docker_compose(
|
||||
"-f", tutor_env.pathjoin(root, "local", "docker-compose.yml"),
|
||||
"--project-name", config["LOCAL_PROJECT_NAME"],
|
||||
"-f",
|
||||
tutor_env.pathjoin(root, "local", "docker-compose.yml"),
|
||||
"--project-name",
|
||||
config["LOCAL_PROJECT_NAME"],
|
||||
*command
|
||||
)
|
||||
|
||||
|
@ -5,27 +5,32 @@ from . import serialize
|
||||
|
||||
|
||||
root = click.option(
|
||||
"-r", "--root",
|
||||
"-r",
|
||||
"--root",
|
||||
envvar="TUTOR_ROOT",
|
||||
default=appdirs.user_data_dir(appname="tutor"), show_default=True,
|
||||
default=appdirs.user_data_dir(appname="tutor"),
|
||||
show_default=True,
|
||||
type=click.Path(resolve_path=True),
|
||||
help="Root project directory (environment variable: TUTOR_ROOT)"
|
||||
help="Root project directory (environment variable: TUTOR_ROOT)",
|
||||
)
|
||||
|
||||
edx_platform_path = click.option(
|
||||
"-P", "--edx-platform-path",
|
||||
"-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)"
|
||||
help="Mount a local edx-platform from the host (environment variable: TUTOR_EDX_PLATFORM_PATH)",
|
||||
)
|
||||
|
||||
edx_platform_settings = click.option(
|
||||
"-S", "--edx-platform-settings",
|
||||
"-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)"
|
||||
help="Mount a local edx-platform from the host (environment variable: TUTOR_EDX_PLATFORM_PATH)",
|
||||
)
|
||||
|
||||
|
||||
class YamlParamType(click.ParamType):
|
||||
name = "yaml"
|
||||
|
||||
@ -36,7 +41,13 @@ class YamlParamType(click.ParamType):
|
||||
self.fail("'{}' is not of the form 'key=value'.".format(value), param, ctx)
|
||||
return k, serialize.parse_value(v)
|
||||
|
||||
|
||||
key_value = click.option(
|
||||
"-s", "--set", "set_", type=YamlParamType(), multiple=True, metavar="KEY=VAL",
|
||||
help="Set a configuration value (can be used multiple times)"
|
||||
"-s",
|
||||
"--set",
|
||||
"set_",
|
||||
type=YamlParamType(),
|
||||
multiple=True,
|
||||
metavar="KEY=VAL",
|
||||
help="Set a configuration value (can be used multiple times)",
|
||||
)
|
||||
|
@ -4,6 +4,7 @@ from . import config as tutor_config
|
||||
from . import env
|
||||
from . import fmt
|
||||
|
||||
|
||||
def migrate(root, run_func):
|
||||
config = tutor_config.load(root)
|
||||
|
||||
@ -31,29 +32,30 @@ def migrate(root, run_func):
|
||||
run_template(root, config, "lms", "oauth2.sh", run_func)
|
||||
click.echo(fmt.info("Databases ready."))
|
||||
|
||||
|
||||
def create_user(root, run_func, superuser, staff, name, email):
|
||||
config = {
|
||||
"OPTS": "",
|
||||
"USERNAME": name,
|
||||
"EMAIL": email,
|
||||
}
|
||||
config = {"OPTS": "", "USERNAME": name, "EMAIL": email}
|
||||
if superuser:
|
||||
config["OPTS"] += " --superuser"
|
||||
if staff:
|
||||
config["OPTS"] += " --staff"
|
||||
run_template(root, config, "lms", "create_user.sh", run_func)
|
||||
|
||||
|
||||
def import_demo_course(root, run_func):
|
||||
run_template(root, {}, "cms", "import_demo_course.sh", run_func)
|
||||
|
||||
|
||||
def index_courses(root, run_func):
|
||||
run_template(root, {}, "cms", "index_courses.sh", run_func)
|
||||
|
||||
|
||||
def run_template(root, config, service, template, run_func):
|
||||
command = render_template(config, template)
|
||||
if command:
|
||||
run_func(root, service, command)
|
||||
|
||||
|
||||
def render_template(config, template):
|
||||
path = env.template_path("scripts", template)
|
||||
return env.render_file(config, path).strip()
|
||||
|
@ -1,11 +1,14 @@
|
||||
import yaml
|
||||
|
||||
|
||||
def load(stream):
|
||||
return yaml.load(stream, Loader=yaml.SafeLoader)
|
||||
|
||||
|
||||
def dump(content, fileobj):
|
||||
yaml.dump(content, fileobj, default_flow_style=False)
|
||||
|
||||
|
||||
def parse_value(v):
|
||||
"""
|
||||
Parse a yaml-formatted string. This is fairly basic and should only be used
|
||||
@ -16,5 +19,5 @@ def parse_value(v):
|
||||
elif v == "null":
|
||||
v = None
|
||||
elif v in ["true", "false"]:
|
||||
v = (v == "true")
|
||||
v = v == "true"
|
||||
return v
|
||||
|
@ -1,28 +1,34 @@
|
||||
from .common import *
|
||||
|
||||
SECRET_KEY = '{{ NOTES_SECRET_KEY }}'
|
||||
ALLOWED_HOSTS = ['localhost', 'notes', 'notes.openedx', 'notes.localhost', 'notes.{{ LMS_HOST }}']
|
||||
SECRET_KEY = "{{ NOTES_SECRET_KEY }}"
|
||||
ALLOWED_HOSTS = [
|
||||
"localhost",
|
||||
"notes",
|
||||
"notes.openedx",
|
||||
"notes.localhost",
|
||||
"notes.{{ LMS_HOST }}",
|
||||
]
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'HOST': '{{ MYSQL_HOST }}',
|
||||
'PORT': {{ MYSQL_PORT }},
|
||||
'NAME': '{{ NOTES_MYSQL_DATABASE }}',
|
||||
'USER': '{{ NOTES_MYSQL_USERNAME }}',
|
||||
'PASSWORD': '{{ NOTES_MYSQL_PASSWORD }}',
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.mysql",
|
||||
"HOST": "{{ MYSQL_HOST }}",
|
||||
"PORT": {{MYSQL_PORT}},
|
||||
"NAME": "{{ NOTES_MYSQL_DATABASE }}",
|
||||
"USER": "{{ NOTES_MYSQL_USERNAME }}",
|
||||
"PASSWORD": "{{ NOTES_MYSQL_PASSWORD }}",
|
||||
}
|
||||
}
|
||||
|
||||
CLIENT_ID = 'notes'
|
||||
CLIENT_SECRET = '{{ NOTES_OAUTH2_SECRET }}'
|
||||
CLIENT_ID = "notes"
|
||||
CLIENT_SECRET = "{{ NOTES_OAUTH2_SECRET }}"
|
||||
|
||||
HAYSTACK_CONNECTIONS = {
|
||||
'default': {
|
||||
'ENGINE': 'notesserver.highlight.ElasticsearchSearchEngine',
|
||||
'URL': 'http://{{ ELASTICSEARCH_HOST }}:{{ ELASTICSEARCH_PORT }}/',
|
||||
'INDEX_NAME': 'notes',
|
||||
},
|
||||
"default": {
|
||||
"ENGINE": "notesserver.highlight.ElasticsearchSearchEngine",
|
||||
"URL": "http://{{ ELASTICSEARCH_HOST }}:{{ ELASTICSEARCH_PORT }}/",
|
||||
"INDEX_NAME": "notes",
|
||||
}
|
||||
}
|
||||
|
||||
LOGGING['handlers']['local'] = LOGGING['handlers']['console'].copy()
|
||||
LOGGING["handlers"]["local"] = LOGGING["handlers"]["console"].copy()
|
||||
|
@ -5,18 +5,18 @@ update_module_store_settings(MODULESTORE, doc_store_settings=DOC_STORE_CONFIG)
|
||||
MEDIA_ROOT = "/openedx/data/uploads/"
|
||||
|
||||
# Video settings
|
||||
VIDEO_IMAGE_SETTINGS['STORAGE_KWARGS']['location'] = MEDIA_ROOT
|
||||
VIDEO_TRANSCRIPTS_SETTINGS['STORAGE_KWARGS']['location'] = MEDIA_ROOT
|
||||
VIDEO_IMAGE_SETTINGS["STORAGE_KWARGS"]["location"] = MEDIA_ROOT
|
||||
VIDEO_TRANSCRIPTS_SETTINGS["STORAGE_KWARGS"]["location"] = MEDIA_ROOT
|
||||
|
||||
# Change syslog-based loggers which don't work inside docker containers
|
||||
LOGGING['handlers']['local'] = {'class': 'logging.NullHandler'}
|
||||
LOGGING['handlers']['tracking'] = {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'standard',
|
||||
LOGGING["handlers"]["local"] = {"class": "logging.NullHandler"}
|
||||
LOGGING["handlers"]["tracking"] = {
|
||||
"level": "DEBUG",
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "standard",
|
||||
}
|
||||
|
||||
LOCALE_PATHS.append('/openedx/locale')
|
||||
LOCALE_PATHS.append("/openedx/locale")
|
||||
|
||||
# Create folders if necessary
|
||||
for folder in [LOG_DIR, MEDIA_ROOT, STATIC_ROOT_BASE]:
|
||||
|
@ -3,7 +3,7 @@ from cms.envs.devstack import *
|
||||
|
||||
|
||||
# Execute the contents of common.py in this context
|
||||
execfile(os.path.join(os.path.dirname(__file__), 'common.py'), globals())
|
||||
execfile(os.path.join(os.path.dirname(__file__), "common.py"), globals())
|
||||
|
||||
# Setup correct webpack configuration file for development
|
||||
WEBPACK_CONFIG_PATH = 'webpack.dev.config.js'
|
||||
WEBPACK_CONFIG_PATH = "webpack.dev.config.js"
|
||||
|
@ -3,15 +3,19 @@ from cms.envs.production import *
|
||||
|
||||
|
||||
# Execute the contents of common.py in this context
|
||||
execfile(os.path.join(os.path.dirname(__file__), 'common.py'), globals())
|
||||
execfile(os.path.join(os.path.dirname(__file__), "common.py"), globals())
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
ENV_TOKENS.get('CMS_BASE'),
|
||||
'127.0.0.1', 'localhost', 'studio.localhost',
|
||||
'127.0.0.1:8000', 'localhost:8000',
|
||||
'127.0.0.1:8001', 'localhost:8001',
|
||||
ENV_TOKENS.get("CMS_BASE"),
|
||||
"127.0.0.1",
|
||||
"localhost",
|
||||
"studio.localhost",
|
||||
"127.0.0.1:8000",
|
||||
"localhost:8000",
|
||||
"127.0.0.1:8001",
|
||||
"localhost:8001",
|
||||
]
|
||||
|
||||
DEFAULT_FROM_EMAIL = ENV_TOKENS['CONTACT_EMAIL']
|
||||
DEFAULT_FEEDBACK_EMAIL = ENV_TOKENS['CONTACT_EMAIL']
|
||||
SERVER_EMAIL = ENV_TOKENS['CONTACT_EMAIL']
|
||||
DEFAULT_FROM_EMAIL = ENV_TOKENS["CONTACT_EMAIL"]
|
||||
DEFAULT_FEEDBACK_EMAIL = ENV_TOKENS["CONTACT_EMAIL"]
|
||||
SERVER_EMAIL = ENV_TOKENS["CONTACT_EMAIL"]
|
||||
|
@ -7,36 +7,38 @@ update_module_store_settings(MODULESTORE, doc_store_settings=DOC_STORE_CONFIG)
|
||||
MEDIA_ROOT = "/openedx/data/uploads/"
|
||||
|
||||
# Video settings
|
||||
VIDEO_IMAGE_SETTINGS['STORAGE_KWARGS']['location'] = MEDIA_ROOT
|
||||
VIDEO_TRANSCRIPTS_SETTINGS['STORAGE_KWARGS']['location'] = MEDIA_ROOT
|
||||
VIDEO_IMAGE_SETTINGS["STORAGE_KWARGS"]["location"] = MEDIA_ROOT
|
||||
VIDEO_TRANSCRIPTS_SETTINGS["STORAGE_KWARGS"]["location"] = MEDIA_ROOT
|
||||
|
||||
# Change syslog-based loggers which don't work inside docker containers
|
||||
LOGGING['handlers']['local'] = {'class': 'logging.NullHandler'}
|
||||
LOGGING['handlers']['tracking'] = {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'standard',
|
||||
LOGGING["handlers"]["local"] = {"class": "logging.NullHandler"}
|
||||
LOGGING["handlers"]["tracking"] = {
|
||||
"level": "DEBUG",
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "standard",
|
||||
}
|
||||
|
||||
# Fix media files paths
|
||||
VIDEO_IMAGE_SETTINGS['STORAGE_KWARGS']['location'] = MEDIA_ROOT
|
||||
VIDEO_TRANSCRIPTS_SETTINGS['STORAGE_KWARGS']['location'] = MEDIA_ROOT
|
||||
PROFILE_IMAGE_BACKEND['options']['location'] = os.path.join(MEDIA_ROOT, 'profile-images/')
|
||||
VIDEO_IMAGE_SETTINGS["STORAGE_KWARGS"]["location"] = MEDIA_ROOT
|
||||
VIDEO_TRANSCRIPTS_SETTINGS["STORAGE_KWARGS"]["location"] = MEDIA_ROOT
|
||||
PROFILE_IMAGE_BACKEND["options"]["location"] = os.path.join(
|
||||
MEDIA_ROOT, "profile-images/"
|
||||
)
|
||||
|
||||
|
||||
ORA2_FILEUPLOAD_BACKEND = 'filesystem'
|
||||
ORA2_FILEUPLOAD_ROOT = '/openedx/data/ora2'
|
||||
ORA2_FILEUPLOAD_CACHE_NAME = 'ora2-storage'
|
||||
ORA2_FILEUPLOAD_BACKEND = "filesystem"
|
||||
ORA2_FILEUPLOAD_ROOT = "/openedx/data/ora2"
|
||||
ORA2_FILEUPLOAD_CACHE_NAME = "ora2-storage"
|
||||
|
||||
GRADES_DOWNLOAD = {
|
||||
'STORAGE_TYPE': '',
|
||||
'STORAGE_KWARGS': {
|
||||
'base_url': "/media/grades/",
|
||||
'location': os.path.join(MEDIA_ROOT, 'grades'),
|
||||
}
|
||||
"STORAGE_TYPE": "",
|
||||
"STORAGE_KWARGS": {
|
||||
"base_url": "/media/grades/",
|
||||
"location": os.path.join(MEDIA_ROOT, "grades"),
|
||||
},
|
||||
}
|
||||
|
||||
LOCALE_PATHS.append('/openedx/locale')
|
||||
LOCALE_PATHS.append("/openedx/locale")
|
||||
|
||||
# Create folders if necessary
|
||||
for folder in [LOG_DIR, MEDIA_ROOT, STATIC_ROOT_BASE, ORA2_FILEUPLOAD_ROOT]:
|
||||
|
@ -3,8 +3,8 @@ from lms.envs.devstack import *
|
||||
|
||||
|
||||
# Execute the contents of common.py in this context
|
||||
execfile(os.path.join(os.path.dirname(__file__), 'common.py'), globals())
|
||||
execfile(os.path.join(os.path.dirname(__file__), "common.py"), globals())
|
||||
|
||||
|
||||
# Setup correct webpack configuration file for development
|
||||
WEBPACK_CONFIG_PATH = 'webpack.dev.config.js'
|
||||
WEBPACK_CONFIG_PATH = "webpack.dev.config.js"
|
||||
|
@ -3,13 +3,17 @@ from lms.envs.production import *
|
||||
|
||||
|
||||
# Execute the contents of common.py in this context
|
||||
execfile(os.path.join(os.path.dirname(__file__), 'common.py'), globals())
|
||||
execfile(os.path.join(os.path.dirname(__file__), "common.py"), globals())
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
ENV_TOKENS.get('LMS_BASE'),
|
||||
FEATURES['PREVIEW_LMS_BASE'],
|
||||
'127.0.0.1', 'localhost', 'preview.localhost',
|
||||
'127.0.0.1:8000', 'localhost:8000', 'preview.localhost:8000',
|
||||
ENV_TOKENS.get("LMS_BASE"),
|
||||
FEATURES["PREVIEW_LMS_BASE"],
|
||||
"127.0.0.1",
|
||||
"localhost",
|
||||
"preview.localhost",
|
||||
"127.0.0.1:8000",
|
||||
"localhost:8000",
|
||||
"preview.localhost:8000",
|
||||
]
|
||||
|
||||
# Required to display all courses on start page
|
||||
@ -18,15 +22,15 @@ SEARCH_SKIP_ENROLLMENT_START_DATE_FILTERING = True
|
||||
# Allow insecure oauth2 for local interaction with local containers
|
||||
OAUTH_ENFORCE_SECURE = False
|
||||
|
||||
DEFAULT_FROM_EMAIL = ENV_TOKENS['CONTACT_EMAIL']
|
||||
DEFAULT_FEEDBACK_EMAIL = ENV_TOKENS['CONTACT_EMAIL']
|
||||
SERVER_EMAIL = ENV_TOKENS['CONTACT_EMAIL']
|
||||
TECH_SUPPORT_EMAIL = ENV_TOKENS['CONTACT_EMAIL']
|
||||
CONTACT_EMAIL = ENV_TOKENS['CONTACT_EMAIL']
|
||||
BUGS_EMAIL = ENV_TOKENS['CONTACT_EMAIL']
|
||||
UNIVERSITY_EMAIL = ENV_TOKENS['CONTACT_EMAIL']
|
||||
PRESS_EMAIL = ENV_TOKENS['CONTACT_EMAIL']
|
||||
PAYMENT_SUPPORT_EMAIL = ENV_TOKENS['CONTACT_EMAIL']
|
||||
BULK_EMAIL_DEFAULT_FROM_EMAIL = 'no-reply@' + ENV_TOKENS['LMS_BASE']
|
||||
API_ACCESS_MANAGER_EMAIL = ENV_TOKENS['CONTACT_EMAIL']
|
||||
API_ACCESS_FROM_EMAIL = ENV_TOKENS['CONTACT_EMAIL']
|
||||
DEFAULT_FROM_EMAIL = ENV_TOKENS["CONTACT_EMAIL"]
|
||||
DEFAULT_FEEDBACK_EMAIL = ENV_TOKENS["CONTACT_EMAIL"]
|
||||
SERVER_EMAIL = ENV_TOKENS["CONTACT_EMAIL"]
|
||||
TECH_SUPPORT_EMAIL = ENV_TOKENS["CONTACT_EMAIL"]
|
||||
CONTACT_EMAIL = ENV_TOKENS["CONTACT_EMAIL"]
|
||||
BUGS_EMAIL = ENV_TOKENS["CONTACT_EMAIL"]
|
||||
UNIVERSITY_EMAIL = ENV_TOKENS["CONTACT_EMAIL"]
|
||||
PRESS_EMAIL = ENV_TOKENS["CONTACT_EMAIL"]
|
||||
PAYMENT_SUPPORT_EMAIL = ENV_TOKENS["CONTACT_EMAIL"]
|
||||
BULK_EMAIL_DEFAULT_FROM_EMAIL = "no-reply@" + ENV_TOKENS["LMS_BASE"]
|
||||
API_ACCESS_MANAGER_EMAIL = ENV_TOKENS["CONTACT_EMAIL"]
|
||||
API_ACCESS_FROM_EMAIL = ENV_TOKENS["CONTACT_EMAIL"]
|
||||
|
@ -1,24 +1,18 @@
|
||||
from .settings import *
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'HOST': '{{ MYSQL_HOST }}',
|
||||
'PORT': {{ MYSQL_PORT }},
|
||||
'NAME': '{{ XQUEUE_MYSQL_DATABASE }}',
|
||||
'USER': '{{ XQUEUE_MYSQL_USERNAME }}',
|
||||
'PASSWORD': '{{ XQUEUE_MYSQL_PASSWORD }}',
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.mysql",
|
||||
"HOST": "{{ MYSQL_HOST }}",
|
||||
"PORT": {{MYSQL_PORT}},
|
||||
"NAME": "{{ XQUEUE_MYSQL_DATABASE }}",
|
||||
"USER": "{{ XQUEUE_MYSQL_USERNAME }}",
|
||||
"PASSWORD": "{{ XQUEUE_MYSQL_PASSWORD }}",
|
||||
}
|
||||
}
|
||||
|
||||
LOGGING = get_logger_config(
|
||||
log_dir="/openedx/data/",
|
||||
logging_env="tutor",
|
||||
dev_env=True,
|
||||
)
|
||||
LOGGING = get_logger_config(log_dir="/openedx/data/", logging_env="tutor", dev_env=True)
|
||||
|
||||
SECRET_KEY = '{{ XQUEUE_SECRET_KEY }}'
|
||||
SECRET_KEY = "{{ XQUEUE_SECRET_KEY }}"
|
||||
|
||||
XQUEUE_USERS = {
|
||||
'{{ XQUEUE_AUTH_USERNAME }}': '{{ XQUEUE_AUTH_PASSWORD}}'
|
||||
}
|
||||
XQUEUE_USERS = {"{{ XQUEUE_AUTH_USERNAME }}": "{{ XQUEUE_AUTH_PASSWORD}}"}
|
||||
|
@ -4,13 +4,15 @@ import click_repl
|
||||
|
||||
@click.command(
|
||||
short_help="Interactive shell",
|
||||
help="Launch an interactive shell for launching Tutor commands"
|
||||
help="Launch an interactive shell for launching Tutor commands",
|
||||
)
|
||||
def ui():
|
||||
click.echo("""Welcome to the Tutor interactive shell UI!
|
||||
click.echo(
|
||||
"""Welcome to the Tutor interactive shell UI!
|
||||
Type "help" to view all available commands.
|
||||
Type "local quickstart" to configure and launch a new platform from scratch.
|
||||
Type <ctrl-d> to exit.""")
|
||||
Type <ctrl-d> to exit."""
|
||||
)
|
||||
while True:
|
||||
try:
|
||||
click_repl.repl(click.get_current_context())
|
||||
|
@ -10,7 +10,9 @@ from . import fmt
|
||||
|
||||
|
||||
def random_string(length):
|
||||
return "".join([random.choice(string.ascii_letters + string.digits) for _ in range(length)])
|
||||
return "".join(
|
||||
[random.choice(string.ascii_letters + string.digits) for _ in range(length)]
|
||||
)
|
||||
|
||||
|
||||
def common_domain(d1, d2):
|
||||
@ -36,13 +38,17 @@ def docker_run(*command):
|
||||
|
||||
def docker(*command):
|
||||
if shutil.which("docker") is None:
|
||||
raise exceptions.TutorError("docker is not installed. Please follow instructions from https://docs.docker.com/install/")
|
||||
raise exceptions.TutorError(
|
||||
"docker is not installed. Please follow instructions from https://docs.docker.com/install/"
|
||||
)
|
||||
return execute("docker", *command)
|
||||
|
||||
|
||||
def docker_compose(*command):
|
||||
if shutil.which("docker-compose") is None:
|
||||
raise exceptions.TutorError("docker-compose is not installed. Please follow instructions from https://docs.docker.com/compose/install/")
|
||||
raise exceptions.TutorError(
|
||||
"docker-compose is not installed. Please follow instructions from https://docs.docker.com/compose/install/"
|
||||
)
|
||||
return execute("docker-compose", *command)
|
||||
|
||||
|
||||
@ -66,11 +72,8 @@ def execute(*command):
|
||||
except Exception:
|
||||
p.kill()
|
||||
p.wait()
|
||||
raise exceptions.TutorError("Command failed: {}".format(
|
||||
" ".join(command)
|
||||
))
|
||||
raise exceptions.TutorError("Command failed: {}".format(" ".join(command)))
|
||||
if result > 0:
|
||||
raise exceptions.TutorError("Command failed with status {}: {}".format(
|
||||
result,
|
||||
" ".join(command)
|
||||
))
|
||||
raise exceptions.TutorError(
|
||||
"Command failed with status {}: {}".format(result, " ".join(command))
|
||||
)
|
||||
|
@ -15,24 +15,26 @@ from . import opts
|
||||
from . import env as tutor_env
|
||||
from . import serialize
|
||||
|
||||
|
||||
@click.group(
|
||||
short_help="Web user interface",
|
||||
help="""Run Tutor commands from a web terminal"""
|
||||
short_help="Web user interface", help="""Run Tutor commands from a web terminal"""
|
||||
)
|
||||
def webui():
|
||||
pass
|
||||
|
||||
@click.command(
|
||||
help="Start the web UI",
|
||||
)
|
||||
|
||||
@click.command(help="Start the web UI")
|
||||
@opts.root
|
||||
@click.option(
|
||||
"-p", "--port", default=3737, type=int, show_default=True,
|
||||
"-p",
|
||||
"--port",
|
||||
default=3737,
|
||||
type=int,
|
||||
show_default=True,
|
||||
help="Port number to listen",
|
||||
)
|
||||
@click.option(
|
||||
"-h", "--host", default="0.0.0.0", show_default=True,
|
||||
help="Host address to listen",
|
||||
"-h", "--host", default="0.0.0.0", show_default=True, help="Host address to listen"
|
||||
)
|
||||
def start(root, port, host):
|
||||
check_gotty_binary(root)
|
||||
@ -42,15 +44,24 @@ def start(root, port, host):
|
||||
user = config["user"]
|
||||
password = config["password"]
|
||||
command = [
|
||||
gotty_path(root), "--permit-write",
|
||||
"--address", host, "--port", str(port),
|
||||
"--title-format", "Tutor web UI - {{ .Command }} ({{ .Hostname }})",
|
||||
gotty_path(root),
|
||||
"--permit-write",
|
||||
"--address",
|
||||
host,
|
||||
"--port",
|
||||
str(port),
|
||||
"--title-format",
|
||||
"Tutor web UI - {{ .Command }} ({{ .Hostname }})",
|
||||
]
|
||||
if user and password:
|
||||
credential = "{}:{}".format(user, password)
|
||||
command += ["--credential", credential]
|
||||
else:
|
||||
click.echo(fmt.alert("Running web UI without user authentication. Run 'tutor webui configure' to setup authentication"))
|
||||
click.echo(
|
||||
fmt.alert(
|
||||
"Running web UI without user authentication. Run 'tutor webui configure' to setup authentication"
|
||||
)
|
||||
)
|
||||
command += [sys.argv[0], "ui"]
|
||||
p = subprocess.Popen(command)
|
||||
while True:
|
||||
@ -59,29 +70,35 @@ def start(root, port, host):
|
||||
except subprocess.TimeoutExpired:
|
||||
new_config = load_config(root)
|
||||
if new_config != config:
|
||||
click.echo("WARNING configuration changed. Tutor web UI is now going to restart. Reload this page to continue.")
|
||||
click.echo(
|
||||
"WARNING configuration changed. Tutor web UI is now going to restart. Reload this page to continue."
|
||||
)
|
||||
p.kill()
|
||||
p.wait()
|
||||
break
|
||||
|
||||
|
||||
@click.command(help="Configure authentication")
|
||||
@opts.root
|
||||
@click.option("-u", "--user", prompt="User name", help="Authentication user name")
|
||||
@click.option(
|
||||
"-p", "--password",
|
||||
prompt=True, hide_input=True, confirmation_prompt=True,
|
||||
help="Authentication password"
|
||||
"-p",
|
||||
"--password",
|
||||
prompt=True,
|
||||
hide_input=True,
|
||||
confirmation_prompt=True,
|
||||
help="Authentication password",
|
||||
)
|
||||
def configure(root, user, password):
|
||||
save_config(root, {
|
||||
"user": user,
|
||||
"password": password,
|
||||
})
|
||||
click.echo(fmt.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))
|
||||
))
|
||||
save_config(root, {"user": user, "password": password})
|
||||
click.echo(
|
||||
fmt.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))
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def check_gotty_binary(root):
|
||||
path = gotty_path(root)
|
||||
@ -93,8 +110,7 @@ def check_gotty_binary(root):
|
||||
# Note: I don't know how to handle arm
|
||||
architecture = "amd64" if platform.architecture()[0] == "64bit" else "386"
|
||||
url = "https://github.com/yudai/gotty/releases/download/v1.0.1/gotty_{system}_{architecture}.tar.gz".format(
|
||||
system=platform.system().lower(),
|
||||
architecture=architecture,
|
||||
system=platform.system().lower(), architecture=architecture
|
||||
)
|
||||
|
||||
# Download
|
||||
@ -107,16 +123,15 @@ def check_gotty_binary(root):
|
||||
compressed = tarfile.open(fileobj=io.BytesIO(response.read()))
|
||||
compressed.extract("./gotty", dirname)
|
||||
|
||||
|
||||
def load_config(root):
|
||||
path = config_path(root)
|
||||
if not os.path.exists(path):
|
||||
save_config(root, {
|
||||
"user": None,
|
||||
"password": None,
|
||||
})
|
||||
save_config(root, {"user": None, "password": None})
|
||||
with open(config_path(root)) as f:
|
||||
return serialize.load(f)
|
||||
|
||||
|
||||
def save_config(root, config):
|
||||
path = config_path(root)
|
||||
directory = os.path.dirname(path)
|
||||
@ -125,14 +140,18 @@ def save_config(root, config):
|
||||
with open(path, "w") as of:
|
||||
serialize.dump(config, of)
|
||||
|
||||
|
||||
def gotty_path(root):
|
||||
return get_path(root, "gotty")
|
||||
|
||||
|
||||
def config_path(root):
|
||||
return get_path(root, "config.yml")
|
||||
|
||||
|
||||
def get_path(root, filename):
|
||||
return tutor_env.pathjoin(root, "webui", filename)
|
||||
|
||||
|
||||
webui.add_command(start)
|
||||
webui.add_command(configure)
|
||||
|
Loading…
Reference in New Issue
Block a user