Example #1
0
def upgrade_from_lilac(config: Config) -> None:
    if not plugins.is_installed("forum"):
        fmt.echo_alert(
            "The Open edX forum feature was moved to a separate plugin in Maple. To keep using this feature, "
            "you must install and enable the tutor-forum plugin: https://github.com/overhangio/tutor-forum"
        )
    elif not plugins.is_loaded("forum"):
        fmt.echo_info(
            "The Open edX forum feature was moved to a separate plugin in Maple. To keep using this feature, "
            "we will now enable the 'forum' plugin. If you do not want to use this feature, you should disable the "
            "plugin with: `tutor plugins disable forum`.")
        plugins.load("forum")
        tutor_config.save_enabled_plugins(config)

    if not plugins.is_installed("mfe"):
        fmt.echo_alert(
            "In Maple the legacy courseware is no longer supported. You need to install and enable the 'mfe' plugin "
            "to make use of the new learning microfrontend: https://github.com/overhangio/tutor-mfe"
        )
    elif not plugins.is_loaded("mfe"):
        fmt.echo_info(
            "In Maple the legacy courseware is no longer supported. To start using the new learning microfrontend, "
            "we will now enable the 'mfe' plugin. If you do not want to use this feature, you should disable the "
            "plugin with: `tutor plugins disable mfe`.")
        plugins.load("mfe")
        tutor_config.save_enabled_plugins(config)
Example #2
0
    def __init__(self, config: t.Optional[Config] = None):
        config = config or {}
        self.config = deepcopy(config)
        self.template_roots = hooks.Filters.ENV_TEMPLATE_ROOTS.apply(
            [TEMPLATES_ROOT])

        # Create environment with extra filters and globals
        self.environment = JinjaEnvironment(self.template_roots)

        # Filters
        plugin_filters: t.Iterator[t.Tuple[
            str, JinjaFilter]] = hooks.Filters.ENV_TEMPLATE_FILTERS.iterate()
        for name, func in plugin_filters:
            if name in self.environment.filters:
                fmt.echo_alert(
                    f"Found conflicting template filters named '{name}'")
            self.environment.filters[name] = func
        self.environment.filters["walk_templates"] = self.walk_templates

        # Globals
        plugin_globals: t.Iterator[t.Tuple[
            str,
            JinjaFilter]] = hooks.Filters.ENV_TEMPLATE_VARIABLES.iterate()
        for name, value in plugin_globals:
            if name in self.environment.globals:
                fmt.echo_alert(
                    f"Found conflicting template variables named '{name}'")
            self.environment.globals[name] = value
        self.environment.globals["iter_values_named"] = self.iter_values_named
        self.environment.globals["patch"] = self.patch
Example #3
0
def set_theme(theme_name: str, domain_names: t.List[str],
              runner: BaseJobRunner) -> None:
    """
    For each domain, get or create a Site object and assign the selected theme.
    """
    if not domain_names:
        return
    python_code = "from django.contrib.sites.models import Site"
    for domain_name in domain_names:
        if len(domain_name) > 50:
            fmt.echo_alert(
                "Assigning a theme to a site with a long (> 50 characters) domain name."
                " The displayed site name will be truncated to 50 characters.")
        python_code += """
print('Assigning theme {theme_name} to {domain_name}...')
site, _ = Site.objects.get_or_create(domain='{domain_name}')
if not site.name:
    name_max_length = Site._meta.get_field('name').max_length
    name = '{domain_name}'[:name_max_length]
    site.name = name
    site.save()
site.themes.all().delete()
site.themes.create(theme_dir_name='{theme_name}')
""".format(theme_name=theme_name, domain_name=domain_name)
    command = BASE_OPENEDX_COMMAND + f'./manage.py lms shell -c "{python_code}"'
    runner.run_job("lms", command)
Example #4
0
File: env.py Project: eduNEXT/tutor
    def __init__(
        self,
        config: Config,
        template_roots: t.List[str],
        ignore_folders: t.Optional[t.List[str]] = None,
    ):
        self.config = deepcopy(config)
        self.template_roots = template_roots
        self.ignore_folders = ignore_folders or []
        self.ignore_folders.append(".git")

        # Create environment with extra filters and globals
        self.environment = JinjaEnvironment(template_roots)

        # Filters
        plugin_filters: t.Iterator[
            t.Tuple[str, JinjaFilter]
        ] = hooks.Filters.ENV_TEMPLATE_FILTERS.iterate()
        for name, func in plugin_filters:
            if name in self.environment.filters:
                fmt.echo_alert(f"Found conflicting template filters named '{name}'")
            self.environment.filters[name] = func
        self.environment.filters["walk_templates"] = self.walk_templates

        # Globals
        plugin_globals: t.Iterator[
            t.Tuple[str, JinjaFilter]
        ] = hooks.Filters.ENV_TEMPLATE_VARIABLES.iterate()
        for name, value in plugin_globals:
            if name in self.environment.globals:
                fmt.echo_alert(f"Found conflicting template variables named '{name}'")
            self.environment.globals[name] = value
        self.environment.globals["iter_values_named"] = self.iter_values_named
        self.environment.globals["patch"] = self.patch
Example #5
0
def check_is_up_to_date(root: str) -> None:
    if not is_up_to_date(root):
        fmt.echo_alert(
            f"The current environment stored at {base_dir(root)} is not up-to-date: it is at "
            f"v{current_version(root)} while the 'tutor' binary is at v{__version__}. You should upgrade "
            f"the environment by running:\n"
            f"\n"
            f"    tutor config save")
Example #6
0
def cli(context: click.Context, root: str, show_help: bool) -> None:
    if utils.is_root():
        fmt.echo_alert(
            "You are running Tutor as root. This is strongly not recommended. If you are doing this in order to access"
            " the Docker daemon, you should instead add your user to the 'docker' group. (see https://docs.docker.com"
            "/install/linux/linux-postinstall/#manage-docker-as-a-non-root-user)"
        )
    context.obj = Context(root)
    if context.invoked_subcommand is None or show_help:
        click.echo(context.get_help())
Example #7
0
def upgrade(context: click.Context, from_release: t.Optional[str]) -> None:
    fmt.echo_alert(
        "This command only performs a partial upgrade of your Open edX platform. "
        "To perform a full upgrade, you should run `tutor local quickstart`.")
    if from_release is None:
        from_release = tutor_env.get_env_release(context.obj.root)
    if from_release is None:
        fmt.echo_info("Your environment is already up-to-date")
    else:
        upgrade_from(context, from_release)
    # We update the environment to update the version
    context.invoke(config_save_command)
Example #8
0
def bindmount_command(context: BaseComposeContext, service: str, path: str) -> None:
    """
    This command is made obsolete by the --mount arguments.
    """
    fmt.echo_alert(
        "The 'bindmount' command is deprecated and will be removed in a later release. Use 'copyfrom' instead."
    )
    config = tutor_config.load(context.root)
    host_path = bindmounts.create(context.job_runner(config), service, path)
    fmt.echo_info(
        f"Bind-mount volume created at {host_path}. You can now use it in all `local` and `dev` "
        f"commands with the `--volume={path}` option."
    )
Example #9
0
def load_all(names: t.Iterable[str]) -> None:
    """
    Load all plugins one by one.

    Plugins are loaded in alphabetical order. We ignore plugins which failed to load.
    After all plugins have been loaded, the PLUGINS_LOADED action is triggered.
    """
    names = sorted(set(names))
    for name in names:
        try:
            load(name)
        except Exception as e:
            fmt.echo_alert(f"Failed to enable plugin '{name}': {e}")
    hooks.Actions.PLUGINS_LOADED.do()
Example #10
0
def quickstart(context: click.Context, non_interactive: bool, pullimages: bool) -> None:
    try:
        utils.check_macos_docker_memory()
    except exceptions.TutorError as e:
        fmt.echo_alert(
            f"""Could not verify sufficient RAM allocation in Docker:
    {e}
Tutor may not work if Docker is configured with < 4 GB RAM. Please follow instructions from:
    https://docs.tutor.overhang.io/install.html"""
        )

    click.echo(fmt.title("Interactive platform configuration"))
    config = tutor_config.load_minimal(context.obj.root)
    if not non_interactive:
        interactive_config.ask_questions(config, run_for_prod=False)
    tutor_config.save_config_file(context.obj.root, config)
    config = tutor_config.load_full(context.obj.root)
    tutor_env.save(context.obj.root, config)

    click.echo(fmt.title("Stopping any existing platform"))
    context.invoke(compose.stop)

    if pullimages:
        click.echo(fmt.title("Docker image updates"))
        context.invoke(compose.dc_command, command="pull")

    click.echo(fmt.title("Building Docker image for LMS and CMS development"))
    context.invoke(compose.dc_command, command="build", args=["lms"])

    click.echo(fmt.title("Starting the platform in detached mode"))
    context.invoke(compose.start, detach=True)

    click.echo(fmt.title("Database creation and migrations"))
    context.invoke(compose.init)

    fmt.echo_info(
        """The Open edX platform is now running in detached mode
Your Open edX platform is ready and can be accessed at the following urls:
    {http}://{lms_host}:8000
    {http}://{cms_host}:8001
    """.format(
            http="https" if config["ENABLE_HTTPS"] else "http",
            lms_host=config["LMS_HOST"],
            cms_host=config["CMS_HOST"],
        )
    )
Example #11
0
def get_base() -> Config:
    """
    Load the base configuration.

    Entries in this configuration are unrendered.
    """
    base = get_template("base.yml")
    extra_config: t.List[t.Tuple[str, ConfigValue]] = []
    extra_config = hooks.Filters.CONFIG_UNIQUE.apply(extra_config)
    extra_config = hooks.Filters.CONFIG_OVERRIDES.apply(extra_config)
    for name, value in extra_config:
        if name in base:
            fmt.echo_alert(
                f"Found conflicting values for setting '{name}': '{value}' or '{base[name]}'"
            )
        base[name] = value
    return base
Example #12
0
def runserver(
    context: click.Context,
    mounts: t.Tuple[t.List[compose.MountParam.MountType]],
    options: t.List[str],
    service: str,
) -> None:
    depr_warning = "'runserver' is deprecated and will be removed in a future release. Use 'start' instead."
    for option in options:
        if option.startswith("-v") or option.startswith("--volume"):
            depr_warning += " Bind-mounts can be specified using '-m/--mount'."
            break
    fmt.echo_alert(depr_warning)
    config = tutor_config.load(context.obj.root)
    if service in ["lms", "cms"]:
        port = 8000 if service == "lms" else 8001
        host = config["LMS_HOST"] if service == "lms" else config["CMS_HOST"]
        fmt.echo_info(
            f"The {service} service will be available at http://{host}:{port}"
        )
    args = ["--service-ports", *options, service]
    context.invoke(compose.run, mounts=mounts, args=args)
Example #13
0
def quickstart(
    context: click.Context,
    mounts: t.Tuple[t.List[compose.MountParam.MountType]],
    non_interactive: bool,
    pullimages: bool,
) -> None:
    try:
        utils.check_macos_docker_memory()
    except exceptions.TutorError as e:
        fmt.echo_alert(
            f"""Could not verify sufficient RAM allocation in Docker:

    {e}

Tutor may not work if Docker is configured with < 4 GB RAM. Please follow instructions from:

    https://docs.tutor.overhang.io/install.html""")

    run_upgrade_from_release = tutor_env.should_upgrade_from_release(
        context.obj.root)
    if run_upgrade_from_release is not None:
        click.echo(fmt.title("Upgrading from an older release"))
        if not non_interactive:
            to_release = tutor_env.get_package_release()
            question = f"""You are about to upgrade your Open edX platform from {run_upgrade_from_release.capitalize()} to {to_release.capitalize()}

It is strongly recommended to make a backup before upgrading. To do so, run:

    tutor local stop
    sudo rsync -avr "$(tutor config printroot)"/ /tmp/tutor-backup/

In case of problem, to restore your backup you will then have to run: sudo rsync -avr /tmp/tutor-backup/ "$(tutor config printroot)"/

Are you sure you want to continue?"""
            click.confirm(fmt.question(question),
                          default=True,
                          abort=True,
                          prompt_suffix=" ")
        context.invoke(
            upgrade,
            from_release=run_upgrade_from_release,
        )

    click.echo(fmt.title("Interactive platform configuration"))
    config = tutor_config.load_minimal(context.obj.root)
    if not non_interactive:
        interactive_config.ask_questions(config)
    tutor_config.save_config_file(context.obj.root, config)
    config = tutor_config.load_full(context.obj.root)
    tutor_env.save(context.obj.root, config)

    if run_upgrade_from_release and not non_interactive:
        question = f"""Your platform is being upgraded from {run_upgrade_from_release.capitalize()}.

If you run custom Docker images, you must rebuild them now by running the following command in a different shell:

    tutor images build all # list your custom images here

See the documentation for more information:

    https://docs.tutor.overhang.io/install.html#upgrading-to-a-new-open-edx-release

Press enter when you are ready to continue"""
        click.confirm(fmt.question(question),
                      default=True,
                      abort=True,
                      prompt_suffix=" ")

    click.echo(fmt.title("Stopping any existing platform"))
    context.invoke(compose.stop)
    if pullimages:
        click.echo(fmt.title("Docker image updates"))
        context.invoke(compose.dc_command, command="pull")
    click.echo(fmt.title("Starting the platform in detached mode"))
    context.invoke(compose.start, mounts=mounts, detach=True)
    click.echo(fmt.title("Database creation and migrations"))
    context.invoke(compose.init, mounts=mounts)

    config = tutor_config.load(context.obj.root)
    fmt.echo_info("""The Open edX platform is now running in detached mode
Your Open edX platform is ready and can be accessed at the following urls:

    {http}://{lms_host}
    {http}://{cms_host}
    """.format(
        http="https" if config["ENABLE_HTTPS"] else "http",
        lms_host=config["LMS_HOST"],
        cms_host=config["CMS_HOST"],
    ))