From e165bf04ea217e6b238b1226c2d155b9565bcf6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Behmo?= Date: Mon, 15 Nov 2021 18:00:50 +0100 Subject: [PATCH] feat: upgrade to maple In Maple, the plugin API is slightly different: the apk file is built and stored in the android-app Docker image, then served by a simple Caddy file server. This makes it easy to distribute the apk to students. --- .gitlab-ci.yml | 2 +- README.rst | 23 ++++----- setup.py | 2 +- tutorandroid/__about__.py | 2 +- tutorandroid/cli.py | 51 ------------------- tutorandroid/patches/caddyfile | 3 ++ tutorandroid/patches/k8s-deployments | 27 ++++++++++ tutorandroid/patches/k8s-services | 12 +++++ .../local-docker-compose-jobs-services | 5 -- .../patches/local-docker-compose-services | 6 +++ tutorandroid/plugin.py | 13 +++-- .../android/build/android/Dockerfile | 27 +++++----- .../android/build/android/edx.properties | 1 - .../templates/android/build/app/Dockerfile | 19 +++++++ .../{apps => build/app/config}/.gitignore | 0 .../{apps => build/app/config}/edx.properties | 0 .../app/config}/gradle.properties | 2 +- .../{apps => build/app/config}/tutor.yaml | 0 18 files changed, 102 insertions(+), 93 deletions(-) delete mode 100644 tutorandroid/cli.py create mode 100644 tutorandroid/patches/caddyfile create mode 100644 tutorandroid/patches/k8s-deployments create mode 100644 tutorandroid/patches/k8s-services delete mode 100644 tutorandroid/patches/local-docker-compose-jobs-services create mode 100644 tutorandroid/patches/local-docker-compose-services delete mode 100644 tutorandroid/templates/android/build/android/edx.properties create mode 100644 tutorandroid/templates/android/build/app/Dockerfile rename tutorandroid/templates/android/{apps => build/app/config}/.gitignore (100%) rename tutorandroid/templates/android/{apps => build/app/config}/edx.properties (100%) rename tutorandroid/templates/android/{apps => build/app/config}/gradle.properties (71%) rename tutorandroid/templates/android/{apps => build/app/config}/tutor.yaml (100%) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ba557e7..bc12633 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,7 @@ variables: TUTOR_PLUGIN: android TUTOR_IMAGES: android TUTOR_PYPI_PACKAGE: tutor-android - OPENEDX_RELEASE: lilac + OPENEDX_RELEASE: maple GITHUB_REPO: overhangio/tutor-android include: diff --git a/README.rst b/README.rst index fb39797..3461168 100644 --- a/README.rst +++ b/README.rst @@ -13,15 +13,13 @@ Installation Usage ----- -Enable the plugin:: +Enable the plugin and start the platform:: tutor plugins enable android + tutor local quickstart -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. +The ``.apk`` file will then be available for download at http(s)://mobile.LMS_HOST/app.apk. When running locally, this will be: http://mobile.local.overhang.io/app.apk. You can forward this address to your students for download. Building a custom Android app ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -38,22 +36,23 @@ 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:: +Releasing an Android app on the Play Store requires to build the app in release mode. To do so, modify the following Tutor settings:: - ANDROID_RELEASE_STORE_PASSWORD - ANDROID_RELEASE_KEY_PASSWORD - ANDROID_RELEASE_KEY_ALIAS + tutor config save \ + --set ANDROID_RELEASE_STORE_PASSWORD=yourstorepassword \ + --set ANDROID_RELEASE_KEY_PASSWORD=yourreleasekeypassword \ + --set ANDROID_RELEASE_KEY_ALIAS=yourreleasekeyalias \ + --set ANDROID_ENABLE_RELEASE_MODE=true -Then, place your keystore file in ``$(tutor config printroot)/env/plugins/android/apps/app.keystore``. Finally, build the application with:: +Then, place your keystore file in ``$(tutor config printroot)/env/plugins/android/build/app/config/app.keystore``. Finally, rebuild the image by starting the "android-app" container:: - tutor android build release + tutor local start -d android-app 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 `_. - License ------- diff --git a/setup.py b/setup.py index 9c51ca3..8075cbc 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ setup( packages=find_packages(exclude=["tests*"]), include_package_data=True, python_requires=">=3.5", - install_requires=["tutor>=12.0.0,<13.0.0"], + install_requires=["tutor>=13.0.0,<14.0.0"], entry_points={ "tutor.plugin.v0": [ "android = tutorandroid.plugin" diff --git a/tutorandroid/__about__.py b/tutorandroid/__about__.py index 805c1fe..1311252 100644 --- a/tutorandroid/__about__.py +++ b/tutorandroid/__about__.py @@ -1 +1 @@ -__version__ = "12.0.1" +__version__ = "13.0.0" diff --git a/tutorandroid/cli.py b/tutorandroid/cli.py deleted file mode 100644 index 4e94e6d..0000000 --- a/tutorandroid/cli.py +++ /dev/null @@ -1,51 +0,0 @@ -import click - -from tutor.commands.context import Context -from tutor.commands.local import LocalJobRunner -from tutor import config as tutor_config -from tutor import env as tutor_env -from tutor import fmt -from tutor.types import Config - - -@click.group(help="Build an Android app for your Open edX platform") -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 = LocalJobRunner(root, config) - runner.run_job("android", command) - - -android.add_command(build) diff --git a/tutorandroid/patches/caddyfile b/tutorandroid/patches/caddyfile new file mode 100644 index 0000000..186eb7f --- /dev/null +++ b/tutorandroid/patches/caddyfile @@ -0,0 +1,3 @@ +{{ ANDROID_APP_HOST }}{$default_site_port} { + import proxy "android-app:8000" +} diff --git a/tutorandroid/patches/k8s-deployments b/tutorandroid/patches/k8s-deployments new file mode 100644 index 0000000..8bb14bc --- /dev/null +++ b/tutorandroid/patches/k8s-deployments @@ -0,0 +1,27 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: android-app + labels: + app.kubernetes.io/name: android-app +spec: + selector: + matchLabels: + app.kubernetes.io/name: android-app + template: + metadata: + labels: + app.kubernetes.io/name: android-app + spec: + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + containers: + - name: android-app + image: {{ ANDROID_APP_DOCKER_IMAGE }} + imagePullPolicy: Always + ports: + - containerPort: 8000 + securityContext: + allowPrivilegeEscalation: false diff --git a/tutorandroid/patches/k8s-services b/tutorandroid/patches/k8s-services new file mode 100644 index 0000000..7c91581 --- /dev/null +++ b/tutorandroid/patches/k8s-services @@ -0,0 +1,12 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: android-app +spec: + type: NodePort + ports: + - port: 8000 + protocol: TCP + selector: + app.kubernetes.io/name: android-app diff --git a/tutorandroid/patches/local-docker-compose-jobs-services b/tutorandroid/patches/local-docker-compose-jobs-services deleted file mode 100644 index 0d05b18..0000000 --- a/tutorandroid/patches/local-docker-compose-jobs-services +++ /dev/null @@ -1,5 +0,0 @@ -android-job: - image: {{ ANDROID_DOCKER_IMAGE }} - volumes: - - "../plugins/android/apps:/openedx/config/" - - "../../data/android/:/openedx/data/" diff --git a/tutorandroid/patches/local-docker-compose-services b/tutorandroid/patches/local-docker-compose-services new file mode 100644 index 0000000..1492ae0 --- /dev/null +++ b/tutorandroid/patches/local-docker-compose-services @@ -0,0 +1,6 @@ +# Android app +android-app: + image: {{ ANDROID_APP_DOCKER_IMAGE }} + build: + context: ../plugins/android/build/app/ + restart: unless-stopped diff --git a/tutorandroid/plugin.py b/tutorandroid/plugin.py index bf5118a..c6ab305 100644 --- a/tutorandroid/plugin.py +++ b/tutorandroid/plugin.py @@ -3,7 +3,6 @@ import os import pkg_resources from .__about__ import __version__ -from .cli import android as android_command templates = pkg_resources.resource_filename("tutorandroid", "templates") @@ -13,7 +12,11 @@ config = { "add": {"OAUTH2_SECRET": "{{ 24|random_string }}"}, "defaults": { "VERSION": __version__, + "APP_HOST": "mobile.{{ LMS_HOST }}", + "APP_VERSION": "2.26.1", # https://github.com/edx/edx-app-android/releases "DOCKER_IMAGE": "{{ DOCKER_REGISTRY }}overhangio/openedx-android:{{ ANDROID_VERSION }}", + "APP_DOCKER_IMAGE": "{{ DOCKER_REGISTRY }}overhangio/openedx-android-app:{{ ANDROID_VERSION }}", + "ENABLE_RELEASE_MODE": False, "RELEASE_STORE_PASSWORD": "android store password", "RELEASE_KEY_PASSWORD": "android release key password", "RELEASE_KEY_ALIAS": "android release key alias", @@ -21,15 +24,15 @@ config = { } hooks = { - "build-image": {"android": "{{ ANDROID_DOCKER_IMAGE }}"}, + "build-image": { + "android": "{{ ANDROID_DOCKER_IMAGE }}", + "android-app": "{{ ANDROID_APP_DOCKER_IMAGE }}", + }, "remote-image": {"android": "{{ ANDROID_DOCKER_IMAGE }}"}, "init": ["lms"], } -command = android_command - - def patches(): all_patches = {} patches_dir = pkg_resources.resource_filename("tutorandroid", "patches") diff --git a/tutorandroid/templates/android/build/android/Dockerfile b/tutorandroid/templates/android/build/android/Dockerfile index 66a8d29..ff0185b 100644 --- a/tutorandroid/templates/android/build/android/Dockerfile +++ b/tutorandroid/templates/android/build/android/Dockerfile @@ -6,35 +6,32 @@ RUN apt update && \ apt upgrade -y && \ apt install -y wget unzip git openjdk-8-jre openjdk-8-jdk -RUN mkdir /openedx +RUN mkdir /app # 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 +# Get sdk version from here: https://developer.android.com/studio#command-tools +ENV ANDROID_SDK_VERSION 7583922 +ENV ANDROID_SDK_PATH /app/android-sdk +ENV ANDROID_HOME /app/android-sdk RUN mkdir ${ANDROID_HOME} -WORKDIR /openedx/android-sdk +WORKDIR /app/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 +# Check target version: https://github.com/edx/edx-app-android/blob/master/constants.gradle +ARG ANDROID_API_LEVEL=29 +RUN yes | /app/android-sdk/cmdline-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.25.1 -RUN git clone $ANDROID_APP_REPOSITORY --branch $ANDROID_APP_VERSION /openedx/edx-app-android -WORKDIR /openedx/edx-app-android +ARG ANDROID_APP_VERSION=release/{{ ANDROID_APP_VERSION }} +RUN git clone $ANDROID_APP_REPOSITORY --branch $ANDROID_APP_VERSION /app/edx-app-android +WORKDIR /app/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 diff --git a/tutorandroid/templates/android/build/android/edx.properties b/tutorandroid/templates/android/build/android/edx.properties deleted file mode 100644 index f49574b..0000000 --- a/tutorandroid/templates/android/build/android/edx.properties +++ /dev/null @@ -1 +0,0 @@ -edx.dir = '/openedx/config' diff --git a/tutorandroid/templates/android/build/app/Dockerfile b/tutorandroid/templates/android/build/app/Dockerfile new file mode 100644 index 0000000..c6a4d92 --- /dev/null +++ b/tutorandroid/templates/android/build/app/Dockerfile @@ -0,0 +1,19 @@ +#### Build image with app-specific settings +FROM {{ ANDROID_DOCKER_IMAGE }} as build + +# User-customized config +COPY ./config/edx.properties ./OpenEdXMobile/default_config/edx.properties +COPY ./config/tutor.yaml ./OpenEdXMobile/default_config/tutor.yaml +{% if ANDROID_ENABLE_RELEASE_MODE %} +# Add release settings +COPY ./config/gradle.properties ./gradle.properties.tutor +RUN cat ./gradle.properties.tutor >> ./gradle.properties +{% endif %} + +RUN sed -i "s/APPLICATION_ID = .*/APPLICATION_ID = \"{{ LMS_HOST|reverse_host|replace("-", "_") }}\"/g" constants.gradle +RUN ./gradlew assembleProd{{ "Release" if ANDROID_ENABLE_RELEASE_MODE else "Debuggable" }} + +#### File server to serve apk file +FROM docker.io/caddy:2.4.3-alpine as production +COPY --from=build /app/edx-app-android/OpenEdXMobile/build/outputs/apk/prod/{{ "release" if ANDROID_ENABLE_RELEASE_MODE else "debuggable" }}/edx-{{ "release" if ANDROID_ENABLE_RELEASE_MODE else "debuggable" }}-{{ ANDROID_APP_VERSION }}.apk /srv/app.apk +CMD caddy file-server --listen=:8000 --root=/srv diff --git a/tutorandroid/templates/android/apps/.gitignore b/tutorandroid/templates/android/build/app/config/.gitignore similarity index 100% rename from tutorandroid/templates/android/apps/.gitignore rename to tutorandroid/templates/android/build/app/config/.gitignore diff --git a/tutorandroid/templates/android/apps/edx.properties b/tutorandroid/templates/android/build/app/config/edx.properties similarity index 100% rename from tutorandroid/templates/android/apps/edx.properties rename to tutorandroid/templates/android/build/app/config/edx.properties diff --git a/tutorandroid/templates/android/apps/gradle.properties b/tutorandroid/templates/android/build/app/config/gradle.properties similarity index 71% rename from tutorandroid/templates/android/apps/gradle.properties rename to tutorandroid/templates/android/build/app/config/gradle.properties index 486a13a..f915656 100644 --- a/tutorandroid/templates/android/apps/gradle.properties +++ b/tutorandroid/templates/android/build/app/config/gradle.properties @@ -1,4 +1,4 @@ -RELEASE_STORE_FILE=/openedx/config/app.keystore +RELEASE_STORE_FILE=/app/edx-app-android/OpenEdXMobile/app.keystore RELEASE_STORE_PASSWORD={{ ANDROID_RELEASE_STORE_PASSWORD }} RELEASE_KEY_PASSWORD={{ ANDROID_RELEASE_KEY_PASSWORD }} RELEASE_KEY_ALIAS={{ ANDROID_RELEASE_KEY_ALIAS }} diff --git a/tutorandroid/templates/android/apps/tutor.yaml b/tutorandroid/templates/android/build/app/config/tutor.yaml similarity index 100% rename from tutorandroid/templates/android/apps/tutor.yaml rename to tutorandroid/templates/android/build/app/config/tutor.yaml