Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions tutormfe/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@
from tutor.core.hooks import Filter

MFE_ATTRS_TYPE = t.Dict[t.Literal["repository", "port", "version"], t.Union["str", int]]
FRONTEND_TEMPLATE_SITE_ATTRS_TYPE = t.Dict

MFE_APPS: Filter[dict[str, MFE_ATTRS_TYPE], []] = Filter()

# TODO: This will hold the list of which apps are "enabled" so we can switch between mfe
# and frontend-base ones
FRONTEND_APPS: Filter[dict[str, t.Dict], []] = Filter()

PLUGIN_SLOTS: Filter[list[tuple[str, str, str]], []] = Filter()
55 changes: 49 additions & 6 deletions tutormfe/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
from glob import glob

import importlib_resources
from tutor import fmt
from tutor import fmt, config as tutor_config
from tutor import hooks as tutor_hooks
from tutor.__about__ import __version_suffix__
from tutor.bindmount import iter_mounts
from tutor.hooks import priorities
from tutor.types import Config, get_typed

from .__about__ import __version__
from .hooks import MFE_APPS, MFE_ATTRS_TYPE, PLUGIN_SLOTS
from .hooks import MFE_APPS, MFE_ATTRS_TYPE, FRONTEND_APPS, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE, PLUGIN_SLOTS

# Handle version suffix in main mode, just like tutor core
if __version_suffix__:
Expand Down Expand Up @@ -76,6 +76,11 @@
"repository": "https://github.com/openedx/frontend-app-profile.git",
"port": 1995,
},
"template-site": {
"repository": "https://github.com/WGU-Open-edX/frontend-template-site.git",
"version": "initial",
"port": 8080,
}
}


Expand All @@ -95,6 +100,14 @@ def get_mfes() -> dict[str, MFE_ATTRS_TYPE]:
return MFE_APPS.apply({})


@tutor_hooks.lru_cache
def get_frontend_apps() -> dict[str, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE]:
"""
This function is cached for performance.
"""
return FRONTEND_APPS.apply({})


class MFEMountData:
"""Stores categorized mounted and unmounted MFEs."""

Expand Down Expand Up @@ -129,6 +142,29 @@ def iter_mfes() -> t.Iterable[tuple[str, MFE_ATTRS_TYPE]]:
"""
yield from get_mfes().items()

# Iter throgh all mfes and adds the unique frontend apps,
# so we can have a list of all the things that are unique that needs
# to be added to Caddyfile, for example instructor dashboard that was
# created as frontend-base app but didn't exist as a MFE before
# so it returns the whole mfes list plus the unique frontend apps that are not in the mfe list
def iter_frontend_apps() -> t.Iterable[tuple[str, FRONTEND_TEMPLATE_SITE_ATTRS_TYPE]]:
"""
Yield:

(name, dict)
"""
mfes = get_mfes()
frontend_apps = get_frontend_apps()

# First yield all MFEs
for name, attrs in mfes.items():
yield (name, attrs)

# Then yield frontend apps that are not already MFEs
for name, attrs in frontend_apps.items():
if name not in mfes:
yield (name, attrs)


def iter_plugin_slots(mfe_name: str) -> t.Iterable[tuple[str, str]]:
"""
Expand All @@ -142,6 +178,9 @@ def iter_plugin_slots(mfe_name: str) -> t.Iterable[tuple[str, str]]:
def is_mfe_enabled(mfe_name: str) -> bool:
return mfe_name in get_mfes()

def is_frontend_app_enabled(app_name: str) -> bool:
return app_name in get_frontend_apps()


def get_mfe(mfe_name: str) -> t.Union[MFE_ATTRS_TYPE, t.Any]:
return get_mfes().get(mfe_name, {})
Expand All @@ -152,8 +191,10 @@ def get_mfe(mfe_name: str) -> t.Union[MFE_ATTRS_TYPE, t.Any]:
[
("get_mfe", get_mfe),
("iter_mfes", iter_mfes),
("iter_frontend_apps", iter_frontend_apps),
("iter_plugin_slots", iter_plugin_slots),
("is_mfe_enabled", is_mfe_enabled),
("is_frontend_app_enabled", is_frontend_app_enabled),
("MFEMountData", MFEMountData),
]
)
Expand Down Expand Up @@ -217,6 +258,8 @@ def _mounted_mfe_image_management() -> None:
tutor_hooks.Filters.CLI_DO_INIT_TASKS.add_item(("lms", task_file.read()))

REPO_PREFIX = "frontend-app-"
# TODO: for now leave this and then find better semantic namings
FRONTEND_TEMPLATE_SITE_PREFIX = "frontend-"


@tutor_hooks.Filters.COMPOSE_MOUNTS.add()
Expand All @@ -229,12 +272,12 @@ def _mount_frontend_apps(
in dev mode, because in production, all MFEs are built and hosted on the
singular 'mfe' service container.
"""
if path_basename.startswith(REPO_PREFIX):
if path_basename.startswith(REPO_PREFIX) or path_basename.startswith(FRONTEND_TEMPLATE_SITE_PREFIX):
# Assumption:
# For each repo named frontend-app-APPNAME, there is an associated
# docker-compose service named APPNAME. If this assumption is broken,
# then Tutor will try to mount the repo in a service that doesn't exist.
app_name = path_basename[len(REPO_PREFIX) :]
app_name = path_basename[len(REPO_PREFIX) :] if path_basename.startswith(REPO_PREFIX) else path_basename[len(FRONTEND_TEMPLATE_SITE_PREFIX) :]
volumes += [(app_name, "/openedx/app")]
return volumes

Expand All @@ -244,9 +287,9 @@ def _mount_frontend_apps_on_build(
mounts: list[tuple[str, str]], host_path: str
) -> list[tuple[str, str]]:
path_basename = os.path.basename(host_path)
if path_basename.startswith(REPO_PREFIX):
if path_basename.startswith(REPO_PREFIX) or path_basename.startswith(FRONTEND_TEMPLATE_SITE_PREFIX):
# Bind-mount repo at build-time, both for prod and dev images
app_name = path_basename[len(REPO_PREFIX) :]
app_name = path_basename[len(REPO_PREFIX) :] if path_basename.startswith(REPO_PREFIX) else path_basename[len(FRONTEND_TEMPLATE_SITE_PREFIX) :]
mounts.append(("mfe", f"{app_name}-src"))
mounts.append((f"{app_name}-dev", f"{app_name}-src"))
return mounts
Expand Down
9 changes: 8 additions & 1 deletion tutormfe/templates/mfe/apps/mfe/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,22 @@
redir @authoring /authoring/{re.authoring.1} permanent
{% endif %}

{% for app_name, app in iter_mfes() %}
{% for app_name, app in iter_frontend_apps() %}
@mfe_{{ app_name }} {
path /{{ app_name }} /{{ app_name }}/*
}
handle @mfe_{{ app_name }} {
uri strip_prefix /{{ app_name }}
{%- if is_frontend_app_enabled(app_name) %}
# {{ app_name }} - using frontend-apps approach
root * /openedx/dist/template-site
{%- else %}
# {{ app_name }} - using traditional MFE approach
root * /openedx/dist/{{ app_name }}
{%- endif %}
try_files /{path} /index.html
file_server
}

{% endfor %}
}
3 changes: 2 additions & 1 deletion tutormfe/templates/mfe/build/mfe/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ RUN --mount=type=cache,target=/root/.npm,sharing=shared npm clean-install --no-a
{{ patch("mfe-dockerfile-post-npm-install-{}".format(app_name)) }}
COPY --from={{ app_name }}-src / /openedx/app

{% if app_name != "template-site" %}
RUN make OPENEDX_ATLAS_PULL=true ATLAS_OPTIONS="--repository={{ ATLAS_REPOSITORY }} --revision={{ ATLAS_REVISION }} {{ ATLAS_OPTIONS }}" pull_translations

{% endif %}
EXPOSE {{ app['port'] }}

# Configuration needed at build time
Expand Down