mirror of
https://github.com/ChristianLight/tutor.git
synced 2024-12-04 19:03:39 +00:00
feat: upgrade to open-release/lilac.master
One of the breaking changes of this release is the removal of the webui and android features; these are moved to dedicated plugins. This causes a breaking change: the renaming of the DOCKER_IMAGE_ANDROID config variable to ANDROID_DOCKER_IMAGE. See this TEP for reference: https://discuss.overhang.io/t/separate-webui-and-android-from-tutor-core-and-move-to-dedicated-plugins/1473
This commit is contained in:
parent
915551268c
commit
ceddc11c29
@ -4,6 +4,12 @@ Note: Breaking changes between versions are indicated by "💥".
|
||||
|
||||
## Unreleased
|
||||
|
||||
## v12.0.0 (2021-06-09)
|
||||
|
||||
- 💥[Improvement] Upgrade all services to open-release/lilac.master.
|
||||
- 💥[Feature] Migrate Android app building and the WebUI frontend away from core Tutor and to dedicated plugins (see [TEP](https://discuss.overhang.io/c/community/tep/9)). The `DOCKER_IMAGE_ANDROID` setting is thus renamed to `ANDROID_DOCKER_IMAGE`.
|
||||
- [Feature] Run `docker-compose build` as part of `tutor local start`.
|
||||
|
||||
## v11.3.1 (2021-06-08)
|
||||
|
||||
- [Improvement] Avoid permission issues in Kubernetes/Openshift for users who do not have the rights to edit their namespace.
|
||||
|
3
Makefile
3
Makefile
@ -90,8 +90,7 @@ ci-test-bundle: ## Run basic tests on bundle
|
||||
yes "" | ./dist/tutor config save --interactive
|
||||
./dist/tutor config save
|
||||
./dist/tutor plugins list
|
||||
# ./dist/tutor plugins enable discovery ecommerce figures license minio notes xqueue
|
||||
./dist/tutor plugins enable discovery ecommerce license minio notes xqueue
|
||||
./dist/tutor plugins enable android discovery ecommerce license mfe minio notes webui xqueue
|
||||
./dist/tutor plugins list
|
||||
./dist/tutor license --help
|
||||
|
||||
|
11
README.rst
11
README.rst
@ -32,7 +32,7 @@ Tutor: the docker-based Open edX distribution designed for peace of mind
|
||||
:alt: AGPL License
|
||||
:target: https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
**Tutor** is a docker-based `Open edX <https://openedx.org>`_ distribution, both for production and local development. The goal of Tutor is to make it easy to deploy, customize, upgrade and scale Open edX. Tutor is reliable, fast, extensible, and it is already used by dozens of Open edX platforms around the world.
|
||||
**Tutor** is a docker-based `Open edX <https://openedx.org>`_ distribution, both for production and local development. The goal of Tutor is to make it easy to deploy, customize, upgrade and scale Open edX. Tutor is reliable, fast, extensible, and it is already used by hundreds of Open edX platforms around the world.
|
||||
|
||||
Do you need professional assistance setting up or managing your Open edX platform? Overhang.IO provides online support as part of its `Long Term Support (LTS) offering <https://overhang.io/tutor/pricing>`__.
|
||||
|
||||
@ -42,11 +42,11 @@ Features
|
||||
* 100% `open source <https://github.com/overhangio/tutor>`__
|
||||
* Runs entirely on Docker
|
||||
* World-famous 1-click `installation and upgrades <https://docs.tutor.overhang.io/install.html>`__
|
||||
* Comes with batteries included: `theming <https://github.com/overhangio/indigo>`__, `SCORM <https://github.com/overhangio/openedx-scorm-xblock>`__, `HTTPS <https://docs.tutor.overhang.io/configuration.html#ssl-tls-certificates-for-https-access>`__, `web-based administration interface <https://docs.tutor.overhang.io/extra.html#web-ui>`__, `mobile app <https://docs.tutor.overhang.io/extra.html#mobile-android-application>`__, `custom translations <https://docs.tutor.overhang.io/configuration.html#adding-custom-translations>`__...
|
||||
* Comes with batteries included: `theming <https://github.com/overhangio/indigo>`__, `SCORM <https://github.com/overhangio/openedx-scorm-xblock>`__, `HTTPS <https://docs.tutor.overhang.io/configuration.html#ssl-tls-certificates-for-https-access>`__, `web-based administration interface <https://github.com/overhangio/tutor-webui>`__, `mobile app <https://github.com/tutor/tutor-android>`__, `custom translations <https://docs.tutor.overhang.io/configuration.html#adding-custom-translations>`__...
|
||||
* Extensible architecture with `plugins <https://docs.tutor.overhang.io/plugins.html>`__
|
||||
* Works with `Kubernetes <https://docs.tutor.overhang.io/k8s.html>`__
|
||||
* No technical skill required with the `1-click Tutor AWS image <https://docs.tutor.overhang.io/install.html#cloud-deployment>`__
|
||||
* Amazing plugins available with `Tutor Wizard Edition <https://overhang.io/tutor>`__
|
||||
* Amazing premium plugins available in the `Tutor Wizard Edition <https://overhang.io/tutor/wizardedition>`__
|
||||
|
||||
.. _readme_intro_end:
|
||||
|
||||
@ -66,6 +66,11 @@ Documentation
|
||||
|
||||
Extensive documentation is available online: https://docs.tutor.overhang.io/
|
||||
|
||||
Is there a problem?
|
||||
-------------------
|
||||
|
||||
Please follow the instructions from the `troubleshooting section <https://docs.tutor.overhang.io/troubleshooting.html>`__ in the docs.
|
||||
|
||||
.. _readme_support_start:
|
||||
|
||||
Support
|
||||
|
@ -3,12 +3,14 @@ from tutor.plugins import OfficialPlugin
|
||||
|
||||
# Manually install plugins (this is for creating the bundle)
|
||||
for plugin_name in [
|
||||
"android",
|
||||
"discovery",
|
||||
"ecommerce",
|
||||
# "figures",
|
||||
"license",
|
||||
"mfe",
|
||||
"minio",
|
||||
"notes",
|
||||
"webui",
|
||||
"xqueue",
|
||||
]:
|
||||
try:
|
||||
|
@ -61,7 +61,6 @@ Custom images
|
||||
*************
|
||||
|
||||
- ``DOCKER_IMAGE_OPENEDX`` (default: ``"{{ DOCKER_REGISTRY }}overhangio/openedx:{{ TUTOR_VERSION }}"``)
|
||||
- ``DOCKER_IMAGE_ANDROID`` (default: ``"{{ DOCKER_REGISTRY }}overhangio/openedx-android:{{ TUTOR_VERSION }}"``)
|
||||
- ``DOCKER_IMAGE_FORUM`` (default: ``"{{ DOCKER_REGISTRY }}overhangio/openedx-forum:{{ TUTOR_VERSION }}"``)
|
||||
|
||||
These configuration parameters define which image to run for each service. By default, the docker image tag matches the Tutor version it was built with.
|
||||
@ -80,7 +79,7 @@ You may want to pull/push images from/to a custom docker registry. For instance,
|
||||
Open edX customisation
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- ``OPENEDX_COMMON_VERSION`` (default: ``"open-release/koa.2"``)
|
||||
- ``OPENEDX_COMMON_VERSION`` (default: ``"open-release/lilac.1"``)
|
||||
|
||||
This defines the default version that will be pulled from all Open edX git repositories.
|
||||
|
||||
@ -214,8 +213,7 @@ openedx Docker Image build arguments
|
||||
When building the "openedx" Docker image, it is possible to specify a few `arguments <https://docs.docker.com/engine/reference/builder/#arg>`__:
|
||||
|
||||
- ``EDX_PLATFORM_REPOSITORY`` (default: ``"https://github.com/edx/edx-platform.git"``)
|
||||
- ``EDX_PLATFORM_VERSION`` (default: ``"open-release/koa.3"``)
|
||||
- ``EDX_PLATFORM_VERSION_DATE`` (default: ``"20200227"``)
|
||||
- ``EDX_PLATFORM_VERSION`` (default: ``"open-release/lilac.1"``)
|
||||
- ``NPM_REGISTRY`` (default: ``"https://registry.npmjs.org/"``)
|
||||
|
||||
These arguments can be specified from the command line, `very much like Docker <https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg>`__. For instance::
|
||||
@ -286,16 +284,16 @@ Note that your edx-platform version must be a fork of the latest release **tag**
|
||||
|
||||
If you don't create your fork from this tag, you *will* have important compatibility issues with other services. In particular:
|
||||
|
||||
- Do not try to run a fork from an older (pre-Koa) version of edx-platform: this will simply not work.
|
||||
- Do not try to run a fork from an older (pre-Lilac) version of edx-platform: this will simply not work.
|
||||
- Do not try to run a fork from the edx-platform master branch: there is a 99% probability that it will fail.
|
||||
- Do not try to run a fork from the open-release/koa.master branch: Tutor will attempt to apply security and bug fix patches that might already be included in the open-release/koa.master but which were not yet applied to the latest release tag. Patch application will thus fail if you base your fork from the open-release/koa.master branch.
|
||||
- Do not try to run a fork from the open-release/lilac.master branch: Tutor will attempt to apply security and bug fix patches that might already be included in the open-release/lilac.master but which were not yet applied to the latest release tag. Patch application will thus fail if you base your fork from the open-release/lilac.master branch.
|
||||
|
||||
.. _i18n:
|
||||
|
||||
Adding custom translations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you are not running Open edX in English, chances are that some strings will not be properly translated. In most cases, this is because not enough contributors have helped translate Open edX in your language. It happens! With Tutor, available translated languages include those that come bundled with `edx-platform <https://github.com/edx/edx-platform/tree/open-release/koa.master/conf/locale>`__ as well as those from `openedx-i18n <https://github.com/openedx/openedx-i18n/tree/master/edx-platform/locale>`__.
|
||||
If you are not running Open edX in English, chances are that some strings will not be properly translated. In most cases, this is because not enough contributors have helped translate Open edX in your language. It happens! With Tutor, available translated languages include those that come bundled with `edx-platform <https://github.com/edx/edx-platform/tree/open-release/lilac.master/conf/locale>`__ as well as those from `openedx-i18n <https://github.com/openedx/openedx-i18n/tree/master/edx-platform/locale>`__.
|
||||
|
||||
Tutor offers a relatively simple mechanism to add custom translations to the openedx Docker image. You should create a folder that corresponds to your language code in the "build/openedx/locale" folder of the Tutor environment. This folder should contain a "LC_MESSAGES" folder. For instance::
|
||||
|
||||
@ -316,9 +314,9 @@ Then, add a "django.po" file there that will contain your custom translations::
|
||||
.. warning::
|
||||
Don't forget to specify the file ``Content-Type`` when adding message strings with non-ASCII characters; otherwise a ``UnicodeDecodeError`` will be raised during compilation.
|
||||
|
||||
The "String to translate" part should match *exactly* the string that you would like to translate. You cannot make it up! The best way to find this string is to copy-paste it from the `upstream django.po file for the English language <https://github.com/edx/edx-platform/blob/open-release/koa.master/conf/locale/en/LC_MESSAGES/django.po>`__.
|
||||
The "String to translate" part should match *exactly* the string that you would like to translate. You cannot make it up! The best way to find this string is to copy-paste it from the `upstream django.po file for the English language <https://github.com/edx/edx-platform/blob/open-release/lilac.master/conf/locale/en/LC_MESSAGES/django.po>`__.
|
||||
|
||||
If you cannot find the string to translate in this file, then it means that you are trying to translate a string that is used in some piece of javascript code. Those strings are stored in a different file named "djangojs.po". You can check it out `in the edx-platform repo as well <https://github.com/edx/edx-platform/blob/open-release/koa.master/conf/locale/en/LC_MESSAGES/djangojs.po>`__. Your custom javascript strings should also be stored in a "djangojs.po" file that should be placed in the same directory.
|
||||
If you cannot find the string to translate in this file, then it means that you are trying to translate a string that is used in some piece of javascript code. Those strings are stored in a different file named "djangojs.po". You can check it out `in the edx-platform repo as well <https://github.com/edx/edx-platform/blob/open-release/lilac.master/conf/locale/en/LC_MESSAGES/djangojs.po>`__. Your custom javascript strings should also be stored in a "djangojs.po" file that should be placed in the same directory.
|
||||
|
||||
To recap, here is an example. To translate a few strings in French, both from django.po and djangojs.po, we would have the following file hierarchy::
|
||||
|
||||
|
@ -25,7 +25,7 @@ This ``openedx-dev`` development image differs from the ``openedx`` production i
|
||||
|
||||
- The user that runs inside the container has the same UID as the user on the host, in order to avoid permission problems inside mounted volumes (and in particular in the edx-platform repository).
|
||||
- Additional python and system requirements are installed for convenient debugging: `ipython <https://ipython.org/>`__, `ipdb <https://pypi.org/project/ipdb/>`__, vim, telnet.
|
||||
- The edx-platform `development requirements <https://github.com/edx/edx-platform/blob/open-release/koa.master/requirements/edx/development.in>`__ are installed.
|
||||
- The edx-platform `development requirements <https://github.com/edx/edx-platform/blob/open-release/lilac.master/requirements/edx/development.in>`__ are installed.
|
||||
|
||||
Since the ``openedx-dev`` is based upon the ``openedx`` docker image, it should be re-built every time the ``openedx`` docker image is modified.
|
||||
|
||||
@ -137,7 +137,7 @@ Prepare the edx-platform repo
|
||||
|
||||
If you choose any but the first solution above, you will have to make sure that your fork works with Tutor.
|
||||
|
||||
First of all, you should make sure that you are working off the ``open-release/koa.3`` tag. See the :ref:`fork edx-platform section <edx_platform_fork>` for more information.
|
||||
First of all, you should make sure that you are working off the ``open-release/lilac.1`` tag. See the :ref:`fork edx-platform section <edx_platform_fork>` for more information.
|
||||
|
||||
Then, you should run the following commands::
|
||||
|
||||
|
@ -1,76 +0,0 @@
|
||||
.. _extra:
|
||||
|
||||
Extra features
|
||||
==============
|
||||
|
||||
.. _webui:
|
||||
|
||||
Web UI
|
||||
------
|
||||
|
||||
Tutor comes with a web user interface (UI) that allows you to administer your Open edX platform remotely. It's especially convenient for remote administration of the platform.
|
||||
|
||||
Launching the web UI
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
::
|
||||
|
||||
tutor webui start
|
||||
|
||||
You can then access the interface at http://localhost:3737, or http://youserverurl:3737.
|
||||
|
||||
.. image:: img/webui.png
|
||||
|
||||
All ``tutor`` commands can be executed from this web UI: you just don't need to prefix the commands with ``tutor``. For instance, to deploy a local Open edX instance, run::
|
||||
|
||||
local quickstart
|
||||
|
||||
instead of ``tutor local quickstart``.
|
||||
|
||||
Authentication
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
**WARNING** Once you launch the web UI, it is accessible by everyone, which means that your Open edX platform is at risk. If you are planning to leave the web UI up for a long time, you should setup a user and password for authentication::
|
||||
|
||||
tutor webui configure
|
||||
|
||||
.. _mobile:
|
||||
|
||||
Mobile Android application
|
||||
--------------------------
|
||||
|
||||
With Tutor, you can build an Android mobile application for your platform. To build the application in debug mode, run::
|
||||
|
||||
tutor android build debug
|
||||
|
||||
The ``.apk`` file will then be available in ``$(tutor config printroot)/data/android``. Transfer it to an Android phone to install the application. You should be able to sign in and view available courses.
|
||||
|
||||
Building a custom Android app
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The Android app is built from the `official edx-app-android repository <https://github.com/edx/edx-app-android/>`__. To change this repository or the app version, you can simply build a different docker image with::
|
||||
|
||||
tutor images build \
|
||||
--build-arg ANDROID_APP_REPOSITORY=https://github.com/mycustomfork/edx-app-android \
|
||||
--build-arg ANDROID_APP_VERSION=master \
|
||||
android
|
||||
|
||||
Releasing an Android app
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Note**: this is an untested feature.
|
||||
|
||||
Releasing an Android app on the Play Store requires to build the app in release mode. To do so, edit the ``$TUTOR_ROOT/config.yml`` configuration file and define the following variables::
|
||||
|
||||
ANDROID_RELEASE_STORE_PASSWORD
|
||||
ANDROID_RELEASE_KEY_PASSWORD
|
||||
ANDROID_RELEASE_KEY_ALIAS
|
||||
|
||||
Then, place your keystore file in ``$(tutor config printroot)/env/android/app.keystore``. Finally, build the application with::
|
||||
|
||||
tutor android build release
|
||||
|
||||
Customising the Android app
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Customising the application, such as the logo or the background image, is currently not supported. If you are interested by this feature, please tell us about it in the Tutor `discussion forums <https://discuss.overhang.io>`_.
|
@ -28,7 +28,7 @@ The `native installation <https://openedx.atlassian.net/wiki/spaces/OpenOPS/page
|
||||
4. Security: because you are no longer bound to a single OS, with Tutor you are now free to install security-related upgrades as soon as they become available.
|
||||
5. Portability: Tutor makes it easy to move your platform from one server to another. Just zip-compress your Tutor project root, send it to another server and you're done.
|
||||
|
||||
There are also many features that are not included in the native installation, such as a :ref:`web user interface <webui>` for remotely installing the platform, :ref:`Kubernetes deployment <k8s>`, additional languages, etc. You'll discover these differences as you explore Tutor :)
|
||||
There are also many features that are not included in the native installation, such as a `web user interface <https://github.com/overhangio/tutor-webui>`__ for remotely installing the platform, :ref:`Kubernetes deployment <k8s>`, additional languages, etc. You'll discover these differences as you explore Tutor :)
|
||||
|
||||
What's the difference with the official devstack?
|
||||
-------------------------------------------------
|
||||
@ -45,12 +45,12 @@ What features are missing from Tutor?
|
||||
|
||||
Tutor tries very hard to support all major Open edX features, notably in the form of :ref:`plugins <existing_plugins>`. In particular, the discovery and ecommerce services, once unavailable in Tutor, can now be easily installed via plugins. If you are interested in sponsoring the development of a new plugin, please `get in touch <mailto:worktogether@overhang.io>`__!
|
||||
|
||||
It should be noted that the `Analytics <https://github.com/edx/edx-analytics-pipeline>`__ stack is currently unsupported, and will likely stay so in the future, as it would require a tremendous amount of work to containerize all the components. For generating great-looking analytics reports, we recommend the `Figures plugin <https://github.com/overhangio/tutor-figures>`__.
|
||||
It should be noted that the `Analytics <https://github.com/edx/edx-analytics-pipeline>`__ stack is currently unsupported, and will likely stay so in the future, as it would require a tremendous amount of work to containerize all the components. We are currently working on a replacement solution.
|
||||
|
||||
Are there people already running this in production?
|
||||
----------------------------------------------------
|
||||
|
||||
Yes, many of them. There is no way to count precisely how many running Open edX platforms were deployed with Tutor, but from feedback collected directly from real users, there must be dozens, if not hundreds. Tutor is also used by some Open edX providers who are hosting platforms for their customers.
|
||||
Yes, many of them. There is no way to count precisely how many running Open edX platforms were deployed with Tutor, but from feedback collected directly from real users, there must be hundreds, if not thousands. Tutor is also used by some Open edX providers who are hosting platforms for their customers.
|
||||
|
||||
Why should I trust software written by some random guy on the Internet?
|
||||
-----------------------------------------------------------------------
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 200 KiB |
@ -23,7 +23,6 @@
|
||||
run
|
||||
configuration
|
||||
plugins
|
||||
extra
|
||||
troubleshooting
|
||||
tutor
|
||||
faq
|
||||
@ -50,6 +49,4 @@ The AGPL license covers the Tutor code, including the Dockerfiles, but not the c
|
||||
|
||||
The Tutor plugin system is licensed under the terms of the `Apache License, Version 2.0 <https://opensource.org/licenses/Apache-2.0>`__.
|
||||
|
||||
The :ref:`Tutor Web UI <webui>` depends on the `Gotty <https://github.com/yudai/gotty/>`_ binary, which is provided under the terms of the `MIT license <https://github.com/yudai/gotty/blob/master/LICENSE>`_.
|
||||
|
||||
© 2021 Tutor is a registered trademark of SASU NULI NULI. All Rights Reserved.
|
||||
|
@ -88,9 +88,9 @@ 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``.
|
||||
|
||||
``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 Juniper to Koa, run::
|
||||
``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 Koa to Lilac, run::
|
||||
|
||||
tutor local upgrade --from=juniper
|
||||
tutor local upgrade --from=koa
|
||||
|
||||
.. _autocomplete:
|
||||
|
||||
|
@ -66,7 +66,6 @@ The Android mobile application for this website can be downloaded at this url: h
|
||||
Urls:
|
||||
|
||||
* LMS: https://demo.openedx.overhang.io
|
||||
* Analytics (from the `Figures plugin <https://pypi.org/project/tutor-figures/>`__): https://demo.openedx.overhang.io/figures
|
||||
* Studio (CMS): https://studio.demo.openedx.overhang.io
|
||||
|
||||
The platform is reset every day at 9:00 AM, `Paris (France) time <https://time.is/Paris>`__, so feel free to try and break things as much as you want.
|
||||
@ -86,12 +85,12 @@ This command does two things:
|
||||
All these files are stored in a single folder, called the Tutor project root. On Linux, this folder is in ``~/.local/share/tutor``. On Mac OS it is ``~/Library/Application Support/tutor``.
|
||||
|
||||
The values from ``config.yml`` are used to generate the environment files in ``env/``. As a consequence, **every time the values from** ``config.yml`` **are modified, the environment must be regenerated**. This can be done with::
|
||||
|
||||
|
||||
tutor config save
|
||||
|
||||
|
||||
Another consequence is that **any manual change made to a file in** ``env/`` **will be overwritten by** ``tutor config save`` **commands**. Consider yourself warned!
|
||||
|
||||
I'm ready, where do I start?
|
||||
----------------------------
|
||||
|
||||
Right :ref:`here <gettingstarted>`!
|
||||
Right :ref:`here <gettingstarted>`!
|
||||
|
@ -20,36 +20,31 @@ Commands
|
||||
--------
|
||||
|
||||
List installed plugins::
|
||||
|
||||
|
||||
tutor plugins list
|
||||
|
||||
|
||||
Enable/disable a plugin::
|
||||
|
||||
|
||||
tutor plugins enable myplugin
|
||||
tutor plugins disable myplugin
|
||||
|
||||
|
||||
After enabling or disabling a plugin, the environment should be re-generated with::
|
||||
|
||||
|
||||
tutor config save
|
||||
|
||||
|
||||
.. _existing_plugins:
|
||||
|
||||
Existing plugins
|
||||
----------------
|
||||
|
||||
- `Course discovery <https://pypi.org/project/tutor-discovery>`__: Deploy an API for interacting with your course catalog
|
||||
- `Ecommerce <https://pypi.org/project/tutor-ecommerce>`__: Sell courses and products on your Open edX platform
|
||||
- `Figures <https://pypi.org/project/tutor-figures>`__: Visualize daily stats about course engagement
|
||||
- `MinIO <https://pypi.org/project/tutor-minio>`__: S3 emulator for object storage and scalable Open edX deployment.
|
||||
- `Notes <https://pypi.org/project/tutor-notes>`__: Allows students to annotate portions of the courseware.
|
||||
- `Xqueue <https://pypi.org/project/tutor-xqueue>`__: for external grading
|
||||
Officially-supported plugins are listed on the `Overhang.IO <https://overhang.io/tutor/plugins>`__ website.
|
||||
|
||||
Plugin development
|
||||
------------------
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
plugins/api
|
||||
plugins/gettingstarted
|
||||
plugins/examples
|
||||
plugins/examples
|
||||
|
@ -45,13 +45,13 @@ Plugin patches affect the rendered environment templates. In many places the Tut
|
||||
|
||||
.. note::
|
||||
The list of existing patches can be found by searching for `{{ patch(` strings in the Tutor source code::
|
||||
|
||||
|
||||
git grep "{{ patch"
|
||||
|
||||
|
||||
The list of patches can also be browsed online `on Github <https://github.com/search?utf8=✓&q={{+patch+repo%3Aoverhangio%2Ftutor+path%3A%2Ftutor%2Ftemplates&type=Code&ref=advsearch&l=&l= 8>`__.
|
||||
|
||||
|
||||
Example::
|
||||
|
||||
|
||||
patches = {
|
||||
"local-docker-compose-services": """redis:
|
||||
image: redis:latest"""
|
||||
@ -76,11 +76,11 @@ Hooks are actions that are run during the lifetime of the platform. For instance
|
||||
The services that will be run during initialisation should be added to the ``init`` hook, for instance for database creation and migrations.
|
||||
|
||||
Example::
|
||||
|
||||
|
||||
hooks = {
|
||||
"init": ["myservice1", "myservice2"]
|
||||
}
|
||||
|
||||
|
||||
During initialisation, "myservice1" and "myservice2" will be run in sequence with the commands defined in the templates ``myplugin/hooks/myservice1/init`` and ``myplugin/hooks/myservice2/init``.
|
||||
|
||||
To initialise a "foo" service, Tutor runs the "foo-job" service that is found in the ``env/local/docker-compose.jobs.yml`` file. By default, Tutor comes with a few services in this file: mysql-job, lms-job, cms-job, forum-job. If your plugin requires running custom services during initialisation, you will need to add them to the ``docker-compose.jobs.yml`` template. To do so, just use the "local-docker-compose-jobs-services" patch.
|
||||
@ -102,13 +102,13 @@ Example::
|
||||
hooks = {
|
||||
"build-image": {"myimage": "myimage:latest"}
|
||||
}
|
||||
|
||||
|
||||
With this hook, users will be able to build the ``myimage:latest`` docker image by running::
|
||||
|
||||
|
||||
tutor images build myimage
|
||||
|
||||
or::
|
||||
|
||||
|
||||
tutor images build all
|
||||
|
||||
This assumes that there is a ``Dockerfile`` file in the ``myplugin/build/myimage`` subfolder of the plugin templates directory.
|
||||
@ -119,18 +119,18 @@ This assumes that there is a ``Dockerfile`` file in the ``myplugin/build/myimage
|
||||
This hook allows pulling/pushing images from/to a docker registry.
|
||||
|
||||
Example::
|
||||
|
||||
|
||||
hooks = {
|
||||
"remote-image": {"myimage": "myimage:latest"},
|
||||
}
|
||||
|
||||
With this hook, users will be able to pull and push the ``myimage:latest`` docker image by running::
|
||||
|
||||
|
||||
tutor images pull myimage
|
||||
tutor images push myimage
|
||||
|
||||
or::
|
||||
|
||||
|
||||
tutor images pull all
|
||||
tutor images push all
|
||||
|
||||
@ -142,20 +142,24 @@ templates
|
||||
In order to define plugin-specific hooks, a plugin should also have a template directory that includes the plugin hooks. The ``templates`` attribute should point to that directory.
|
||||
|
||||
Example::
|
||||
|
||||
|
||||
import os
|
||||
templates = os.path.join(os.path.abspath(os.path.dirname(__file__)), "templates")
|
||||
|
||||
With the above declaration, you can store plugin-specific templates in the ``templates/myplugin`` folder next to the ``plugin.py`` file.
|
||||
|
||||
In Tutor, templates are `Jinja2 <https://jinja.palletsprojects.com/en/2.11.x/>`__-formatted files that will be rendered in the Tutor environment (the ``$(tutor config printroot)/env`` folder) when running ``tutor config save``. The environment files are overwritten every time the environment is saved. Plugin developers can create templates that make use of the built-in `Jinja2 API <https://jinja.palletsprojects.com/en/2.11.x/api/>`__. In addition, a couple additional filters are added by Tutor:
|
||||
|
||||
|
||||
* ``common_domain``: Return the longest common name between two domain names. Example: ``{{ "studio.demo.myopenedx.com"|common_domain("lms.demo.myopenedx.com") }}`` is equal to "demo.myopenedx.com".
|
||||
* ``encrypt``: Encrypt an arbitrary string. The encryption process is compatible with `htpasswd <https://httpd.apache.org/docs/2.4/programs/htpasswd.html>`__ verification.
|
||||
* ``list_if``: In a list of ``(value, condition)`` tuples, return the list of ``value`` for which the ``condition`` is true.
|
||||
* ``long_to_base64``: Base-64 encode a long integer.
|
||||
* ``iter_values_named``: Yield the values of the configuration settings that match a certain pattern. Example: ``{% for value in iter_values_named(prefix="KEY", suffix="SUFFIX")%}...{% endfor %}``. By default, only non-empty values are yielded. To iterate also on empty values, pass the ``allow_empty=True`` argument.
|
||||
* ``patch``: See :ref:`patches <plugin_patches>`.
|
||||
* ``random_string``: Return a random string of the given length composed of ASCII letters and digits. Example: ``{{ 8|random_string }}``.
|
||||
* ``reverse_host``: Reverse a domain name (see `reference <https://en.wikipedia.org/wiki/Reverse_domain_name_notation>`__). Example: ``{{ "demo.myopenedx.com"|reverse_host }}`` is equal to "com.myopenedx.demo".
|
||||
* ``rsa_import_key``: Import a PEM-formatted RSA key and return the corresponding object.
|
||||
* ``rsa_private_key``: Export an RSA private key in PEM format.
|
||||
* ``walk_templates``: Iterate recursively over the templates of the given folder. For instance::
|
||||
|
||||
{% for file in "apps/myplugin"|walk_templates %}
|
||||
@ -175,26 +179,26 @@ command
|
||||
A plugin can provide custom command line commands. Commands are assumed to be `click.Command <https://click.palletsprojects.com/en/7.x/api/#commands>`__ objects.
|
||||
|
||||
Example::
|
||||
|
||||
|
||||
import click
|
||||
|
||||
|
||||
@click.command(help="I'm a plugin command")
|
||||
def command():
|
||||
click.echo("Hello from myplugin!")
|
||||
|
||||
Any user who installs the ``myplugin`` plugin can then run::
|
||||
|
||||
|
||||
$ tutor myplugin
|
||||
Hello from myplugin!
|
||||
|
||||
You can even define subcommands by creating `command groups <https://click.palletsprojects.com/en/7.x/api/#click.Group>`__::
|
||||
|
||||
|
||||
import click
|
||||
|
||||
|
||||
@click.group(help="I'm a plugin command group")
|
||||
def command():
|
||||
pass
|
||||
|
||||
|
||||
@click.command(help="I'm a plugin subcommand")
|
||||
def dosomething():
|
||||
click.echo("This subcommand is awesome")
|
||||
@ -203,5 +207,5 @@ This would allow any user to run::
|
||||
|
||||
$ tutor myplugin dosomething
|
||||
This subcommand is awesome
|
||||
|
||||
|
||||
See the official `click documentation <https://click.palletsprojects.com/en/7.x/>`__ for more information.
|
||||
|
@ -9,7 +9,7 @@ YAML file
|
||||
~~~~~~~~~
|
||||
|
||||
YAML files that are stored in the tutor plugins root folder will be automatically considered as plugins. The location of the plugin root can be found by running::
|
||||
|
||||
|
||||
tutor plugins printroot
|
||||
|
||||
On Linux, this points to ``~/.local/share/tutor-plugins``. The location of the plugin root folder can be modified by setting the ``TUTOR_PLUGINS_ROOT`` environment variable.
|
||||
@ -17,7 +17,7 @@ On Linux, this points to ``~/.local/share/tutor-plugins``. The location of the p
|
||||
YAML plugins need to define two extra keys: "name" and "version". Custom CLI commands are not supported by YAML plugins.
|
||||
|
||||
Let's create a simple plugin that adds your own `Google Analytics <https://analytics.google.com/>`__ tracking code to your Open edX platform. We need to add the ``GOOGLE_ANALYTICS_ACCOUNT`` and ``GOOGLE_ANALYTICS_TRACKING_ID`` settings to both the LMS and the CMS settings. To do so, we will only have to create the ``openedx-common-settings`` patch, which is shared by the development and the production settings both for the LMS and the CMS. First, create the plugin directory::
|
||||
|
||||
|
||||
mkdir "$(tutor plugins printroot)"
|
||||
|
||||
Then add the following content to the plugin file located at ``$(tutor plugins printroot)/myplugin.yml``::
|
||||
@ -31,16 +31,16 @@ Then add the following content to the plugin file located at ``$(tutor plugins p
|
||||
GOOGLE_ANALYTICS_TRACKING_ID = "UA-654321-1"
|
||||
|
||||
Of course, you should replace your Google Analytics tracking code with your own. You can verify that your plugin is correctly installed, but not enabled yet::
|
||||
|
||||
|
||||
$ tutor plugins list
|
||||
googleanalytics@0.1.0 (disabled)
|
||||
|
||||
|
||||
You can then enable your newly-created plugin::
|
||||
|
||||
|
||||
tutor plugins enable googleanalytics
|
||||
|
||||
Update your environment to apply changes from your plugin::
|
||||
|
||||
|
||||
tutor config save
|
||||
|
||||
You should be able to view your changes in every LMS and CMS settings file::
|
||||
@ -48,11 +48,11 @@ You should be able to view your changes in every LMS and CMS settings file::
|
||||
grep -r googleanalytics "$(tutor config printroot)/env/apps/openedx/settings/"
|
||||
|
||||
Now just restart your platform to start sending tracking events to Google Analytics::
|
||||
|
||||
|
||||
tutor local quickstart
|
||||
|
||||
That's it! And it's very easy to share your plugins. Just upload them to your Github repo and share the url with other users. They will be able to install your plugin by running::
|
||||
|
||||
|
||||
tutor plugins install https://raw.githubusercontent.com/username/yourrepo/master/googleanalytics.yml
|
||||
|
||||
Python package
|
||||
@ -61,7 +61,7 @@ Python package
|
||||
Creating a plugin as a Python package allows you to define more complex logic and to store your patches in a more structured way. Python Tutor plugins are regular Python packages that define a specific entrypoint: ``tutor.plugin.v0``.
|
||||
|
||||
Example::
|
||||
|
||||
|
||||
from setuptools import setup
|
||||
setup(
|
||||
...
|
||||
|
@ -18,7 +18,7 @@ Yes :) This is what happens when you run ``tutor local quickstart``:
|
||||
2. Configuration files are generated from templates.
|
||||
3. Docker images are downloaded.
|
||||
4. Docker containers are provisioned.
|
||||
5. A full, production-ready Open edX platform (`Koa <https://edx.readthedocs.io/projects/edx-installing-configuring-and-running/en/open-release-koa.master/platform_releases/koa.html>`__ release) is run with docker-compose.
|
||||
5. A full, production-ready Open edX platform (`Lilac <https://edx.readthedocs.io/projects/edx-installing-configuring-and-running/en/open-release-lilac.master/platform_releases/koa.html>`__ release) is run with docker-compose.
|
||||
|
||||
The whole procedure should require less than 10 minutes, on a server with a good bandwidth. Note that your host environment will not be affected in any way, since everything runs inside docker containers. Root access is not even necessary.
|
||||
|
||||
|
@ -13,7 +13,7 @@ What should you do if you have a problem?
|
||||
3. Search for your problem in the `open and closed Github issues <https://github.com/overhangio/tutor/issues?utf8=%E2%9C%93&q=is%3Aissue>`_.
|
||||
4. Search for your problem in the `community forums <https://discuss.overhang.io>`__.
|
||||
5. If, despite all your efforts, you can't solve the problem by yourself, you should discuss it in the `community forums <https://discuss.overhang.io>`__. Please give as much details about your problem as possible! As a rule of thumb, **people will not dedicate more time to solving your problem than you took to write your question**.
|
||||
6. If you are *absolutely* positive that you are facing a technical issue with Tutor, and not with Open edX, not with your server, not your custom configuration, then, and only then, should you open an issue on `Github <https://github.com/overhangio/tutor/issues/new>`__. You *must* follow the instructions from the issue template!!! If you do not follow this procedure, your Github issues will be mercilessly closed 🤯.
|
||||
6. If you are *absolutely* positive that you are facing a technical issue with Tutor, and not with Open edX, not with your server, not your custom configuration, then, and only then, should you open an issue on `Github <https://github.com/overhangio/tutor/issues/>`__. You *must* follow the instructions from the issue template!!! If you do not follow this procedure, your Github issues will be mercilessly closed 🤯.
|
||||
|
||||
Do you need professional assistance with your tutor-managed Open edX platform? Overhang.IO offers online support as part of its `Long Term Support (LTS) offering <https://overhang.io/tutor/pricing>`__.
|
||||
|
||||
|
@ -23,7 +23,7 @@ Tutor makes it easy to :ref:`develop <theming>` and :ref:`install <settheme>` yo
|
||||
Adding features
|
||||
---------------
|
||||
|
||||
Check out the Tutor :ref:`plugins <plugins>`, :ref:`extra features <extra>` and :ref:`configuration/customization options <configuration_customisation>`.
|
||||
Check out the Tutor :ref:`plugins <plugins>` and :ref:`configuration/customization options <configuration_customisation>`.
|
||||
|
||||
Hacking into Open edX
|
||||
---------------------
|
||||
@ -38,4 +38,4 @@ Yes, Tutor comes with Kubernetes deployment support :ref:`out of the box <k8s>`.
|
||||
Meeting the community
|
||||
---------------------
|
||||
|
||||
Ask your questions and chat with the Tutor community on the official community forums: https://discuss.overhang.io
|
||||
Ask your questions and chat with the Tutor community on the official community forums: https://discuss.overhang.io
|
||||
|
@ -1,6 +1,5 @@
|
||||
appdirs
|
||||
click
|
||||
click_repl
|
||||
mypy
|
||||
pycryptodome
|
||||
jinja2
|
||||
|
@ -14,12 +14,8 @@ certifi==2021.5.30
|
||||
# requests
|
||||
chardet==4.0.0
|
||||
# via requests
|
||||
click-repl==0.2.0
|
||||
# via -r requirements/base.in
|
||||
click==8.0.1
|
||||
# via
|
||||
# -r requirements/base.in
|
||||
# click-repl
|
||||
# via -r requirements/base.in
|
||||
google-auth==1.30.1
|
||||
# via kubernetes
|
||||
idna==2.10
|
||||
@ -36,8 +32,6 @@ mypy==0.812
|
||||
# via -r requirements/base.in
|
||||
oauthlib==3.1.1
|
||||
# via requests-oauthlib
|
||||
prompt-toolkit==3.0.18
|
||||
# via click-repl
|
||||
pyasn1-modules==0.2.8
|
||||
# via google-auth
|
||||
pyasn1==0.4.8
|
||||
@ -62,7 +56,6 @@ rsa==4.7.2
|
||||
# via google-auth
|
||||
six==1.16.0
|
||||
# via
|
||||
# click-repl
|
||||
# google-auth
|
||||
# kubernetes
|
||||
# python-dateutil
|
||||
@ -74,8 +67,6 @@ urllib3==1.26.5
|
||||
# via
|
||||
# kubernetes
|
||||
# requests
|
||||
wcwidth==0.2.5
|
||||
# via prompt-toolkit
|
||||
websocket-client==1.0.1
|
||||
# via kubernetes
|
||||
|
||||
|
@ -31,13 +31,10 @@ chardet==4.0.0
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# requests
|
||||
click-repl==0.2.0
|
||||
# via -r requirements/base.txt
|
||||
click==8.0.1
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# black
|
||||
# click-repl
|
||||
# pip-tools
|
||||
colorama==0.4.4
|
||||
# via twine
|
||||
@ -98,10 +95,6 @@ pip-tools==6.1.0
|
||||
# via -r requirements/dev.in
|
||||
pkginfo==1.7.0
|
||||
# via twine
|
||||
prompt-toolkit==3.0.18
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# click-repl
|
||||
pyasn1-modules==0.2.8
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
@ -162,7 +155,6 @@ six==1.16.0
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# bleach
|
||||
# click-repl
|
||||
# google-auth
|
||||
# kubernetes
|
||||
# python-dateutil
|
||||
@ -189,10 +181,6 @@ urllib3==1.26.5
|
||||
# -r requirements/base.txt
|
||||
# kubernetes
|
||||
# requests
|
||||
wcwidth==0.2.5
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# prompt-toolkit
|
||||
webencodings==0.5.1
|
||||
# via bleach
|
||||
websocket-client==1.0.1
|
||||
|
@ -23,12 +23,8 @@ chardet==4.0.0
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# requests
|
||||
click-repl==0.2.0
|
||||
# via -r requirements/base.txt
|
||||
click==8.0.1
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# click-repl
|
||||
# via -r requirements/base.txt
|
||||
docutils==0.16
|
||||
# via
|
||||
# sphinx
|
||||
@ -65,10 +61,6 @@ oauthlib==3.1.1
|
||||
# requests-oauthlib
|
||||
packaging==20.9
|
||||
# via sphinx
|
||||
prompt-toolkit==3.0.18
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# click-repl
|
||||
pyasn1-modules==0.2.8
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
@ -111,7 +103,6 @@ rsa==4.7.2
|
||||
six==1.16.0
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# click-repl
|
||||
# google-auth
|
||||
# kubernetes
|
||||
# python-dateutil
|
||||
@ -148,10 +139,6 @@ urllib3==1.26.5
|
||||
# -r requirements/base.txt
|
||||
# kubernetes
|
||||
# requests
|
||||
wcwidth==0.2.5
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
# prompt-toolkit
|
||||
websocket-client==1.0.1
|
||||
# via
|
||||
# -r requirements/base.txt
|
||||
|
@ -1,7 +1,10 @@
|
||||
tutor-discovery
|
||||
tutor-ecommerce
|
||||
#tutor-figures
|
||||
tutor-license
|
||||
tutor-minio
|
||||
tutor-notes
|
||||
tutor-xqueue
|
||||
# change version ranges when upgrading from lilac
|
||||
tutor-android>=12.0.0,<13.0.0
|
||||
tutor-discovery>=12.0.0,<13.0.0
|
||||
tutor-ecommerce>=12.0.0,<13.0.0
|
||||
tutor-license>=12.0.0,<13.0.0
|
||||
tutor-mfe>=12.0.0,<13.0.0
|
||||
tutor-minio>=12.0.0,<13.0.0
|
||||
tutor-notes>=12.0.0,<13.0.0
|
||||
tutor-webui>=12.0.0,<13.0.0
|
||||
tutor-xqueue>=12.0.0,<13.0.0
|
||||
|
@ -42,6 +42,13 @@ class EnvTests(unittest.TestCase):
|
||||
"hello world", env.render_str({"name": "world"}, "hello {{ name }}")
|
||||
)
|
||||
|
||||
def test_render_unknown(self) -> None:
|
||||
config: Config = {
|
||||
"var1": "a",
|
||||
}
|
||||
self.assertEqual("ab", env.render_unknown(config, "{{ var1 }}b"))
|
||||
self.assertEqual({"x": "ac"}, env.render_unknown(config, {"x": "{{ var1 }}c"}))
|
||||
|
||||
def test_common_domain(self) -> None:
|
||||
self.assertEqual(
|
||||
"mydomain.com",
|
||||
@ -176,3 +183,22 @@ class EnvTests(unittest.TestCase):
|
||||
|
||||
self.assertNotIn("plugin1/myplugin.txt", env1.loader.list_templates())
|
||||
self.assertIn("plugin1/myplugin.txt", env2.loader.list_templates())
|
||||
|
||||
def test_iter_values_named(self) -> None:
|
||||
config: Config = {
|
||||
"something0_test_app": 0,
|
||||
"something1_test_not_app": 1,
|
||||
"notsomething_test_app": 2,
|
||||
"something3_test_app": 3,
|
||||
}
|
||||
renderer = env.Renderer.instance(config)
|
||||
self.assertEqual([2, 3], list(renderer.iter_values_named(suffix="test_app")))
|
||||
self.assertEqual([1, 3], list(renderer.iter_values_named(prefix="something")))
|
||||
self.assertEqual(
|
||||
[0, 3],
|
||||
list(
|
||||
renderer.iter_values_named(
|
||||
prefix="something", suffix="test_app", allow_empty=True
|
||||
)
|
||||
),
|
||||
)
|
||||
|
@ -2,16 +2,12 @@
|
||||
import importlib
|
||||
import os
|
||||
import pkg_resources
|
||||
import wcwidth
|
||||
|
||||
block_cipher = None
|
||||
|
||||
datas = [("./tutor/templates", "./tutor/templates")]
|
||||
hidden_imports = []
|
||||
|
||||
# Fix missing wcwidth/version.json file
|
||||
datas.append((os.path.dirname(wcwidth.__file__), 'wcwidth'))
|
||||
|
||||
# Auto-discover plugins and include patches & templates folders
|
||||
for entrypoint in pkg_resources.iter_entry_points("tutor.plugin.v0"):
|
||||
plugin_name = entrypoint.name
|
||||
|
@ -1 +1 @@
|
||||
__version__ = "11.3.1"
|
||||
__version__ = "12.0.0"
|
||||
|
@ -1,52 +0,0 @@
|
||||
import click
|
||||
|
||||
from .compose import ComposeJobRunner
|
||||
from .local import docker_compose as local_docker_compose
|
||||
from .. import config as tutor_config
|
||||
from .. import env as tutor_env
|
||||
from .. import fmt
|
||||
from ..types import Config
|
||||
from .context import Context
|
||||
|
||||
|
||||
@click.group(help="Build an Android app for your Open edX platform [BETA FEATURE]")
|
||||
def android() -> None:
|
||||
pass
|
||||
|
||||
|
||||
@click.command(help="Build the application")
|
||||
@click.argument("mode", type=click.Choice(["debug", "release"]))
|
||||
@click.pass_obj
|
||||
def build(context: Context, mode: str) -> None:
|
||||
config = tutor_config.load(context.root)
|
||||
docker_run(context.root, build_command(config, mode))
|
||||
fmt.echo_info(
|
||||
"The {} APK file is available in {}".format(
|
||||
mode, tutor_env.data_path(context.root, "android")
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def build_command(config: Config, target: str) -> str:
|
||||
gradle_target = {
|
||||
"debug": "assembleProdDebuggable",
|
||||
"release": "assembleProdRelease",
|
||||
}[target]
|
||||
apk_folder = {"debug": "debuggable", "release": "release"}[target]
|
||||
|
||||
command = """
|
||||
sed -i "s/APPLICATION_ID = .*/APPLICATION_ID = \\"{{ LMS_HOST|reverse_host|replace("-", "_") }}\\"/g" constants.gradle
|
||||
./gradlew {gradle_target}
|
||||
cp OpenEdXMobile/build/outputs/apk/prod/{apk_folder}/*.apk /openedx/data/"""
|
||||
command = tutor_env.render_str(config, command)
|
||||
command = command.format(gradle_target=gradle_target, apk_folder=apk_folder)
|
||||
return command
|
||||
|
||||
|
||||
def docker_run(root: str, command: str) -> None:
|
||||
config = tutor_config.load(root)
|
||||
runner = ComposeJobRunner(root, config, local_docker_compose)
|
||||
runner.run_job("android", command)
|
||||
|
||||
|
||||
android.add_command(build)
|
@ -3,9 +3,7 @@ import sys
|
||||
|
||||
import appdirs
|
||||
import click
|
||||
import click_repl
|
||||
|
||||
from .android import android
|
||||
from .config import config_command
|
||||
from .context import Context
|
||||
from .dev import dev
|
||||
@ -13,8 +11,6 @@ from .images import images_command
|
||||
from .k8s import k8s
|
||||
from .local import local
|
||||
from .plugins import plugins_command, add_plugin_commands
|
||||
from .ui import ui
|
||||
from .webui import webui
|
||||
from ..__about__ import __version__
|
||||
from .. import exceptions
|
||||
from .. import fmt
|
||||
@ -23,15 +19,11 @@ from .. import utils
|
||||
|
||||
def main() -> None:
|
||||
try:
|
||||
click_repl.register_repl(cli, name="ui")
|
||||
cli.add_command(images_command)
|
||||
cli.add_command(config_command)
|
||||
cli.add_command(local)
|
||||
cli.add_command(dev)
|
||||
cli.add_command(android)
|
||||
cli.add_command(k8s)
|
||||
cli.add_command(ui)
|
||||
cli.add_command(webui)
|
||||
cli.add_command(print_help)
|
||||
cli.add_command(plugins_command)
|
||||
add_plugin_commands(cli)
|
||||
|
@ -83,7 +83,10 @@ class ComposeJobRunner(jobs.BaseJobRunner):
|
||||
)
|
||||
|
||||
|
||||
@click.command(help="Run all or a selection of configured Open edX services")
|
||||
@click.command(
|
||||
short_help="Run all or a selection of services.",
|
||||
help="Run all or a selection of services. Docker images will be rebuilt where necessary.",
|
||||
)
|
||||
@click.option("-d", "--detach", is_flag=True, help="Start in daemon mode")
|
||||
@click.argument("services", metavar="service", nargs=-1)
|
||||
@click.pass_obj
|
||||
@ -93,6 +96,9 @@ def start(context: Context, detach: bool, services: List[str]) -> None:
|
||||
command.append("-d")
|
||||
|
||||
config = tutor_config.load(context.root)
|
||||
# Rebuild Docker images with a `build: ...` context.
|
||||
context.docker_compose(context.root, config, "build")
|
||||
# Start services
|
||||
context.docker_compose(context.root, config, *command, *services)
|
||||
|
||||
|
||||
|
@ -11,7 +11,7 @@ from ..types import Config
|
||||
from .. import utils
|
||||
from .context import Context
|
||||
|
||||
BASE_IMAGE_NAMES = ["openedx", "forum", "android"]
|
||||
BASE_IMAGE_NAMES = ["openedx", "forum"]
|
||||
DEV_IMAGE_NAMES = ["openedx-dev"]
|
||||
VENDOR_IMAGES = [
|
||||
"caddy",
|
||||
|
@ -396,7 +396,10 @@ def wait(context: Context, name: str) -> None:
|
||||
|
||||
@click.command(help="Upgrade from a previous Open edX named release")
|
||||
@click.option(
|
||||
"--from", "from_version", default="ironwood", type=click.Choice(["ironwood"])
|
||||
"--from",
|
||||
"from_version",
|
||||
default="koa",
|
||||
type=click.Choice(["ironwood", "juniper", "koa"]),
|
||||
)
|
||||
@click.pass_obj
|
||||
def upgrade(context: Context, from_version: str) -> None:
|
||||
@ -408,8 +411,13 @@ def upgrade(context: Context, from_version: str) -> None:
|
||||
running_version = "juniper"
|
||||
|
||||
if running_version == "juniper":
|
||||
upgrade_from_juniper(config)
|
||||
running_version = "koa"
|
||||
|
||||
if running_version == "koa":
|
||||
upgrade_from_koa(config)
|
||||
running_version = "lilac"
|
||||
|
||||
|
||||
def upgrade_from_ironwood(config: Config) -> None:
|
||||
if not config["RUN_MONGODB"]:
|
||||
@ -458,6 +466,26 @@ your MySQL database from v5.6 to v5.7. You should run something similar to:
|
||||
fmt.echo_info(message)
|
||||
|
||||
|
||||
def upgrade_from_koa(config: Config) -> None:
|
||||
if not config["RUN_MONGODB"]:
|
||||
fmt.echo_info(
|
||||
"You are not running MongDB (RUN_MONGODB=false). It is your "
|
||||
"responsibility to upgrade your MongoDb instance to v4.0. There is "
|
||||
"nothing left to do to upgrade to Lilac from Koa."
|
||||
)
|
||||
return
|
||||
message = """Automatic release upgrade is unsupported in Kubernetes. To upgrade from Koa to Lilac, you should upgrade
|
||||
your MongoDb cluster from v3.6 to v4.0. You should run something similar to:
|
||||
|
||||
tutor k8s stop
|
||||
tutor config save --set DOCKER_IMAGE_MONGODB=mongo:4.0
|
||||
tutor k8s start
|
||||
tutor k8s exec mongodb mongo --eval 'db.adminCommand({ setFeatureCompatibilityVersion: "4.0" })'
|
||||
tutor config save --unset DOCKER_IMAGE_MONGODB
|
||||
"""
|
||||
fmt.echo_info(message)
|
||||
|
||||
|
||||
def kubectl_exec(
|
||||
config: Config, service: str, command: str, attach: bool = False
|
||||
) -> int:
|
||||
|
@ -87,8 +87,8 @@ Your Open edX platform is ready and can be accessed at the following urls:
|
||||
@click.option(
|
||||
"--from",
|
||||
"from_version",
|
||||
default="juniper",
|
||||
type=click.Choice(["ironwood", "juniper"]),
|
||||
default="koa",
|
||||
type=click.Choice(["ironwood", "juniper", "koa"]),
|
||||
)
|
||||
@click.option("-I", "--non-interactive", is_flag=True, help="Run non-interactively")
|
||||
@click.pass_context
|
||||
@ -117,6 +117,10 @@ Are you sure you want to continue?"""
|
||||
upgrade_from_juniper(context, config)
|
||||
running_version = "koa"
|
||||
|
||||
if running_version == "koa":
|
||||
upgrade_from_koa(context, config)
|
||||
running_version = "lilac"
|
||||
|
||||
|
||||
def upgrade_from_ironwood(context: click.Context, config: Config) -> None:
|
||||
click.echo(fmt.title("Upgrading from Ironwood"))
|
||||
@ -129,40 +133,13 @@ def upgrade_from_ironwood(context: click.Context, config: Config) -> None:
|
||||
fmt.echo_info(
|
||||
"You are not running MongDB (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."
|
||||
"nothing left to do to upgrade from Ironwood to Juniper."
|
||||
)
|
||||
return
|
||||
|
||||
# Note that the DOCKER_IMAGE_MONGODB value is never saved, because we only save the
|
||||
# environment, not the configuration.
|
||||
click.echo(fmt.title("Upgrading MongoDb from v3.2 to v3.4"))
|
||||
config["DOCKER_IMAGE_MONGODB"] = "mongo:3.4.24"
|
||||
tutor_env.save(context.obj.root, config)
|
||||
context.invoke(compose.start, detach=True, services=["mongodb"])
|
||||
context.invoke(
|
||||
compose.execute,
|
||||
args=[
|
||||
"mongodb",
|
||||
"mongo",
|
||||
"--eval",
|
||||
'db.adminCommand({ setFeatureCompatibilityVersion: "3.4" })',
|
||||
],
|
||||
)
|
||||
upgrade_mongodb(context, config, "3.4")
|
||||
context.invoke(compose.stop)
|
||||
|
||||
click.echo(fmt.title("Upgrading MongoDb from v3.4 to v3.6"))
|
||||
config["DOCKER_IMAGE_MONGODB"] = "mongo:3.6.18"
|
||||
tutor_env.save(context.obj.root, config)
|
||||
context.invoke(compose.start, detach=True, services=["mongodb"])
|
||||
context.invoke(
|
||||
compose.execute,
|
||||
args=[
|
||||
"mongodb",
|
||||
"mongo",
|
||||
"--eval",
|
||||
'db.adminCommand({ setFeatureCompatibilityVersion: "3.6" })',
|
||||
],
|
||||
)
|
||||
upgrade_mongodb(context, config, "3.6")
|
||||
context.invoke(compose.stop)
|
||||
|
||||
|
||||
@ -198,6 +175,36 @@ def upgrade_from_juniper(context: click.Context, config: Config) -> None:
|
||||
context.invoke(compose.stop)
|
||||
|
||||
|
||||
def upgrade_from_koa(context: click.Context, config: Config) -> None:
|
||||
if not config["RUN_MONGODB"]:
|
||||
fmt.echo_info(
|
||||
"You are not running MongDB (RUN_MONGODB=false). It is your "
|
||||
"responsibility to upgrade your MongoDb instance to v4.0. There is "
|
||||
"nothing left to do to upgrade from Koa to Lilac."
|
||||
)
|
||||
return
|
||||
upgrade_mongodb(context, config, "4.0")
|
||||
|
||||
|
||||
def upgrade_mongodb(context: click.Context, config: Config, to_version: str) -> None:
|
||||
click.echo(fmt.title("Upgrading MongoDb to v{}".format(to_version)))
|
||||
# Note that the DOCKER_IMAGE_MONGODB value is never saved, because we only save the
|
||||
# environment, not the configuration.
|
||||
config["DOCKER_IMAGE_MONGODB"] = "mongo:{}".format(to_version)
|
||||
tutor_env.save(context.obj.root, config)
|
||||
context.invoke(compose.start, detach=True, services=["mongodb"])
|
||||
context.invoke(
|
||||
compose.execute,
|
||||
args=[
|
||||
"mongodb",
|
||||
"mongo",
|
||||
"--eval",
|
||||
'db.adminCommand({ setFeatureCompatibilityVersion: "%s" })' % to_version,
|
||||
],
|
||||
)
|
||||
context.invoke(compose.stop)
|
||||
|
||||
|
||||
local.add_command(quickstart)
|
||||
local.add_command(upgrade)
|
||||
compose.add_commands(local)
|
||||
|
@ -1,21 +0,0 @@
|
||||
import click
|
||||
import click_repl
|
||||
|
||||
|
||||
@click.command(
|
||||
short_help="Interactive shell",
|
||||
help="Launch an interactive shell for launching Tutor commands",
|
||||
)
|
||||
def ui() -> None:
|
||||
click.echo(
|
||||
"""Welcome to the Tutor interactive shell UI!
|
||||
Type "help" to view all available commands.
|
||||
Type "local quickstart" to configure and launch a new platform from scratch.
|
||||
Type <ctrl-d> to exit."""
|
||||
)
|
||||
while True:
|
||||
try:
|
||||
click_repl.repl(click.get_current_context())
|
||||
return # this happens on a ctrl+d
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
@ -1,161 +0,0 @@
|
||||
import io
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
from typing import Dict, Optional
|
||||
from urllib.request import urlopen
|
||||
|
||||
import click
|
||||
|
||||
# Note: it is important that this module does not depend on config, such that
|
||||
# the web ui can be launched even where there is no configuration.
|
||||
from .. import fmt
|
||||
from .. import env as tutor_env
|
||||
from .. import exceptions
|
||||
from .. import serialize
|
||||
from ..types import Config
|
||||
from .context import Context
|
||||
|
||||
|
||||
@click.group(
|
||||
short_help="Web user interface", help="""Run Tutor commands from a web terminal"""
|
||||
)
|
||||
def webui() -> None:
|
||||
pass
|
||||
|
||||
|
||||
@click.command(help="Start the web UI")
|
||||
@click.option(
|
||||
"-p",
|
||||
"--port",
|
||||
default=3737,
|
||||
type=int,
|
||||
show_default=True,
|
||||
help="Port number to listen",
|
||||
)
|
||||
@click.option(
|
||||
"-h", "--host", default="0.0.0.0", show_default=True, help="Host address to listen"
|
||||
)
|
||||
@click.pass_obj
|
||||
def start(context: Context, port: int, host: str) -> None:
|
||||
check_gotty_binary(context.root)
|
||||
fmt.echo_info("Access the Tutor web UI at http://{}:{}".format(host, port))
|
||||
while True:
|
||||
config = load_config(context.root)
|
||||
user = config["user"]
|
||||
password = config["password"]
|
||||
command = [
|
||||
gotty_path(context.root),
|
||||
"--permit-write",
|
||||
"--address",
|
||||
host,
|
||||
"--port",
|
||||
str(port),
|
||||
"--title-format",
|
||||
"Tutor web UI - {{ .Command }} ({{ .Hostname }})",
|
||||
]
|
||||
if user and password:
|
||||
credential = "{}:{}".format(user, password)
|
||||
command += ["--credential", credential]
|
||||
else:
|
||||
fmt.echo_alert(
|
||||
"Running web UI without user authentication. Run 'tutor webui configure' to setup authentication"
|
||||
)
|
||||
command += [sys.argv[0], "ui"]
|
||||
p = subprocess.Popen(command)
|
||||
while True:
|
||||
try:
|
||||
p.wait(timeout=2)
|
||||
except subprocess.TimeoutExpired:
|
||||
new_config = load_config(context.root)
|
||||
if new_config != config:
|
||||
click.echo(
|
||||
"WARNING configuration changed. Tutor web UI is now going to restart. Reload this page to continue."
|
||||
)
|
||||
p.kill()
|
||||
p.wait()
|
||||
break
|
||||
|
||||
|
||||
@click.command(help="Configure authentication")
|
||||
@click.option("-u", "--user", prompt="User name", help="Authentication user name")
|
||||
@click.option(
|
||||
"-p",
|
||||
"--password",
|
||||
prompt=True,
|
||||
hide_input=True,
|
||||
confirmation_prompt=True,
|
||||
help="Authentication password",
|
||||
)
|
||||
@click.pass_obj
|
||||
def configure(context: Context, user: str, password: str) -> None:
|
||||
save_webui_config_file(context.root, {"user": user, "password": password})
|
||||
fmt.echo_info(
|
||||
"The web UI configuration has been updated. "
|
||||
"If at any point you wish to reset your username and password, "
|
||||
"just delete the following file:\n\n {}".format(config_path(context.root))
|
||||
)
|
||||
|
||||
|
||||
def check_gotty_binary(root: str) -> None:
|
||||
path = gotty_path(root)
|
||||
if os.path.exists(path):
|
||||
return
|
||||
fmt.echo_info("Downloading gotty to {}...".format(path))
|
||||
|
||||
# Generate release url
|
||||
# Note: I don't know how to handle arm
|
||||
architecture = "amd64" if platform.architecture()[0] == "64bit" else "386"
|
||||
url = "https://github.com/yudai/gotty/releases/download/v1.0.1/gotty_{system}_{architecture}.tar.gz".format(
|
||||
system=platform.system().lower(), architecture=architecture
|
||||
)
|
||||
|
||||
# Download
|
||||
response = urlopen(url)
|
||||
|
||||
# Decompress
|
||||
dirname = os.path.dirname(path)
|
||||
if not os.path.exists(dirname):
|
||||
os.makedirs(dirname)
|
||||
compressed = tarfile.open(fileobj=io.BytesIO(response.read()))
|
||||
compressed.extract("./gotty", dirname)
|
||||
|
||||
|
||||
def load_config(root: str) -> Dict[str, Optional[str]]:
|
||||
path = config_path(root)
|
||||
if not os.path.exists(path):
|
||||
save_webui_config_file(root, {"user": None, "password": None})
|
||||
with open(config_path(root)) as f:
|
||||
config = serialize.load(f)
|
||||
if not isinstance(config, dict):
|
||||
raise exceptions.TutorError(
|
||||
"Invalid webui: expected dict, got {}".format(config.__class__)
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def save_webui_config_file(root: str, config: Config) -> None:
|
||||
path = config_path(root)
|
||||
directory = os.path.dirname(path)
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
with open(path, "w") as of:
|
||||
serialize.dump(config, of)
|
||||
|
||||
|
||||
def gotty_path(root: str) -> str:
|
||||
return get_path(root, "gotty")
|
||||
|
||||
|
||||
def config_path(root: str) -> str:
|
||||
return get_path(root, "config.yml")
|
||||
|
||||
|
||||
def get_path(root: str, filename: str) -> str:
|
||||
return tutor_env.pathjoin(root, "webui", filename)
|
||||
|
||||
|
||||
webui.add_command(start)
|
||||
webui.add_command(configure)
|
@ -102,7 +102,6 @@ def load_required(config: Config, defaults: Config) -> None:
|
||||
"OPENEDX_SECRET_KEY",
|
||||
"MYSQL_ROOT_PASSWORD",
|
||||
"OPENEDX_MYSQL_PASSWORD",
|
||||
"ANDROID_OAUTH2_SECRET",
|
||||
"ID",
|
||||
"JWT_RSA_PRIVATE_KEY",
|
||||
]:
|
||||
|
69
tutor/env.py
69
tutor/env.py
@ -8,7 +8,7 @@ import pkg_resources
|
||||
|
||||
from . import exceptions, fmt, plugins, utils
|
||||
from .__about__ import __version__
|
||||
from .types import Config
|
||||
from .types import Config, ConfigValue
|
||||
|
||||
TEMPLATES_ROOT = pkg_resources.resource_filename("tutor", "templates")
|
||||
VERSION_FILENAME = "version"
|
||||
@ -52,12 +52,13 @@ class Renderer:
|
||||
environment.filters["encrypt"] = utils.encrypt
|
||||
environment.filters["list_if"] = utils.list_if
|
||||
environment.filters["long_to_base64"] = utils.long_to_base64
|
||||
environment.globals["iter_values_named"] = self.iter_values_named
|
||||
environment.globals["patch"] = self.patch
|
||||
environment.filters["random_string"] = utils.random_string
|
||||
environment.filters["reverse_host"] = utils.reverse_host
|
||||
environment.globals["rsa_import_key"] = utils.rsa_import_key
|
||||
environment.filters["rsa_private_key"] = utils.rsa_private_key
|
||||
environment.filters["walk_templates"] = self.walk_templates
|
||||
environment.globals["patch"] = self.patch
|
||||
environment.globals["rsa_import_key"] = utils.rsa_import_key
|
||||
environment.globals["TUTOR_VERSION"] = __version__
|
||||
self.environment = environment
|
||||
|
||||
@ -71,6 +72,29 @@ class Renderer:
|
||||
if template.startswith(full_prefix) and self.is_part_of_env(template):
|
||||
yield template
|
||||
|
||||
def iter_values_named(
|
||||
self,
|
||||
prefix: Optional[str] = None,
|
||||
suffix: Optional[str] = None,
|
||||
allow_empty: bool = False,
|
||||
) -> Iterable[ConfigValue]:
|
||||
"""
|
||||
Iterate on all config values for which the name match the given pattern.
|
||||
|
||||
Note that here we only iterate on the values, not the key names. Empty
|
||||
values (those that evaluate to boolean `false`) will not be yielded, unless
|
||||
`allow_empty` is True.
|
||||
TODO document this in the plugins API
|
||||
"""
|
||||
for var_name, value in self.config.items():
|
||||
if prefix is not None and not var_name.startswith(prefix):
|
||||
continue
|
||||
if suffix is not None and not var_name.endswith(suffix):
|
||||
continue
|
||||
if not allow_empty and not value:
|
||||
continue
|
||||
yield value
|
||||
|
||||
def walk_templates(self, subdir: str) -> Iterable[str]:
|
||||
"""
|
||||
Iterate on the template files from `templates/<subdir>`.
|
||||
@ -110,15 +134,13 @@ class Renderer:
|
||||
"""
|
||||
patches = []
|
||||
for plugin, patch in plugins.iter_patches(self.config, name):
|
||||
patch_template = self.environment.from_string(patch)
|
||||
try:
|
||||
patches.append(patch_template.render(**self.config))
|
||||
except jinja2.exceptions.UndefinedError as e:
|
||||
raise exceptions.TutorError(
|
||||
"Missing configuration value: {} in patch '{}' from plugin {}".format(
|
||||
e.args[0], name, plugin
|
||||
)
|
||||
patches.append(self.render_str(patch))
|
||||
except exceptions.TutorError:
|
||||
fmt.echo_error(
|
||||
"Error rendering patch '{}' from plugin {}".format(name, plugin)
|
||||
)
|
||||
raise
|
||||
rendered = separator.join(patches)
|
||||
if rendered:
|
||||
rendered += suffix
|
||||
@ -180,13 +202,11 @@ def save(root: str, config: Config) -> None:
|
||||
"""
|
||||
root_env = pathjoin(root)
|
||||
for prefix in [
|
||||
"android/",
|
||||
"apps/",
|
||||
"build/",
|
||||
"dev/",
|
||||
"k8s/",
|
||||
"local/",
|
||||
"webui/",
|
||||
VERSION_FILENAME,
|
||||
"kustomization.yml",
|
||||
]:
|
||||
@ -252,27 +272,16 @@ def render_file(config: Config, *path: str) -> Union[str, bytes]:
|
||||
return renderer.render_template(template_name)
|
||||
|
||||
|
||||
def render_dict(config: Config) -> None:
|
||||
"""
|
||||
Render the values from the dict. This is useful for rendering the default
|
||||
values from config.yml.
|
||||
|
||||
Args:
|
||||
config (dict)
|
||||
"""
|
||||
rendered: Config = {}
|
||||
for key, value in config.items():
|
||||
if isinstance(value, str):
|
||||
rendered[key] = render_str(config, value)
|
||||
else:
|
||||
rendered[key] = value
|
||||
for k, v in rendered.items():
|
||||
config[k] = v
|
||||
|
||||
|
||||
def render_unknown(config: Config, value: Any) -> Any:
|
||||
"""
|
||||
Render an unknown `value` object with the selected config.
|
||||
|
||||
If `value` is a dict, its values are also rendered.
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
return render_str(config, value)
|
||||
elif isinstance(value, dict):
|
||||
return {k: render_unknown(config, v) for k, v in value.items()}
|
||||
return value
|
||||
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
edx.android {
|
||||
configFiles = ['tutor.yaml']
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
RELEASE_STORE_FILE=/openedx/config/app.keystore
|
||||
RELEASE_STORE_PASSWORD={{ ANDROID_RELEASE_STORE_PASSWORD }}
|
||||
RELEASE_KEY_PASSWORD={{ ANDROID_RELEASE_KEY_PASSWORD }}
|
||||
RELEASE_KEY_ALIAS={{ ANDROID_RELEASE_KEY_ALIAS }}
|
@ -1,18 +0,0 @@
|
||||
# See docs: https://openedx.atlassian.net/wiki/spaces/LEARNER/pages/48792067/App+Configuration+Flags
|
||||
API_HOST_URL: "{{ "https" if ENABLE_HTTPS else "http" }}://{{ LMS_HOST }}"
|
||||
ENVIRONMENT_DISPLAY_NAME: "tutor"
|
||||
PLATFORM_NAME: "{{ PLATFORM_NAME }}"
|
||||
PLATFORM_DESTINATION_NAME: "{{ LMS_HOST }}"
|
||||
FEEDBACK_EMAIL_ADDRESS: "{{ CONTACT_EMAIL }}"
|
||||
OAUTH_CLIENT_ID: "android"
|
||||
|
||||
COURSE_VIDEOS_ENABLED: true
|
||||
CERTIFICATES_ENABLED: true
|
||||
DISCUSSIONS_ENABLED: true
|
||||
DISCOVERY:
|
||||
COURSE:
|
||||
TYPE: native
|
||||
DOWNLOAD_TO_SD_CARD_ENABLED: true
|
||||
NEW_LOGISTRATION_ENABLED: true
|
||||
USER_PROFILES_ENABLED : true
|
||||
VIDEO_TRANSCRIPT_ENABLED: true
|
@ -29,11 +29,6 @@
|
||||
"ENABLE_COMPREHENSIVE_THEMING": true,
|
||||
"COMPREHENSIVE_THEME_DIRS": ["/openedx/themes"],
|
||||
"STATIC_ROOT_BASE": "/openedx/staticfiles",
|
||||
"ELASTIC_SEARCH_CONFIG": [{
|
||||
{% if ELASTICSEARCH_SCHEME == "https" %}"use_ssl": true,{% endif %}
|
||||
"host": "{{ ELASTICSEARCH_HOST }}",
|
||||
"port": {{ ELASTICSEARCH_PORT }}
|
||||
}],
|
||||
"EMAIL_BACKEND": "django.core.mail.backends.smtp.EmailBackend",
|
||||
"EMAIL_HOST": "{{ SMTP_HOST }}",
|
||||
"EMAIL_PORT": {{ SMTP_PORT }},
|
||||
|
@ -38,11 +38,6 @@
|
||||
"ENABLE_COMPREHENSIVE_THEMING": true,
|
||||
"COMPREHENSIVE_THEME_DIRS": ["/openedx/themes"],
|
||||
"STATIC_ROOT_BASE": "/openedx/staticfiles",
|
||||
"ELASTIC_SEARCH_CONFIG": [{
|
||||
{% if ELASTICSEARCH_SCHEME == "https" %}"use_ssl": true,{% endif %}
|
||||
"host": "{{ ELASTICSEARCH_HOST }}",
|
||||
"port": {{ ELASTICSEARCH_PORT }}
|
||||
}],
|
||||
"EMAIL_BACKEND": "django.core.mail.backends.smtp.EmailBackend",
|
||||
"EMAIL_HOST": "{{ SMTP_HOST }}",
|
||||
"EMAIL_PORT": {{ SMTP_PORT }},
|
||||
|
@ -1 +0,0 @@
|
||||
{% include "apps/openedx/settings/partials/pre_common_all.py" %}
|
@ -1 +0,0 @@
|
||||
{% include "apps/openedx/settings/partials/pre_common_all.py" %}
|
@ -32,6 +32,13 @@ for store in MODULESTORE["default"]["OPTIONS"]["stores"]:
|
||||
# Behave like memcache when it comes to connection errors
|
||||
DJANGO_REDIS_IGNORE_EXCEPTIONS = True
|
||||
|
||||
# Elasticsearch connection parameters
|
||||
ELASTIC_SEARCH_CONFIG = [{
|
||||
{% if ELASTICSEARCH_SCHEME == "https" %}"use_ssl": True,{% endif %}
|
||||
"host": "{{ ELASTICSEARCH_HOST }}",
|
||||
"port": {{ ELASTICSEARCH_PORT }},
|
||||
}]
|
||||
|
||||
DEFAULT_FROM_EMAIL = ENV_TOKENS.get("DEFAULT_FROM_EMAIL", ENV_TOKENS["CONTACT_EMAIL"])
|
||||
DEFAULT_FEEDBACK_EMAIL = ENV_TOKENS.get("DEFAULT_FEEDBACK_EMAIL", ENV_TOKENS["CONTACT_EMAIL"])
|
||||
SERVER_EMAIL = ENV_TOKENS.get("SERVER_EMAIL", ENV_TOKENS["CONTACT_EMAIL"])
|
||||
@ -86,6 +93,11 @@ LOGGING["handlers"]["tracking"] = {
|
||||
"formatter": "standard",
|
||||
}
|
||||
LOGGING["loggers"]["tracking"]["handlers"] = ["console", "local", "tracking"]
|
||||
# Silence some loggers (note: we must attempt to get rid of these when upgrading from one release to the next)
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning, module="newrelic.console")
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning, module="lms.djangoapps.course_wiki.plugins.markdownedx.wiki_plugin")
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning, module="wiki.plugins.links.wiki_plugin")
|
||||
|
||||
# Email
|
||||
EMAIL_USE_SSL = {{ SMTP_USE_SSL }}
|
||||
@ -146,5 +158,11 @@ CODE_JAIL = {
|
||||
"user": None,
|
||||
}
|
||||
|
||||
# Custom features
|
||||
# LTI 1.3 will be enabled by default after lilac, and it's going to be a big
|
||||
# deal, so we enable it early. We should remove this once the feature flag is
|
||||
# deprecated.
|
||||
FEATURES["LTI_1P3_ENABLED"] = True
|
||||
|
||||
{{ patch("openedx-common-settings") }}
|
||||
######## End of settings common to LMS and CMS
|
||||
|
@ -7,9 +7,6 @@ LOGIN_REDIRECT_WHITELIST = ["{{ CMS_HOST }}"]
|
||||
REGISTRATION_EXTRA_FIELDS["terms_of_service"] = "required"
|
||||
REGISTRATION_EXTRA_FIELDS["honor_code"] = "hidden"
|
||||
|
||||
# This url must not be None and should not be used anywhere
|
||||
LEARNING_MICROFRONTEND_URL = "http://learn.openedx.org"
|
||||
|
||||
# Fix media files paths
|
||||
PROFILE_IMAGE_BACKEND["options"]["location"] = os.path.join(
|
||||
MEDIA_ROOT, "profile-images/"
|
||||
@ -28,4 +25,4 @@ for folder in [DATA_DIR, LOG_DIR, MEDIA_ROOT, STATIC_ROOT_BASE, ORA2_FILEUPLOAD_
|
||||
|
||||
{{ patch("openedx-lms-common-settings") }}
|
||||
|
||||
######## End of common LMS settings
|
||||
######## End of common LMS settings
|
||||
|
@ -1,10 +0,0 @@
|
||||
# Silence overly verbose warnings
|
||||
import logging
|
||||
import warnings
|
||||
from django.utils.deprecation import RemovedInDjango30Warning, RemovedInDjango31Warning
|
||||
from rest_framework import RemovedInDRF310Warning, RemovedInDRF311Warning
|
||||
warnings.simplefilter('ignore', RemovedInDjango30Warning)
|
||||
warnings.simplefilter('ignore', RemovedInDjango31Warning)
|
||||
warnings.simplefilter('ignore', RemovedInDRF310Warning)
|
||||
warnings.simplefilter('ignore', RemovedInDRF311Warning)
|
||||
warnings.simplefilter('ignore', DeprecationWarning)
|
@ -1,40 +0,0 @@
|
||||
FROM docker.io/ubuntu:20.04
|
||||
MAINTAINER Overhang.io <contact@overhang.io>
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt update && \
|
||||
apt upgrade -y && \
|
||||
apt install -y wget unzip git openjdk-8-jre openjdk-8-jdk
|
||||
|
||||
RUN mkdir /openedx
|
||||
|
||||
# Install Android SDK
|
||||
# Inspired from https://github.com/LiveXP/docker-android-sdk/blob/master/Dockerfile
|
||||
ENV ANDROID_SDK_VERSION 6200805
|
||||
ENV ANDROID_SDK_PATH /openedx/android-sdk
|
||||
ENV ANDROID_HOME /openedx/android-sdk
|
||||
RUN mkdir ${ANDROID_HOME}
|
||||
WORKDIR /openedx/android-sdk
|
||||
RUN wget https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_VERSION}_latest.zip && \
|
||||
unzip commandlinetools-linux-${ANDROID_SDK_VERSION}_latest.zip && \
|
||||
rm commandlinetools-linux-${ANDROID_SDK_VERSION}_latest.zip
|
||||
|
||||
# Accept licenses
|
||||
# https://developer.android.com/studio/command-line/sdkmanager
|
||||
ARG ANDROID_API_LEVEL=28
|
||||
RUN yes | /openedx/android-sdk/tools/bin/sdkmanager --sdk_root=${ANDROID_HOME} --install "platforms;android-$ANDROID_API_LEVEL" 1> /dev/null
|
||||
|
||||
# Install android app repo
|
||||
ARG ANDROID_APP_REPOSITORY=https://github.com/edx/edx-app-android
|
||||
ARG ANDROID_APP_VERSION=release/2.23.2
|
||||
RUN git clone $ANDROID_APP_REPOSITORY --branch $ANDROID_APP_VERSION /openedx/edx-app-android
|
||||
WORKDIR /openedx/edx-app-android
|
||||
|
||||
# Install gradle and all dependencies
|
||||
RUN ./gradlew -v
|
||||
RUN ./gradlew tasks
|
||||
|
||||
# User-customized config
|
||||
COPY ./edx.properties ./OpenEdXMobile/edx.properties
|
||||
RUN mkdir /openedx/config
|
||||
RUN ln -s /openedx/config/gradle.properties ./OpenEdXMobile/gradle.properties
|
@ -1 +0,0 @@
|
||||
edx.dir = '/openedx/config'
|
@ -1,6 +1,8 @@
|
||||
#!/bin/sh -e
|
||||
|
||||
export MONGOHQ_URL="mongodb://$MONGODB_AUTH$MONGODB_HOST:$MONGODB_PORT/cs_comments_service"
|
||||
# the search server variable was renamed after the upgrade to elasticsearch 7
|
||||
export SEARCH_SERVER_ES7="$SEARCH_SERVER"
|
||||
|
||||
echo "Waiting for mongodb/elasticsearch..."
|
||||
dockerize -wait tcp://$MONGODB_HOST:$MONGODB_PORT -wait $SEARCH_SERVER -wait-retry-interval 5s -timeout 600s
|
||||
|
@ -36,29 +36,11 @@ RUN mkdir -p /openedx/edx-platform && \
|
||||
WORKDIR /openedx/edx-platform
|
||||
|
||||
{% if patch("openedx-dockerfile-git-patches-default") %}
|
||||
# Custom edx-platform default patches
|
||||
# Custom edx-platform patches
|
||||
{{ patch("openedx-dockerfile-git-patches-default") }}
|
||||
{% else %}
|
||||
# Patch edx-platform
|
||||
# Security patches
|
||||
# https://github.com/edx/edx-platform/pull/27394
|
||||
RUN curl https://github.com/overhangio/edx-platform/commit/a0fdc97f1704659d26e167de3fbf2ab8c371d67b.patch | git apply -
|
||||
# Django security releases
|
||||
RUN curl https://github.com/overhangio/edx-platform/commit/67973f2445f667af23f779d5551070835de03efe.patch | git apply -
|
||||
# Fix video unit completion
|
||||
RUN curl https://github.com/overhangio/edx-platform/commit/3d489952f7cfd83fed47c700c7cd0b477b68351e.patch | git apply -
|
||||
# Make it possible to disable learner records globally
|
||||
# https://github.com/edx/edx-platform/pull/25182
|
||||
# https://github.com/overhangio/edx-platform/tree/overhangio/disable-learner-records-from-settings
|
||||
RUN curl https://github.com/overhangio/edx-platform/commit/bd038bab3cf02df147e754f7743e46b68b43bac8.patch | git apply -
|
||||
# Fix inconvenient pavelib warning
|
||||
# https://github.com/edx/edx-platform/pull/25771
|
||||
# https://github.com/overhangio/edx-platform/tree/overhangio/fix-paver-warning
|
||||
RUN curl https://github.com/overhangio/edx-platform/commit/bc0ab09f9945bd14aa6be1dbbf928cce58f079d2.patch | git apply -
|
||||
# Fix js upload in scorm packages by upgrading django-pipeline
|
||||
# https://github.com/edx/edx-platform/pull/25957
|
||||
# https://github.com/overhangio/edx-platform/tree/overhangio/upgrade-django-pipeline
|
||||
RUN curl https://github.com/overhangio/edx-platform/commit/1c733e3ba11249cf16358684169e6137a308e796.patch | git apply -
|
||||
# RUN curl https://github.com/overhangio/edx-platform/commit/<sha1>.patch | git apply -
|
||||
{% endif %}
|
||||
|
||||
###### Download extra locales to /openedx/locale/contrib/locale
|
||||
@ -91,7 +73,7 @@ RUN pip install setuptools==44.1.0 pip==20.0.2 wheel==0.34.2
|
||||
RUN pip install -r ./requirements/edx/base.txt
|
||||
|
||||
# Install scorm xblock
|
||||
RUN pip install "openedx-scorm-xblock<12.0.0,>=11.0.0"
|
||||
RUN pip install "openedx-scorm-xblock<13.0.0,>=12.0.0"
|
||||
|
||||
# Install django-redis for using redis as a django cache
|
||||
RUN pip install django-redis==4.12.1
|
||||
@ -112,7 +94,7 @@ FROM python as nodejs-requirements
|
||||
ENV PATH /openedx/nodeenv/bin:/openedx/venv/bin:${PATH}
|
||||
|
||||
# Install nodeenv with the version provided by edx-platform
|
||||
RUN pip install nodeenv==1.4.0
|
||||
RUN pip install nodeenv==1.6.0
|
||||
RUN nodeenv /openedx/nodeenv --node=12.13.0 --prebuilt
|
||||
|
||||
# Install nodejs requirements
|
||||
|
81
tutor/templates/build/openedx/bin/site-configuration
Normal file
81
tutor/templates/build/openedx/bin/site-configuration
Normal file
@ -0,0 +1,81 @@
|
||||
#! /usr/bin/env python3
|
||||
import argparse
|
||||
import lms.startup
|
||||
|
||||
lms.startup.run()
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
from openedx.core.djangoapps.site_configuration.models import SiteConfiguration
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Manage site configuration")
|
||||
subparsers = parser.add_subparsers()
|
||||
|
||||
# Set command
|
||||
parser_set = subparsers.add_parser("set", help="Set a site configuration key/value")
|
||||
parser_set.add_argument(
|
||||
"-d", "--domain", help="Site domain: by default this will be the LMS domain"
|
||||
)
|
||||
parser_set.add_argument("key", help="Configuration key")
|
||||
parser_set.add_argument(
|
||||
"value",
|
||||
help="Configuration value: 'true' and 'false' will be converted to booleans.",
|
||||
)
|
||||
parser_set.set_defaults(func=set_command)
|
||||
|
||||
# Unset command
|
||||
parser_unset = subparsers.add_parser(
|
||||
"unset", help="Remove a site configuration key"
|
||||
)
|
||||
parser_unset.add_argument(
|
||||
"-d", "--domain", help="Site domain: by default this will be the LMS domain"
|
||||
)
|
||||
parser_unset.add_argument("key", help="Configuration key")
|
||||
parser_unset.set_defaults(func=unset_command)
|
||||
|
||||
args = parser.parse_args()
|
||||
if hasattr(args, "func"):
|
||||
args.func(args)
|
||||
else:
|
||||
parser.print_help()
|
||||
|
||||
|
||||
def set_command(args):
|
||||
configuration = get_site_configuration(args.domain)
|
||||
|
||||
value = args.value
|
||||
if value == "true":
|
||||
value = True
|
||||
elif value == "false":
|
||||
value = False
|
||||
|
||||
configuration.site_values[args.key] = args.value
|
||||
configuration.save()
|
||||
|
||||
|
||||
def get_site_configuration(domain):
|
||||
domain = domain or settings.LMS_BASE
|
||||
site, site_created = Site.objects.get_or_create(domain=domain)
|
||||
if site_created:
|
||||
site.name = domain
|
||||
site.save()
|
||||
configuration, configuration_created = SiteConfiguration.objects.get_or_create(site=site)
|
||||
if configuration_created:
|
||||
# Configuration is disabled by default
|
||||
configuration.enabled = True
|
||||
configuration.save()
|
||||
return configuration
|
||||
|
||||
|
||||
def unset_command(args):
|
||||
configuration = get_site_configuration(args.domain)
|
||||
if args.key in configuration.site_values:
|
||||
configuration.site_values.pop(args.key)
|
||||
configuration.save()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1 +1 @@
|
||||
EDX_PLATFORM_REVISION: koa
|
||||
EDX_PLATFORM_REVISION: lilac
|
@ -3,7 +3,6 @@
|
||||
MYSQL_ROOT_PASSWORD: "{{ 8|random_string }}"
|
||||
OPENEDX_MYSQL_PASSWORD: "{{ 8|random_string }}"
|
||||
OPENEDX_SECRET_KEY: "{{ 24|random_string }}"
|
||||
ANDROID_OAUTH2_SECRET: "{{ 24|random_string }}"
|
||||
ID: "{{ 24|random_string }}"
|
||||
|
||||
# This must be defined early
|
||||
@ -24,21 +23,17 @@ CMS_HOST: "studio.{{ LMS_HOST }}"
|
||||
CONTACT_EMAIL: "contact@{{ LMS_HOST }}"
|
||||
OPENEDX_AWS_ACCESS_KEY: ""
|
||||
OPENEDX_AWS_SECRET_ACCESS_KEY: ""
|
||||
ANDROID_RELEASE_STORE_PASSWORD: "android store password"
|
||||
ANDROID_RELEASE_KEY_PASSWORD: "android release key password"
|
||||
ANDROID_RELEASE_KEY_ALIAS: "android release key alias"
|
||||
DEV_PROJECT_NAME: "tutor_dev"
|
||||
DOCKER_REGISTRY: "docker.io/"
|
||||
DOCKER_IMAGE_OPENEDX: "{{ DOCKER_REGISTRY }}overhangio/openedx:{{ TUTOR_VERSION }}"
|
||||
DOCKER_IMAGE_OPENEDX_DEV: "{{ DOCKER_REGISTRY }}overhangio/openedx-dev:{{ TUTOR_VERSION }}"
|
||||
DOCKER_IMAGE_ANDROID: "{{ DOCKER_REGISTRY }}overhangio/openedx-android:{{ TUTOR_VERSION }}"
|
||||
DOCKER_IMAGE_CADDY: "{{ DOCKER_REGISTRY }}caddy:2.2.1"
|
||||
DOCKER_IMAGE_CADDY: "{{ DOCKER_REGISTRY }}caddy:2.3.0"
|
||||
DOCKER_IMAGE_FORUM: "{{ DOCKER_REGISTRY }}overhangio/openedx-forum:{{ TUTOR_VERSION }}"
|
||||
DOCKER_IMAGE_MONGODB: "{{ DOCKER_REGISTRY }}mongo:3.6.18"
|
||||
DOCKER_IMAGE_MYSQL: "{{ DOCKER_REGISTRY }}mysql:5.7.32"
|
||||
DOCKER_IMAGE_ELASTICSEARCH: "{{ DOCKER_REGISTRY }}elasticsearch:1.5.2"
|
||||
DOCKER_IMAGE_NGINX: "{{ DOCKER_REGISTRY }}nginx:1.13"
|
||||
DOCKER_IMAGE_REDIS: "{{ DOCKER_REGISTRY }}redis:6.0.9"
|
||||
DOCKER_IMAGE_MONGODB: "{{ DOCKER_REGISTRY }}mongo:4.0.23"
|
||||
DOCKER_IMAGE_MYSQL: "{{ DOCKER_REGISTRY }}mysql:5.7.33"
|
||||
DOCKER_IMAGE_ELASTICSEARCH: "{{ DOCKER_REGISTRY }}elasticsearch:7.8.1"
|
||||
DOCKER_IMAGE_NGINX: "{{ DOCKER_REGISTRY }}nginx:1.19.9"
|
||||
DOCKER_IMAGE_REDIS: "{{ DOCKER_REGISTRY }}redis:6.2.1"
|
||||
DOCKER_IMAGE_SMTP: "{{ DOCKER_REGISTRY }}namshi/smtp:latest"
|
||||
LOCAL_PROJECT_NAME: "tutor_local"
|
||||
ELASTICSEARCH_HOST: "elasticsearch"
|
||||
@ -57,14 +52,14 @@ MONGODB_DATABASE: "openedx"
|
||||
MONGODB_PORT: 27017
|
||||
MONGODB_USERNAME: ""
|
||||
MONGODB_PASSWORD: ""
|
||||
OPENEDX_CACHE_REDIS_DB: 1
|
||||
OPENEDX_CELERY_REDIS_DB: 0
|
||||
OPENEDX_CMS_UWSGI_WORKERS: 2
|
||||
OPENEDX_LMS_UWSGI_WORKERS: 2
|
||||
OPENEDX_MYSQL_DATABASE: "openedx"
|
||||
OPENEDX_CSMH_MYSQL_DATABASE: "{{ OPENEDX_MYSQL_DATABASE }}_csmh"
|
||||
OPENEDX_MYSQL_USERNAME: "openedx"
|
||||
OPENEDX_COMMON_VERSION: "open-release/koa.3"
|
||||
OPENEDX_CELERY_REDIS_DB: 0
|
||||
OPENEDX_CACHE_REDIS_DB: 1
|
||||
OPENEDX_COMMON_VERSION: "open-release/lilac.1"
|
||||
MYSQL_HOST: "mysql"
|
||||
MYSQL_PORT: 3306
|
||||
MYSQL_ROOT_USERNAME: "root"
|
||||
|
@ -1,2 +1,2 @@
|
||||
bundle exec rake search:initialize
|
||||
bundle exec rake search:rebuild_index
|
||||
bundle exec rake search:rebuild_indices
|
||||
|
@ -4,19 +4,6 @@ echo "Loading settings $DJANGO_SETTINGS_MODULE"
|
||||
|
||||
./manage.py lms migrate
|
||||
|
||||
# Delete obsolete credentials for Android application
|
||||
./manage.py lms shell -c 'from oauth2_provider.models import get_application_model
|
||||
get_application_model().objects.filter(name="android").exclude(user__username="login_service_user").delete()'
|
||||
# Create oauth credentials for Android application
|
||||
./manage.py lms create_dot_application \
|
||||
--client-id android \
|
||||
--client-secret {{ ANDROID_OAUTH2_SECRET }} \
|
||||
--grant-type password \
|
||||
--public \
|
||||
--update \
|
||||
android \
|
||||
login_service_user
|
||||
|
||||
# Fix incorrect uploaded file path
|
||||
if [ -d /openedx/data/uploads/ ]; then
|
||||
if [ -n "$(ls -A /openedx/data/uploads/)" ]; then
|
||||
|
@ -262,12 +262,16 @@ spec:
|
||||
- name: elasticsearch
|
||||
image: {{ DOCKER_IMAGE_ELASTICSEARCH }}
|
||||
env:
|
||||
- name: ES_JAVA_OPTS
|
||||
value: "-Xms1g -Xmx1g"
|
||||
- name: "cluster.name"
|
||||
value: openedx
|
||||
- name: "bootstrap.memory_lock"
|
||||
- name: cluster.name
|
||||
value: "openedx"
|
||||
- name: bootstrap.memory_lock
|
||||
value: "true"
|
||||
- name: discovery.type
|
||||
value: "single-node"
|
||||
- name: ES_JAVA_OPTS
|
||||
value: "-Xms{{ ELASTICSEARCH_HEAP_SIZE }} -Xmx{{ ELASTICSEARCH_HEAP_SIZE }}"
|
||||
- name: TAKE_FILE_OWNERSHIP
|
||||
value: "1"
|
||||
ports:
|
||||
- containerPort: 9200
|
||||
volumeMounts:
|
||||
|
@ -38,10 +38,4 @@ services:
|
||||
MONGODB_PORT: "{{ MONGODB_PORT }}"
|
||||
depends_on: {{ [("elasticsearch", RUN_ELASTICSEARCH), ("mongodb", RUN_MONGODB)]|list_if }}
|
||||
|
||||
android-job:
|
||||
image: {{ DOCKER_IMAGE_ANDROID }}
|
||||
volumes:
|
||||
- "../android/:/openedx/config/"
|
||||
- "../../data/android/:/openedx/data/"
|
||||
|
||||
{{ patch("local-docker-compose-jobs-services")|indent(4) }}
|
||||
{{ patch("local-docker-compose-jobs-services")|indent(4) }}
|
||||
|
@ -27,7 +27,12 @@ services:
|
||||
{% if RUN_ELASTICSEARCH %}
|
||||
elasticsearch:
|
||||
image: {{ DOCKER_IMAGE_ELASTICSEARCH }}
|
||||
command: ["elasticsearch", "-Xms{{ ELASTICSEARCH_HEAP_SIZE }}", "-Xmx{{ ELASTICSEARCH_HEAP_SIZE }}", "--cluster.name=openedx", "--bootstrap.mlockall=true"]
|
||||
environment:
|
||||
- cluster.name=openedx
|
||||
- bootstrap.memory_lock=true
|
||||
- discovery.type=single-node
|
||||
- "ES_JAVA_OPTS=-Xms{{ ELASTICSEARCH_HEAP_SIZE }} -Xmx{{ ELASTICSEARCH_HEAP_SIZE }}"
|
||||
- TAKE_FILE_OWNERSHIP=1
|
||||
ulimits:
|
||||
memlock:
|
||||
soft: -1
|
||||
|
Loading…
Reference in New Issue
Block a user