mirror of
https://github.com/ChristianLight/tutor.git
synced 2024-12-13 14:43:03 +00:00
fix: attempt to make upgrade much clearer
`upgrade` had several issues, which are summarized here: https://discuss.overhang.io/t/confusing-instructions-during-upgrade/2281/7 - The docs say that you should run quickstart, but what most people will see is the big command tutor local upgrade --from=lilac verbatim paragraph. - The local upgrade command should be very explicit about the fact that users need to run quickstart. - Maybe the name of the local upgrade command should be improved. - When upgrading tutor from one major release to the next, there should be a more explicit warning to inform users of what they are doing (see this other conversation 1) - We should tell people that they almost certainly need to enable the tutor and the mfe plugins, if they are not enabled during upgrade. - A link to all of the breaking changes from the changelog should be prominently displayed during upgrade. - The docs should emphasize that upgrading from one major release to the next is potentially a risky endeavor and that downgrading is not possible. The docs should also link to the changelog. This commit has grown slightly beyond the intended scope, but the changes should be mostly positive.
This commit is contained in:
parent
1daba42f1e
commit
4dc772d1e4
@ -4,10 +4,11 @@ Note: Breaking changes between versions are indicated by "💥".
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- [Improvement] Provide much more comprehensive instructions when upgrading.
|
||||||
- [Bugfix] During upgrade, make sure that environment is up-to-date prior to prompting to rebuild the custom images.
|
- [Bugfix] During upgrade, make sure that environment is up-to-date prior to prompting to rebuild the custom images.
|
||||||
- [Bugfix] Fix ownership of mysql data, in particular when upgrading a Kubernetes cluster to Maple.
|
- [Bugfix] Fix ownership of mysql data, in particular when upgrading a Kubernetes cluster to Maple.
|
||||||
- [Bugfix] Ensure that ``tutor k8s upgrade`` is run during ``tutor k8s quickstart``, when necessary.
|
- [Bugfix] Ensure that ``tutor k8s upgrade`` is run during ``tutor k8s quickstart``, when necessary.
|
||||||
- [Bugfix] By default, upgrade from Lilac and not Koa during ``tutor k8s upgrade``.
|
- 💥[Bugfix] By default, detect the current version during ``tutor k8s/local upgrade``.
|
||||||
- [Bugfix] Fix upgrading from Lilac to Maple on Kubernetes by deleting deployments and services.
|
- [Bugfix] Fix upgrading from Lilac to Maple on Kubernetes by deleting deployments and services.
|
||||||
|
|
||||||
## v13.0.3 (2022-01-04)
|
## v13.0.3 (2022-01-04)
|
||||||
|
@ -87,11 +87,45 @@ Tutor can be launched on Amazon Web Services very quickly with the `official Tut
|
|||||||
Upgrading
|
Upgrading
|
||||||
---------
|
---------
|
||||||
|
|
||||||
With Tutor, it is very easy to upgrade to a more recent Open edX or Tutor release. Just install the latest ``tutor`` version (using either methods above) and run the ``quickstart`` command again. If you have :ref:`customised <configuration_customisation>` your docker images, you will have to re-build them prior to running ``quickstart``.
|
To upgrade Open edX or benefit from the latest features and bug fixes, you should simply upgrade Tutor. Start by upgrading the "tutor" package and its dependencies::
|
||||||
|
|
||||||
``quickstart`` should take care of automatically running the upgrade process. If for some reason you need to *manually* upgrade from an Open edX release to the next, you should run ``tutor local upgrade``. For instance, to upgrade from Lilac to Maple, run::
|
pip install --upgrade tutor[full]
|
||||||
|
|
||||||
|
Then run the ``quickstart`` command again. Depending on your deployment target, run either::
|
||||||
|
|
||||||
|
tutor local quickstart # for local installations
|
||||||
|
tutor k8s quickstart # for Kubernetes installation
|
||||||
|
|
||||||
|
Upgrading with custom Docker images
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If you run :ref:`customised <configuration_customisation>` Docker images, you need to rebuild them prior to running ``quickstart``::
|
||||||
|
|
||||||
|
tutor config save
|
||||||
|
tutor images build all # specify here the images that you need to build
|
||||||
|
tutor local quickstart
|
||||||
|
|
||||||
|
Upgrading to a new Open edX release
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Major Open edX releases are published twice a year, in June and December, by the Open edX `Build/Test/Release working group <https://discuss.openedx.org/c/working-groups/build-test-release/30>`__. When a new Open edX release comes out, Tutor gets a major version bump (see :ref:`versioning`). Such an upgrade typically includes multiple breaking changes. Any upgrade is final, because downgrading is not supported. Thus, when upgrading your platform from one major version to the next, it is strongly recommended to do the following:
|
||||||
|
|
||||||
|
1. Read the changes listed in the `CHANGELOG.md <https://github.com/overhangio/tutor/blob/master/CHANGELOG.md>`__ file. Breaking changes are identified by a "💥".
|
||||||
|
2. Perform a backup. On a local installation, this is typically done with::
|
||||||
|
|
||||||
|
tutor local stop
|
||||||
|
sudo rsync -avr "$(tutor config printroot)"/ /tmp/tutor-backup/
|
||||||
|
|
||||||
|
3. If you created custom plugins, make sure that they are compatible with the newer release.
|
||||||
|
4. Test the new release in a sandboxed environment.
|
||||||
|
5. If you are running edx-platform, or some other repository from a custom branch, then you should rebase (and test) your changes on top of the latest release tag (see :ref:`edx_platform_fork`).
|
||||||
|
|
||||||
|
The process for upgrading from one major release to the next works similarly to any other upgrade, with the ``quickstart`` command (see above). The single difference is that if the ``quickstart`` command detects that your tutor environment was generated with an older release, it will perform a few release-specific upgrade steps. These extra upgrade steps will be performed just once. But they will be ignored if you updated your local environment (for instance: with ``tutor config save``) prior to running ``quickstart``. This situation typically occurs if you need to re-build some Docker images (see above). In such a case, you should make use of the ``upgrade`` command. For instance, to upgrade a local installation from Lilac to Maple and rebuild some Docker images, run::
|
||||||
|
|
||||||
|
tutor config save
|
||||||
|
tutor images build all # list the images that should be rebuilt here
|
||||||
tutor local upgrade --from=lilac
|
tutor local upgrade --from=lilac
|
||||||
|
tutor local quickstart
|
||||||
|
|
||||||
.. _autocomplete:
|
.. _autocomplete:
|
||||||
|
|
||||||
|
@ -213,11 +213,13 @@ class EnvTests(unittest.TestCase):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CurrentVersionTests(unittest.TestCase):
|
||||||
def test_current_version_in_empty_env(self) -> None:
|
def test_current_version_in_empty_env(self) -> None:
|
||||||
with temporary_root() as root:
|
with temporary_root() as root:
|
||||||
self.assertIsNone(env.current_version(root))
|
self.assertIsNone(env.current_version(root))
|
||||||
self.assertIsNone(env.current_release(root))
|
self.assertIsNone(env.get_env_release(root))
|
||||||
self.assertFalse(env.needs_major_upgrade(root))
|
self.assertIsNone(env.should_upgrade_from_release(root))
|
||||||
self.assertTrue(env.is_up_to_date(root))
|
self.assertTrue(env.is_up_to_date(root))
|
||||||
|
|
||||||
def test_current_version_in_lilac_env(self) -> None:
|
def test_current_version_in_lilac_env(self) -> None:
|
||||||
@ -230,8 +232,8 @@ class EnvTests(unittest.TestCase):
|
|||||||
) as f:
|
) as f:
|
||||||
f.write("12.0.46")
|
f.write("12.0.46")
|
||||||
self.assertEqual("12.0.46", env.current_version(root))
|
self.assertEqual("12.0.46", env.current_version(root))
|
||||||
self.assertEqual("lilac", env.current_release(root))
|
self.assertEqual("lilac", env.get_env_release(root))
|
||||||
self.assertTrue(env.needs_major_upgrade(root))
|
self.assertEqual("lilac", env.should_upgrade_from_release(root))
|
||||||
self.assertFalse(env.is_up_to_date(root))
|
self.assertFalse(env.is_up_to_date(root))
|
||||||
|
|
||||||
def test_current_version_in_latest_env(self) -> None:
|
def test_current_version_in_latest_env(self) -> None:
|
||||||
@ -244,6 +246,6 @@ class EnvTests(unittest.TestCase):
|
|||||||
) as f:
|
) as f:
|
||||||
f.write(__version__)
|
f.write(__version__)
|
||||||
self.assertEqual(__version__, env.current_version(root))
|
self.assertEqual(__version__, env.current_version(root))
|
||||||
self.assertEqual("maple", env.current_release(root))
|
self.assertEqual("maple", env.get_env_release(root))
|
||||||
self.assertFalse(env.needs_major_upgrade(root))
|
self.assertIsNone(env.should_upgrade_from_release(root))
|
||||||
self.assertTrue(env.is_up_to_date(root))
|
self.assertTrue(env.is_up_to_date(root))
|
||||||
|
@ -51,11 +51,11 @@ class K8sJobRunner(jobs.BaseJobRunner):
|
|||||||
job_name = job["metadata"]["name"]
|
job_name = job["metadata"]["name"]
|
||||||
if not isinstance(job_name, str):
|
if not isinstance(job_name, str):
|
||||||
raise exceptions.TutorError(
|
raise exceptions.TutorError(
|
||||||
"Invalid job name: '{}'. Expected str.".format(job_name)
|
f"Invalid job name: '{job_name}'. Expected str."
|
||||||
)
|
)
|
||||||
if job_name == name:
|
if job_name == name:
|
||||||
return job
|
return job
|
||||||
raise exceptions.TutorError("Could not find job '{}'".format(name))
|
raise exceptions.TutorError(f"Could not find job '{name}'")
|
||||||
|
|
||||||
def active_job_names(self) -> List[str]:
|
def active_job_names(self) -> List[str]:
|
||||||
"""
|
"""
|
||||||
@ -71,7 +71,7 @@ class K8sJobRunner(jobs.BaseJobRunner):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def run_job(self, service: str, command: str) -> int:
|
def run_job(self, service: str, command: str) -> int:
|
||||||
job_name = "{}-job".format(service)
|
job_name = f"{service}-job"
|
||||||
job = self.load_job(job_name)
|
job = self.load_job(job_name)
|
||||||
# Create a unique job name to make it deduplicate jobs and make it easier to
|
# Create a unique job name to make it deduplicate jobs and make it easier to
|
||||||
# find later. Logs of older jobs will remain available for some time.
|
# find later. Logs of older jobs will remain available for some time.
|
||||||
@ -83,7 +83,7 @@ class K8sJobRunner(jobs.BaseJobRunner):
|
|||||||
if not active_jobs:
|
if not active_jobs:
|
||||||
break
|
break
|
||||||
fmt.echo_info(
|
fmt.echo_info(
|
||||||
"Waiting for active jobs to terminate: {}".format(" ".join(active_jobs))
|
f"Waiting for active jobs to terminate: {' '.join(active_jobs)}"
|
||||||
)
|
)
|
||||||
sleep(5)
|
sleep(5)
|
||||||
|
|
||||||
@ -106,7 +106,9 @@ class K8sJobRunner(jobs.BaseJobRunner):
|
|||||||
job["spec"]["backoffLimit"] = 1
|
job["spec"]["backoffLimit"] = 1
|
||||||
job["spec"]["ttlSecondsAfterFinished"] = 3600
|
job["spec"]["ttlSecondsAfterFinished"] = 3600
|
||||||
# Save patched job to "jobs.yml" file
|
# Save patched job to "jobs.yml" file
|
||||||
with open(tutor_env.pathjoin(self.root, "k8s", "jobs.yml"), "w") as job_file:
|
with open(
|
||||||
|
tutor_env.pathjoin(self.root, "k8s", "jobs.yml"), "w", encoding="utf-8"
|
||||||
|
) as job_file:
|
||||||
serialize.dump(job, job_file)
|
serialize.dump(job, job_file)
|
||||||
# We cannot use the k8s API to create the job: configMap and volume names need
|
# We cannot use the k8s API to create the job: configMap and volume names need
|
||||||
# to be found with the right suffixes.
|
# to be found with the right suffixes.
|
||||||
@ -115,7 +117,7 @@ class K8sJobRunner(jobs.BaseJobRunner):
|
|||||||
"--kustomize",
|
"--kustomize",
|
||||||
tutor_env.pathjoin(self.root),
|
tutor_env.pathjoin(self.root),
|
||||||
"--selector",
|
"--selector",
|
||||||
"app.kubernetes.io/name={}".format(job_name),
|
f"app.kubernetes.io/name={job_name}",
|
||||||
)
|
)
|
||||||
|
|
||||||
message = (
|
message = (
|
||||||
@ -127,7 +129,7 @@ class K8sJobRunner(jobs.BaseJobRunner):
|
|||||||
fmt.echo_info(message)
|
fmt.echo_info(message)
|
||||||
|
|
||||||
# Wait for completion
|
# Wait for completion
|
||||||
field_selector = "metadata.name={}".format(job_name)
|
field_selector = f"metadata.name={job_name}"
|
||||||
while True:
|
while True:
|
||||||
namespaced_jobs = K8sClients.instance().batch_api.list_namespaced_job(
|
namespaced_jobs = K8sClients.instance().batch_api.list_namespaced_job(
|
||||||
k8s_namespace(self.config), field_selector=field_selector
|
k8s_namespace(self.config), field_selector=field_selector
|
||||||
@ -137,13 +139,11 @@ class K8sJobRunner(jobs.BaseJobRunner):
|
|||||||
job = namespaced_jobs.items[0]
|
job = namespaced_jobs.items[0]
|
||||||
if not job.status.active:
|
if not job.status.active:
|
||||||
if job.status.succeeded:
|
if job.status.succeeded:
|
||||||
fmt.echo_info("Job {} successful.".format(job_name))
|
fmt.echo_info(f"Job {job_name} successful.")
|
||||||
break
|
break
|
||||||
if job.status.failed:
|
if job.status.failed:
|
||||||
raise exceptions.TutorError(
|
raise exceptions.TutorError(
|
||||||
"Job {} failed. View the job logs to debug this issue.".format(
|
f"Job {job_name} failed. View the job logs to debug this issue."
|
||||||
job_name
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
sleep(5)
|
sleep(5)
|
||||||
return 0
|
return 0
|
||||||
@ -158,12 +158,12 @@ def k8s() -> None:
|
|||||||
@click.option("-I", "--non-interactive", is_flag=True, help="Run non-interactively")
|
@click.option("-I", "--non-interactive", is_flag=True, help="Run non-interactively")
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def quickstart(context: click.Context, non_interactive: bool) -> None:
|
def quickstart(context: click.Context, non_interactive: bool) -> None:
|
||||||
if tutor_env.needs_major_upgrade(context.obj.root):
|
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"))
|
click.echo(fmt.title("Upgrading from an older release"))
|
||||||
context.invoke(
|
context.invoke(
|
||||||
upgrade,
|
upgrade,
|
||||||
from_version=tutor_env.current_release(context.obj.root),
|
from_version=tutor_env.get_env_release(context.obj.root),
|
||||||
non_interactive=non_interactive,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
click.echo(fmt.title("Interactive platform configuration"))
|
click.echo(fmt.title("Interactive platform configuration"))
|
||||||
@ -171,21 +171,28 @@ def quickstart(context: click.Context, non_interactive: bool) -> None:
|
|||||||
config_save_command,
|
config_save_command,
|
||||||
interactive=(not non_interactive),
|
interactive=(not non_interactive),
|
||||||
)
|
)
|
||||||
config = tutor_config.load(context.obj.root)
|
|
||||||
if not config["ENABLE_WEB_PROXY"]:
|
if run_upgrade_from_release and not non_interactive:
|
||||||
fmt.echo_alert(
|
question = f"""Your platform is being upgraded from {run_upgrade_from_release.capitalize()}.
|
||||||
"Potentially invalid configuration: ENABLE_WEB_PROXY=false\n"
|
|
||||||
"This setting might have been defined because you previously set WEB_PROXY=true. This is no longer"
|
If you run custom Docker images, you must rebuild and push them to your private repository now by running the following
|
||||||
" necessary in order to get Tutor to work on Kubernetes. In Tutor v11+ a Caddy-based load balancer is"
|
commands in a different shell:
|
||||||
" provided out of the box to handle SSL/TLS certificate generation at runtime. If you disable this"
|
|
||||||
" service, you will have to configure an Ingress resource and a certificate manager yourself to redirect"
|
tutor images build all # add your custom images here
|
||||||
" traffic to the caddy service. See the Kubernetes section in the Tutor documentation for more"
|
tutor images push all
|
||||||
" information."
|
|
||||||
|
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"))
|
click.echo(fmt.title("Starting the platform"))
|
||||||
context.invoke(start)
|
context.invoke(start)
|
||||||
|
|
||||||
click.echo(fmt.title("Database creation and migrations"))
|
click.echo(fmt.title("Database creation and migrations"))
|
||||||
context.invoke(init, limit=None)
|
context.invoke(init, limit=None)
|
||||||
|
|
||||||
|
config = tutor_config.load(context.obj.root)
|
||||||
fmt.echo_info(
|
fmt.echo_info(
|
||||||
"""Your Open edX platform is ready and can be accessed at the following urls:
|
"""Your Open edX platform is ready and can be accessed at the following urls:
|
||||||
|
|
||||||
@ -253,7 +260,7 @@ def start(context: Context, names: List[str]) -> None:
|
|||||||
"--kustomize",
|
"--kustomize",
|
||||||
tutor_env.pathjoin(context.root),
|
tutor_env.pathjoin(context.root),
|
||||||
"--selector",
|
"--selector",
|
||||||
"app.kubernetes.io/name={}".format(name),
|
f"app.kubernetes.io/name={name}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -345,8 +352,8 @@ def scale(context: Context, deployment: str, replicas: int) -> None:
|
|||||||
*resource_namespace_selector(
|
*resource_namespace_selector(
|
||||||
config,
|
config,
|
||||||
),
|
),
|
||||||
"--replicas={}".format(replicas),
|
f"--replicas={replicas}",
|
||||||
"deployment/{}".format(deployment),
|
f"deployment/{deployment}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -443,29 +450,41 @@ def wait(context: Context, name: str) -> None:
|
|||||||
wait_for_pod_ready(config, name)
|
wait_for_pod_ready(config, name)
|
||||||
|
|
||||||
|
|
||||||
@click.command(help="Upgrade from a previous Open edX named release")
|
@click.command(
|
||||||
|
short_help="Perform release-specific upgrade tasks",
|
||||||
|
help="Perform release-specific upgrade tasks. To perform a full upgrade remember to run `quickstart`.",
|
||||||
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--from",
|
"--from",
|
||||||
"from_version",
|
"from_release",
|
||||||
default="lilac",
|
|
||||||
type=click.Choice(["ironwood", "juniper", "koa", "lilac"]),
|
type=click.Choice(["ironwood", "juniper", "koa", "lilac"]),
|
||||||
)
|
)
|
||||||
@click.option("-I", "--non-interactive", is_flag=True, help="Run non-interactively")
|
@click.pass_context
|
||||||
@click.pass_obj
|
def upgrade(context: click.Context, from_release: Optional[str]) -> None:
|
||||||
def upgrade(context: Context, from_version: str, non_interactive: bool) -> None:
|
if from_release is None:
|
||||||
upgrade_from(context, from_version, interactive=not non_interactive)
|
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:
|
||||||
|
fmt.echo_alert(
|
||||||
|
"This command only performs a partial upgrade of your Open edX platform. "
|
||||||
|
"To perform a full upgrade, you should run `tutor k8s quickstart`."
|
||||||
|
)
|
||||||
|
upgrade_from(context.obj, from_release)
|
||||||
|
# We update the environment to update the version
|
||||||
|
context.invoke(config_save_command)
|
||||||
|
|
||||||
|
|
||||||
def kubectl_exec(
|
def kubectl_exec(
|
||||||
config: Config, service: str, command: str, attach: bool = False
|
config: Config, service: str, command: str, attach: bool = False
|
||||||
) -> int:
|
) -> int:
|
||||||
selector = "app.kubernetes.io/name={}".format(service)
|
selector = f"app.kubernetes.io/name={service}"
|
||||||
pods = K8sClients.instance().core_api.list_namespaced_pod(
|
pods = K8sClients.instance().core_api.list_namespaced_pod(
|
||||||
namespace=k8s_namespace(config), label_selector=selector
|
namespace=k8s_namespace(config), label_selector=selector
|
||||||
)
|
)
|
||||||
if not pods.items:
|
if not pods.items:
|
||||||
raise exceptions.TutorError(
|
raise exceptions.TutorError(
|
||||||
"Could not find an active pod for the {} service".format(service)
|
f"Could not find an active pod for the {service} service"
|
||||||
)
|
)
|
||||||
pod_name = pods.items[0].metadata.name
|
pod_name = pods.items[0].metadata.name
|
||||||
|
|
||||||
@ -486,10 +505,10 @@ def kubectl_exec(
|
|||||||
|
|
||||||
|
|
||||||
def wait_for_pod_ready(config: Config, service: str) -> None:
|
def wait_for_pod_ready(config: Config, service: str) -> None:
|
||||||
fmt.echo_info("Waiting for a {} pod to be ready...".format(service))
|
fmt.echo_info(f"Waiting for a {service} pod to be ready...")
|
||||||
utils.kubectl(
|
utils.kubectl(
|
||||||
"wait",
|
"wait",
|
||||||
*resource_selector(config, "app.kubernetes.io/name={}".format(service)),
|
*resource_selector(config, f"app.kubernetes.io/name={service}"),
|
||||||
"--for=condition=ContainersReady",
|
"--for=condition=ContainersReady",
|
||||||
"--timeout=600s",
|
"--timeout=600s",
|
||||||
"pod",
|
"pod",
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
from tutor import config as tutor_config
|
from tutor import config as tutor_config
|
||||||
@ -27,6 +29,7 @@ class LocalJobRunner(compose.ComposeJobRunner):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
class LocalContext(compose.BaseComposeContext):
|
class LocalContext(compose.BaseComposeContext):
|
||||||
def job_runner(self, config: Config) -> LocalJobRunner:
|
def job_runner(self, config: Config) -> LocalJobRunner:
|
||||||
return LocalJobRunner(self.root, config)
|
return LocalJobRunner(self.root, config)
|
||||||
@ -56,16 +59,49 @@ Tutor may not work if Docker is configured with < 4 GB RAM. Please follow instru
|
|||||||
https://docs.tutor.overhang.io/install.html"""
|
https://docs.tutor.overhang.io/install.html"""
|
||||||
)
|
)
|
||||||
|
|
||||||
if tutor_env.needs_major_upgrade(context.obj.root):
|
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"))
|
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(
|
context.invoke(
|
||||||
upgrade,
|
upgrade,
|
||||||
from_version=tutor_env.current_release(context.obj.root),
|
from_version=run_upgrade_from_release,
|
||||||
non_interactive=non_interactive,
|
non_interactive=non_interactive,
|
||||||
)
|
)
|
||||||
|
|
||||||
click.echo(fmt.title("Interactive platform configuration"))
|
click.echo(fmt.title("Interactive platform configuration"))
|
||||||
context.invoke(config_save_command, interactive=(not non_interactive))
|
context.invoke(config_save_command, interactive=(not non_interactive))
|
||||||
|
|
||||||
|
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"))
|
click.echo(fmt.title("Stopping any existing platform"))
|
||||||
context.invoke(compose.stop)
|
context.invoke(compose.stop)
|
||||||
if pullimages:
|
if pullimages:
|
||||||
@ -91,17 +127,29 @@ Your Open edX platform is ready and can be accessed at the following urls:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@click.command(help="Upgrade from a previous Open edX named release")
|
@click.command(
|
||||||
|
short_help="Perform release-specific upgrade tasks",
|
||||||
|
help="Perform release-specific upgrade tasks. To perform a full upgrade remember to run `quickstart`.",
|
||||||
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--from",
|
"--from",
|
||||||
"from_version",
|
"from_release",
|
||||||
default="koa",
|
|
||||||
type=click.Choice(["ironwood", "juniper", "koa", "lilac"]),
|
type=click.Choice(["ironwood", "juniper", "koa", "lilac"]),
|
||||||
)
|
)
|
||||||
@click.option("-I", "--non-interactive", is_flag=True, help="Run non-interactively")
|
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def upgrade(context: click.Context, from_version: str, non_interactive: bool) -> None:
|
def upgrade(context: click.Context, from_release: Optional[str]) -> None:
|
||||||
upgrade_from(context, from_version, not non_interactive)
|
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)
|
||||||
|
|
||||||
|
|
||||||
local.add_command(quickstart)
|
local.add_command(quickstart)
|
||||||
|
31
tutor/commands/upgrade/common.py
Normal file
31
tutor/commands/upgrade/common.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from tutor import fmt
|
||||||
|
from tutor import plugins
|
||||||
|
from tutor.types import Config
|
||||||
|
|
||||||
|
|
||||||
|
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_enabled(config, "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.enable(config, "forum")
|
||||||
|
|
||||||
|
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_enabled(config, "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.enable(config, "mfe")
|
@ -1,51 +1,30 @@
|
|||||||
import click
|
|
||||||
|
|
||||||
from tutor import config as tutor_config
|
from tutor import config as tutor_config
|
||||||
from tutor import env as tutor_env
|
|
||||||
from tutor import fmt
|
from tutor import fmt
|
||||||
from tutor.commands import k8s
|
from tutor.commands import k8s
|
||||||
from tutor.commands.context import Context
|
from tutor.commands.context import Context
|
||||||
from tutor.types import Config
|
from tutor.types import Config
|
||||||
|
from . import common as common_upgrade
|
||||||
|
|
||||||
|
|
||||||
def upgrade_from(
|
def upgrade_from(context: Context, from_release: str) -> None:
|
||||||
context: Context, from_version: str, interactive: bool = False
|
|
||||||
) -> None:
|
|
||||||
config = tutor_config.load(context.root)
|
config = tutor_config.load(context.root)
|
||||||
|
|
||||||
running_version = from_version
|
running_release = from_release
|
||||||
if running_version == "ironwood":
|
if running_release == "ironwood":
|
||||||
upgrade_from_ironwood(config)
|
upgrade_from_ironwood(config)
|
||||||
running_version = "juniper"
|
running_release = "juniper"
|
||||||
|
|
||||||
if running_version == "juniper":
|
if running_release == "juniper":
|
||||||
upgrade_from_juniper(config)
|
upgrade_from_juniper(config)
|
||||||
running_version = "koa"
|
running_release = "koa"
|
||||||
|
|
||||||
if running_version == "koa":
|
if running_release == "koa":
|
||||||
upgrade_from_koa(config)
|
upgrade_from_koa(config)
|
||||||
running_version = "lilac"
|
running_release = "lilac"
|
||||||
|
|
||||||
if running_version == "lilac":
|
if running_release == "lilac":
|
||||||
upgrade_from_lilac(config)
|
upgrade_from_lilac(config)
|
||||||
running_version = "maple"
|
running_release = "maple"
|
||||||
|
|
||||||
# Update env such that the build environment is up-to-date
|
|
||||||
tutor_env.save(context.root, config)
|
|
||||||
if interactive:
|
|
||||||
question = f"""Your platform was successfuly upgraded from {from_version} to {running_version}.
|
|
||||||
|
|
||||||
Depending on your setup, you might have to rebuild some of your Docker images
|
|
||||||
and push them to your private registry (if any). You can do this now by running
|
|
||||||
the following command in a different shell:
|
|
||||||
|
|
||||||
tutor images build openedx # add your custom images here
|
|
||||||
tutor images push openedx
|
|
||||||
|
|
||||||
Press enter when you are ready to continue"""
|
|
||||||
click.confirm(
|
|
||||||
fmt.question(question), default=True, abort=True, prompt_suffix=" "
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade_from_ironwood(config: Config) -> None:
|
def upgrade_from_ironwood(config: Config) -> None:
|
||||||
@ -116,6 +95,7 @@ your MongoDb cluster from v3.6 to v4.0. You should run something similar to:
|
|||||||
|
|
||||||
|
|
||||||
def upgrade_from_lilac(config: Config) -> None:
|
def upgrade_from_lilac(config: Config) -> None:
|
||||||
|
common_upgrade.upgrade_from_lilac(config)
|
||||||
fmt.echo_info(
|
fmt.echo_info(
|
||||||
"All Kubernetes services and deployments need to be deleted during "
|
"All Kubernetes services and deployments need to be deleted during "
|
||||||
"upgrade from Lilac to Maple"
|
"upgrade from Lilac to Maple"
|
||||||
|
@ -7,59 +7,28 @@ from tutor import env as tutor_env
|
|||||||
from tutor import fmt
|
from tutor import fmt
|
||||||
from tutor.commands import compose
|
from tutor.commands import compose
|
||||||
from tutor.types import Config
|
from tutor.types import Config
|
||||||
|
from . import common as common_upgrade
|
||||||
|
|
||||||
|
|
||||||
def upgrade_from(
|
def upgrade_from(context: click.Context, from_release: str) -> None:
|
||||||
context: click.Context, from_version: str, interactive: bool = False
|
# Make sure to bypass current version check
|
||||||
) -> None:
|
config = tutor_config.load_full(context.obj.root)
|
||||||
config = tutor_config.load(context.obj.root)
|
running_release = from_release
|
||||||
|
if running_release == "ironwood":
|
||||||
if interactive:
|
|
||||||
question = """You are about to upgrade your Open edX platform.
|
|
||||||
|
|
||||||
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=" "
|
|
||||||
)
|
|
||||||
|
|
||||||
running_version = from_version
|
|
||||||
if running_version == "ironwood":
|
|
||||||
upgrade_from_ironwood(context, config)
|
upgrade_from_ironwood(context, config)
|
||||||
running_version = "juniper"
|
running_release = "juniper"
|
||||||
|
|
||||||
if running_version == "juniper":
|
if running_release == "juniper":
|
||||||
upgrade_from_juniper(context, config)
|
upgrade_from_juniper(context, config)
|
||||||
running_version = "koa"
|
running_release = "koa"
|
||||||
|
|
||||||
if running_version == "koa":
|
if running_release == "koa":
|
||||||
upgrade_from_koa(context, config)
|
upgrade_from_koa(context, config)
|
||||||
running_version = "lilac"
|
running_release = "lilac"
|
||||||
|
|
||||||
if running_version == "lilac":
|
if running_release == "lilac":
|
||||||
# Nothing to do here
|
common_upgrade.upgrade_from_lilac(config)
|
||||||
running_version = "maple"
|
running_release = "maple"
|
||||||
|
|
||||||
# Update env such that the build environment is up-to-date
|
|
||||||
tutor_env.save(context.obj.root, config)
|
|
||||||
if interactive:
|
|
||||||
question = f"""Your platform was successfuly upgraded from {from_version} to {running_version}.
|
|
||||||
|
|
||||||
Depending on your setup, you might have to rebuild some of your Docker images.
|
|
||||||
You can do this now by running the following command in a different shell:
|
|
||||||
|
|
||||||
tutor images build openedx # add your custom images here
|
|
||||||
|
|
||||||
Press enter when you are ready to continue"""
|
|
||||||
click.confirm(
|
|
||||||
fmt.question(question), default=True, abort=True, prompt_suffix=" "
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade_from_ironwood(context: click.Context, config: Config) -> None:
|
def upgrade_from_ironwood(context: click.Context, config: Config) -> None:
|
||||||
@ -114,6 +83,7 @@ def upgrade_from_juniper(context: click.Context, config: Config) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def upgrade_from_koa(context: click.Context, config: Config) -> None:
|
def upgrade_from_koa(context: click.Context, config: Config) -> None:
|
||||||
|
click.echo(fmt.title("Upgrading from Koa"))
|
||||||
if not config["RUN_MONGODB"]:
|
if not config["RUN_MONGODB"]:
|
||||||
fmt.echo_info(
|
fmt.echo_info(
|
||||||
"You are not running MongDB (RUN_MONGODB=false). It is your "
|
"You are not running MongDB (RUN_MONGODB=false). It is your "
|
||||||
|
46
tutor/env.py
46
tutor/env.py
@ -311,25 +311,41 @@ def is_up_to_date(root: str) -> bool:
|
|||||||
return current is None or current == __version__
|
return current is None or current == __version__
|
||||||
|
|
||||||
|
|
||||||
def needs_major_upgrade(root: str) -> bool:
|
def should_upgrade_from_release(root: str) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Return true if the current version is less than the tutor version.
|
Return the name of the currently installed release that we should upgrade from. Return None If we already run the
|
||||||
"""
|
latest release.
|
||||||
current = current_version(root)
|
|
||||||
if current is None:
|
|
||||||
return False
|
|
||||||
current_as_int = int(current.split(".")[0])
|
|
||||||
required = int(__version__.split(".", maxsplit=1)[0])
|
|
||||||
return 0 < current_as_int < required
|
|
||||||
|
|
||||||
|
|
||||||
def current_release(root: str) -> Optional[str]:
|
|
||||||
"""
|
|
||||||
Return the name of the current Open edX release. If the current environment has no version, return None.
|
|
||||||
"""
|
"""
|
||||||
current = current_version(root)
|
current = current_version(root)
|
||||||
if current is None:
|
if current is None:
|
||||||
return None
|
return None
|
||||||
|
current_as_int = int(current.split(".")[0])
|
||||||
|
required_as_int = int(__version__.split(".", maxsplit=1)[0])
|
||||||
|
if current_as_int >= required_as_int:
|
||||||
|
return None
|
||||||
|
return get_release(current)
|
||||||
|
|
||||||
|
|
||||||
|
def get_env_release(root: str) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
Return the Open edX release name from the current environment.
|
||||||
|
|
||||||
|
If the current environment has no version, return None.
|
||||||
|
"""
|
||||||
|
version = current_version(root)
|
||||||
|
if version is None:
|
||||||
|
return None
|
||||||
|
return get_release(version)
|
||||||
|
|
||||||
|
|
||||||
|
def get_package_release() -> str:
|
||||||
|
"""
|
||||||
|
Return the release name associated to this package.
|
||||||
|
"""
|
||||||
|
return get_release(__version__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_release(version: str) -> str:
|
||||||
return {
|
return {
|
||||||
"0": "ironwood",
|
"0": "ironwood",
|
||||||
"3": "ironwood",
|
"3": "ironwood",
|
||||||
@ -337,7 +353,7 @@ def current_release(root: str) -> Optional[str]:
|
|||||||
"11": "koa",
|
"11": "koa",
|
||||||
"12": "lilac",
|
"12": "lilac",
|
||||||
"13": "maple",
|
"13": "maple",
|
||||||
}[current.split(".")[0]]
|
}[version.split(".", maxsplit=1)[0]]
|
||||||
|
|
||||||
|
|
||||||
def current_version(root: str) -> Optional[str]:
|
def current_version(root: str) -> Optional[str]:
|
||||||
|
Loading…
Reference in New Issue
Block a user