From 2dbce5ea1e8332245f5fd3913d4f81ba30cc41cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mois=C3=A9s=20Gonz=C3=A1lez?= Date: Mon, 15 Jul 2024 14:24:20 -0400 Subject: [PATCH] feat: support for redwood (#9) * allow MFEs to be hosted on either the LMS or the CMS Before all the MFEs that were hosted by path were hosted in the LMS domain. For certain MFEs (course-authoring) the CMS domain is more appropriate. * point the MFEs URLs to either the LMS or the CMS * handle special cases in the $LMS_HOST/account route. The `/account/password` endpoint is used in the 'forgot password' flow, for that reason we must forward those requests to the LMS. The `/account/settings` is more specific to the multitenant use case. Some sites may be configured to use the legacy pages but the current configuration would send every subpath of `/account/` to the MFE, we make an exception for the legacy URL. --- README.md | 4 +- setup.py | 2 +- tutormfe_extensions/__about__.py | 2 +- tutormfe_extensions/patches/caddyfile-cms | 16 ++ .../patches/caddyfile-mfe-by-path | 18 +- .../patches/openedx-cms-production-settings | 5 + .../patches/openedx-lms-production-settings | 56 ++++-- tutormfe_extensions/plugin.py | 190 +++--------------- 8 files changed, 108 insertions(+), 185 deletions(-) create mode 100644 tutormfe_extensions/patches/caddyfile-cms create mode 100644 tutormfe_extensions/patches/openedx-cms-production-settings diff --git a/README.md b/README.md index cc4780d..75adf9d 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,12 @@ for MFEs. | Olive | `>=15.0, <16` | `edunext/tutor-mfe@15.0.7.post2` | 15.x.x | | Palm | `>=16.0, <17` | `edunext/tutor-mfe@16.1.3.post1` | 16.x.x | | Quince | `>=17.0, <18` | `openedx/tutor-mfe@v17.0.1` | 17.x.x | +| Redwood | `>=18.0, <19` | `tutor-mfe>17` | 18.x.x | ## Installation ```bash -pip install git+https://github.com/edunext/tutor-contrib-mfe-extensions@v16.0.0 -pip install git+https://github.com/edunext/tutor-mfe@v16.1.3.post1 +pip install git+https://github.com/edunext/tutor-contrib-mfe-extensions@v18.0.0 ``` ## Plugin Configuration diff --git a/setup.py b/setup.py index f52217b..047f596 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ def load_about(): packages=find_packages(exclude=["tests*"]), include_package_data=True, python_requires=">=3.8", - install_requires=["tutor>=17.0.2", "tutor-mfe>=17.0.1"], + install_requires=["tutor>=18.0.0", "tutor-mfe>=18.0.0"], entry_points={ "tutor.plugin.v1": [ "mfe_extensions = tutormfe_extensions.plugin" diff --git a/tutormfe_extensions/__about__.py b/tutormfe_extensions/__about__.py index a08b09c..c6a8b8e 100644 --- a/tutormfe_extensions/__about__.py +++ b/tutormfe_extensions/__about__.py @@ -1 +1 @@ -__version__ = "17.0.0" +__version__ = "18.0.0" diff --git a/tutormfe_extensions/patches/caddyfile-cms b/tutormfe_extensions/patches/caddyfile-cms new file mode 100644 index 0000000..b7c1bb7 --- /dev/null +++ b/tutormfe_extensions/patches/caddyfile-cms @@ -0,0 +1,16 @@ +{%- if MFE_EXTENSIONS_BY_PATH %} +reverse_proxy /api/mfe_config/v1* lms:8000 { + header_up Host {{ LMS_HOST }} +} +{%- for app_name in iter_mfes_per_service("cms") %} +@mfe_{{ app_name }} { + path /{{ app_name }} /{{ app_name }}/* +} +handle @mfe_{{ app_name }} { + redir /{{ app_name }} /{{ app_name }}/ + reverse_proxy mfe:8002 { + header_up Host {host} + } +} +{%- endfor %} +{%- endif %} diff --git a/tutormfe_extensions/patches/caddyfile-mfe-by-path b/tutormfe_extensions/patches/caddyfile-mfe-by-path index 4c403a8..94eb75f 100644 --- a/tutormfe_extensions/patches/caddyfile-mfe-by-path +++ b/tutormfe_extensions/patches/caddyfile-mfe-by-path @@ -1,9 +1,21 @@ {%- if MFE_EXTENSIONS_BY_PATH %} -{% for app_name, app in iter_mfes() %} -route /{{app_name}}/* { +{%- for app_name in iter_mfes_per_service("lms") %} +@mfe_{{ app_name }} { + path /{{ app_name }} /{{ app_name }}/* +} +handle @mfe_{{ app_name }} { + redir /{{ app_name }} /{{ app_name }}/ + {%- if app_name == "account" %} + handle /account/settings { + reverse_proxy lms:8000 + } + handle /account/password { + reverse_proxy lms:8000 + } + {%- endif %} reverse_proxy mfe:8002 { header_up Host {host} } } -{% endfor %} +{%- endfor %} {%- endif %} diff --git a/tutormfe_extensions/patches/openedx-cms-production-settings b/tutormfe_extensions/patches/openedx-cms-production-settings new file mode 100644 index 0000000..463d880 --- /dev/null +++ b/tutormfe_extensions/patches/openedx-cms-production-settings @@ -0,0 +1,5 @@ +{%- if MFE_EXTENSIONS_BY_PATH %} +{%- if get_mfe("course-authoring") %} +COURSE_AUTHORING_MICROFRONTEND_URL = "{% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ CMS_HOST }}/course-authoring" +{%- endif %} +{%- endif %} diff --git a/tutormfe_extensions/patches/openedx-lms-production-settings b/tutormfe_extensions/patches/openedx-lms-production-settings index 1a95374..59cebc5 100644 --- a/tutormfe_extensions/patches/openedx-lms-production-settings +++ b/tutormfe_extensions/patches/openedx-lms-production-settings @@ -1,16 +1,46 @@ {% if MFE_EXTENSIONS_BY_PATH %} LEARNING_MICROFRONTEND_URL = "{% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ LMS_HOST }}/learning" +MFE_CONFIG["LEARNING_BASE_URL"] = "{% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ LMS_HOST }}/learning" + +{%- if get_mfe("authn") %} +AUTHN_MICROFRONTEND_URL = "{% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ LMS_HOST }}/authn" +AUTHN_MICROFRONTEND_DOMAIN = "{{ LMS_HOST }}/authn" +{%- endif %} + +{% if get_mfe("account") %} +ACCOUNT_MICROFRONTEND_URL = "{% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ LMS_HOST }}/account/" +MFE_CONFIG["ACCOUNT_SETTINGS_URL"] = ACCOUNT_MICROFRONTEND_URL +{%- endif %} + +{% if get_mfe("discussions") %} +DISCUSSIONS_MICROFRONTEND_URL = "{% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ LMS_HOST }}/discussions" +MFE_CONFIG["DISCUSSIONS_MFE_BASE_URL"] = DISCUSSIONS_MICROFRONTEND_URL +{% endif %} + +{% if get_mfe("gradebook") %} +WRITABLE_GRADEBOOK_URL = "{% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ LMS_HOST }}/gradebook" +{% endif %} + +{% if get_mfe("learner-dashboard") %} +LEARNER_HOME_MICROFRONTEND_URL = "{% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ LMS_HOST }}/learner-dashboard/" +{% endif %} + +{% if get_mfe("ora-grading") %} +ORA_GRADING_MICROFRONTEND_URL = "{% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ LMS_HOST }}/ora-grading" +{% endif %} + +{% if get_mfe("profile") %} +PROFILE_MICROFRONTEND_URL = "{% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ LMS_HOST }}/profile/u/" +MFE_CONFIG["ACCOUNT_PROFILE_URL"] = "{% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ LMS_HOST }}/profile" +{% endif %} + +{% if get_mfe("communications") %} +COMMUNICATIONS_MICROFRONTEND_URL = "{% if ENABLE_HTTPS %}https{% else %}http{% endif %}://{{ LMS_HOST }}/communications" +{% endif %} + +{% if get_mfe("course-authoring") %} +MFE_CONFIG["COURSE_AUTHORING_MICROFRONTEND_URL"] = "{% if ENABLE_HTTPS %}https://{% else %}http://{% endif %}{{ LMS_HOST }}/course-authoring" +{% endif %} + +CORS_ORIGIN_WHITELIST.append("{% if ENABLE_HTTPS %}https://{% else %}http://{% endif %}{{ CMS_HOST }}") {% endif %} -MFE_CONFIG["PARAGON_THEME_URLS"] = { - "core": { - "url": "https://cdn.jsdelivr.net/combine/npm/@edx/paragon@22.0.0-alpha.13/styles/css/themes/light/utility-classes.min.css,npm/@edx/paragon@22.0.0-alpha.13/dist/core.min.css" - }, - "defaults": { - "light": "light" - }, - "variants": { - "light": { - "url": "https://css-varsify.s3.amazonaws.com/public/a9959998-0bab-4447-ada5-6819866195f3.css" - } - } -} diff --git a/tutormfe_extensions/plugin.py b/tutormfe_extensions/plugin.py index 384e1b0..2c58bd7 100644 --- a/tutormfe_extensions/plugin.py +++ b/tutormfe_extensions/plugin.py @@ -3,12 +3,13 @@ import os import os.path from glob import glob +from typing import Iterable -import click import importlib_resources from tutor import config as tutor_config from tutor import hooks from tutormfe.hooks import MFE_APPS +from tutormfe.plugin import CORE_MFE_APPS from .__about__ import __version__ @@ -23,17 +24,17 @@ def validate_mfe_config(mfe_setting_name: str): if mfe_setting_name.startswith("MFE_") and mfe_setting_name.endswith("_MFE_APP"): return ( - mfe_setting_name - .replace("_MFE_APP", "") + mfe_setting_name.replace("_MFE_APP", "") .replace("MFE_", "") .replace("_", "-") .lower() ) return None + @MFE_APPS.add() def _manage_mfes_from_config(mfe_list): - config = tutor_config.load('.') + config = tutor_config.load(".") for setting, value in config.items(): mfe_name = validate_mfe_config(setting) if not mfe_name: @@ -54,6 +55,7 @@ def _manage_mfes_from_config(mfe_list): return mfe_list + hooks.Filters.CONFIG_DEFAULTS.add_items( [ # Add your new settings that have default values here. @@ -65,104 +67,22 @@ def _manage_mfes_from_config(mfe_list): ] ) -hooks.Filters.CONFIG_UNIQUE.add_items( - [ - # Add settings that don't have a reasonable default for all users here. - # For instance: passwords, secret keys, etc. - # Each new setting is a pair: (setting_name, unique_generated_value). - # Prefix your setting names with 'MFE_EXTENSIONS_'. - # For example: - ### ("MFE_EXTENSIONS_SECRET_KEY", "{{ 24|random_string }}"), - ] -) -hooks.Filters.CONFIG_OVERRIDES.add_items( - [ - # Danger zone! - # Add values to override settings from Tutor core or other plugins here. - # Each override is a pair: (setting_name, new_value). For example: - (k,v) for k,v in CORE_MFES_CONFIG.items() - ] -) +def iter_mfes_per_service(service: str = "") -> Iterable[str]: + """ + Return the list of MFEs that should be hosted via path in the + same domain as each service. -######################################## -# INITIALIZATION TASKS -######################################## + """ + active_mfes = MFE_APPS.apply({}) + cms_mfes = {"course-authoring"} + lms_mfes = set(CORE_MFE_APPS) - cms_mfes -# To add a custom initialization task, create a bash script template under: -# tutormfe_extensions/templates/mfe_extensions/jobs/init/ -# and then add it to the MY_INIT_TASKS list. Each task is in the format: -# ("", ("", "", "