def upgrade_from_juniper(context: click.Context, config: Config) -> None: click.echo(fmt.title("Upgrading from Juniper")) tutor_env.save(context.obj.root, config) click.echo(fmt.title("Stopping any existing platform")) context.invoke(compose.stop) if not config["RUN_MYSQL"]: fmt.echo_info( "You are not running MySQL (RUN_MYSQL=false). It is your " "responsibility to upgrade your MySQL instance to v5.7. There is " "nothing left to do to upgrade from Juniper.") return click.echo(fmt.title("Upgrading MySQL from v5.6 to v5.7")) context.invoke(compose.start, detach=True, services=["mysql"]) context.invoke( compose.execute, args=[ "mysql", "bash", "-e", "-c", f"mysql_upgrade -u {config['MYSQL_ROOT_USERNAME']} --password='******'MYSQL_ROOT_PASSWORD']}'", ], ) context.invoke(compose.stop)
def test_save_full(self) -> None: with tempfile.TemporaryDirectory() as root: config = tutor_config.load_full(root) with patch.object(fmt, "STDOUT"): env.save(root, config) self.assertTrue( os.path.exists( os.path.join(root, "env", "local", "docker-compose.yml")))
def test_save_full_with_https(self): defaults = tutor_config.load_defaults() defaults["ACTIVATE_HTTPS"] = True with tempfile.TemporaryDirectory() as root: with unittest.mock.patch.object(fmt, "STDOUT"): env.save(root, defaults) with open(os.path.join(root, "env", "apps", "nginx", "lms.conf")) as f: self.assertIn("ssl", f.read())
def test_save_full(self): defaults = tutor_config.load_defaults() with tempfile.TemporaryDirectory() as root: with unittest.mock.patch.object(fmt, "STDOUT"): env.save(root, defaults) self.assertTrue(os.path.exists(os.path.join(root, "env", "version"))) self.assertTrue( os.path.exists(os.path.join(root, "env", "local", "docker-compose.yml")) )
def test_save_full_with_https(self): defaults = tutor_config.load_defaults() with tempfile.TemporaryDirectory() as root: config = tutor_config.load_current(root, defaults) tutor_config.merge(config, defaults) config["ENABLE_HTTPS"] = True with unittest.mock.patch.object(fmt, "STDOUT"): env.save(root, config) with open(os.path.join(root, "env", "apps", "caddy", "Caddyfile")) as f: self.assertIn("www.myopenedx.com {", f.read())
def upgrade_from_maple(context: Context, config: Config) -> None: fmt.echo_info("Upgrading from Maple") # The environment needs to be updated because the backpopulate/backfill commands are from Nutmeg tutor_env.save(context.root, config) # Start mysql k8s.kubectl_apply( context.root, "--selector", "app.kubernetes.io/name=mysql", ) k8s.wait_for_pod_ready(config, "mysql") # lms upgrade k8s.kubectl_apply( context.root, "--selector", "app.kubernetes.io/name=lms", ) k8s.wait_for_pod_ready(config, "lms") # Command backpopulate_user_tours k8s.kubectl_exec(config, "lms", ["sh", "-e", "-c", "./manage.py lms migrate user_tours"]) k8s.kubectl_exec( config, "lms", ["sh", "-e", "-c", "./manage.py lms backpopulate_user_tours"]) # cms upgrade k8s.kubectl_apply( context.root, "--selector", "app.kubernetes.io/name=cms", ) k8s.wait_for_pod_ready(config, "cms") # Command backfill_course_tabs k8s.kubectl_exec( config, "cms", ["sh", "-e", "-c", "./manage.py cms migrate contentstore"]) k8s.kubectl_exec( config, "cms", ["sh", "-e", "-c", "./manage.py cms migrate split_modulestore_django"], ) k8s.kubectl_exec( config, "cms", ["sh", "-e", "-c", "./manage.py cms backfill_course_tabs"]) # Command simulate_publish k8s.kubectl_exec( config, "cms", ["sh", "-e", "-c", "./manage.py cms migrate course_overviews"]) k8s.kubectl_exec(config, "cms", ["sh", "-e", "-c", "./manage.py cms simulate_publish"])
def test_save_full_with_https(self) -> None: with temporary_root() as root: config = tutor_config.load_full(root) config["ENABLE_HTTPS"] = True with patch.object(fmt, "STDOUT"): env.save(root, config) with open( os.path.join(env.base_dir(root), "apps", "caddy", "Caddyfile"), encoding="utf-8", ) as f: self.assertIn("www.myopenedx.com{$default_site_port}", f.read())
def quickstart(context: click.Context, non_interactive: bool) -> None: 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")) context.invoke( upgrade, from_release=tutor_env.get_env_release(context.obj.root), ) 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=True) 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 and push them to your private repository now by running the following commands in a different shell: tutor images build all # add your custom images here tutor images push all Press enter when you are ready to continue""" click.confirm(fmt.question(question), default=True, abort=True, prompt_suffix=" ") click.echo(fmt.title("Starting the platform")) context.invoke(start) click.echo(fmt.title("Database creation and migrations")) context.invoke(init, limit=None) config = tutor_config.load(context.obj.root) fmt.echo_info( """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"], ))
def test_plugin_templates(self) -> None: with tempfile.TemporaryDirectory() as plugin_templates: DictPlugin({ "name": "plugin1", "version": "0", "templates": plugin_templates }) # Create two templates os.makedirs(os.path.join(plugin_templates, "plugin1", "apps")) with open( os.path.join(plugin_templates, "plugin1", "unrendered.txt"), "w", encoding="utf-8", ) as f: f.write("This file should not be rendered") with open( os.path.join(plugin_templates, "plugin1", "apps", "rendered.txt"), "w", encoding="utf-8", ) as f: f.write("Hello my ID is {{ ID }}") # Render templates with temporary_root() as root: # Create configuration config: Config = tutor_config.load_full(root) config["ID"] = "Hector Rumblethorpe" plugins.load("plugin1") tutor_config.save_enabled_plugins(config) # Render environment with patch.object(fmt, "STDOUT"): env.save(root, config) # Check that plugin template was rendered root_env = os.path.join(root, "env") dst_unrendered = os.path.join(root_env, "plugins", "plugin1", "unrendered.txt") dst_rendered = os.path.join(root_env, "plugins", "plugin1", "apps", "rendered.txt") self.assertFalse(os.path.exists(dst_unrendered)) self.assertTrue(os.path.exists(dst_rendered)) with open(dst_rendered, encoding="utf-8") as f: self.assertEqual("Hello my ID is Hector Rumblethorpe", f.read())
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"], ) )
def upgrade_from_maple(context: click.Context, config: Config) -> None: click.echo(fmt.title("Upgrading from Maple")) # The environment needs to be updated because the management commands are from Nutmeg tutor_env.save(context.obj.root, config) # Command backpopulate_user_tours context.invoke( compose.run, args=["lms", "sh", "-e", "-c", "./manage.py lms migrate user_tours"], ) context.invoke( compose.run, args=[ "lms", "sh", "-e", "-c", "./manage.py lms backpopulate_user_tours" ], ) # Command backfill_course_tabs context.invoke( compose.run, args=["cms", "sh", "-e", "-c", "./manage.py cms migrate contentstore"], ) context.invoke( compose.run, args=[ "cms", "sh", "-e", "-c", "./manage.py cms migrate split_modulestore_django", ], ) context.invoke( compose.run, args=["cms", "sh", "-e", "-c", "./manage.py cms backfill_course_tabs"], ) # Command simulate_publish context.invoke( compose.run, args=[ "cms", "sh", "-e", "-c", "./manage.py cms migrate course_overviews" ], ) context.invoke( compose.run, args=["cms", "sh", "-e", "-c", "./manage.py cms simulate_publish"], )
def upgrade_from_ironwood(context: click.Context, config: Config) -> None: click.echo(fmt.title("Upgrading from Ironwood")) tutor_env.save(context.obj.root, config) click.echo(fmt.title("Stopping any existing platform")) context.invoke(compose.stop) if not config["RUN_MONGODB"]: fmt.echo_info( "You are not running MongoDB (RUN_MONGODB=false). It is your " "responsibility to upgrade your MongoDb instance to v3.6. There is " "nothing left to do to upgrade from Ironwood to Juniper.") return upgrade_mongodb(context, config, "3.4", "3.4") context.invoke(compose.stop) upgrade_mongodb(context, config, "3.6", "3.6") context.invoke(compose.stop)
def upgrade_mongodb( context: click.Context, config: Config, to_docker_version: str, to_compatibility_version: str, ) -> None: click.echo(fmt.title(f"Upgrading MongoDb to v{to_docker_version}")) # Note that the DOCKER_IMAGE_MONGODB value is never saved, because we only save the # environment, not the configuration. config["DOCKER_IMAGE_MONGODB"] = f"mongo:{to_docker_version}" tutor_env.save(context.obj.root, config) context.invoke(compose.start, detach=True, services=["mongodb"]) fmt.echo_info("Waiting for mongodb to boot...") sleep(10) context.invoke( compose.execute, args=[ "mongodb", "mongo", "--eval", f'db.adminCommand({{ setFeatureCompatibilityVersion: "{to_compatibility_version}" }})', ], ) context.invoke(compose.stop)
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"], ))