diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 0000000..8b21b73 --- /dev/null +++ b/.bumpversion.cfg @@ -0,0 +1,17 @@ +[bumpversion] +current_version = 2.0.9 +commit = False +message = service version: {current_version} → {new_version} +tag = False + +[bumpversion:file:.osparc/jupyter-math/metadata.yml] +search = {current_version} +replace = {new_version} + +[bumpversion:file:Makefile] +search = {current_version} +replace = {new_version} + +[bumpversion:file:docker-compose-local.yml] +search = {current_version} +replace = {new_version} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..0bd0700 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,9 @@ + +# Maps code in repository with maintainers +# Order is important. The last matching pattern has the most precedence. +# SEE https://docs.github.com/en/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/about-code-owners#example-of-a-codeowners-file + +* @elisabettai + +# NOTE: '/' denotes the root of the repository +/src/templates/index.html @GitHK diff --git a/.github/ISSUE_TEMPLATE/ask_question.md b/.github/ISSUE_TEMPLATE/ask_question.md new file mode 100644 index 0000000..b2265bf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ask_question.md @@ -0,0 +1,17 @@ +--- +name: 💬 Question +about: Ask a question +labels: question +assignees: elisabettai +--- + +## What version of this service are you using? + + + +## How can we help you? diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..abdd495 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,44 @@ +--- +name: 🐛 Bug +about: File a bug/issue +labels: bug +assignees: elisabettai +--- + +## What version of this service are you using? + + + +## Long story short + + + +## Expected behaviour + + + +## Actual behaviour + + + +## Steps to reproduce + + + +## Your environment + + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..888fe5f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,28 @@ +--- +name: ✨ Feature request +about: Suggest an idea to implement +labels: enhancement +assignees: elisabettai +--- + +## User Story + + + +## Example + + + + +## Definition of Done + diff --git a/.github/workflows/check-image.yml b/.github/workflows/check-image.yml new file mode 100644 index 0000000..b6bc5b3 --- /dev/null +++ b/.github/workflows/check-image.yml @@ -0,0 +1,22 @@ +name: Build and check image + +on: push + +jobs: + verify-image-build: + runs-on: ubuntu-latest + steps: + - name: Checkout repo content + uses: actions/checkout@v2 + - name: ooil version + uses: docker://itisfoundation/ci-service-integration-library:v1.0.1-dev-43 + with: + args: ooil --version + - name: Assemble docker-compose spec + uses: docker://itisfoundation/ci-service-integration-library:v1.0.1-dev-43 + with: + args: ooil compose + - name: Build all images if multiple + uses: docker://itisfoundation/ci-service-integration-library:v1.0.1-dev-43 + with: + args: docker-compose build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be71fd5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Build folders +build/ +deploy/ + +# test folders +test/ + +# .env files +.env + +# Python +__pycache__/ +.venv/ +.pytest_cache/ + +# java +*.class + +# vscode +.vscode/ + +# docker +.env +# temp folders +tmp/ +.tmp +# gitlab +*.toml + +.cache/ + +# not interested in storing this file +docker-compose.yml + +# ignore all files in validation +validation/ + +*.ignore.* \ No newline at end of file diff --git a/.osparc/voila-viewer/docker-compose.overwrite.yml b/.osparc/voila-viewer/docker-compose.overwrite.yml new file mode 100644 index 0000000..7013933 --- /dev/null +++ b/.osparc/voila-viewer/docker-compose.overwrite.yml @@ -0,0 +1,4 @@ +services: + voila-viewer: + build: + dockerfile: Dockerfile diff --git a/.osparc/voila-viewer/metadata.yml b/.osparc/voila-viewer/metadata.yml new file mode 100644 index 0000000..76c82fc --- /dev/null +++ b/.osparc/voila-viewer/metadata.yml @@ -0,0 +1,163 @@ +name: Voilà Viewer +thumbnail: https://github.com/voila-dashboards/voila/raw/main/docs/source/voila-logo.svg +description: + "Takes as input a Jupyter Notebook and it shows it using Jupyter [Voilà](https://github.com/voila-dashboards/voila)" +key: simcore/services/dynamic/voila-viewer +version: 1.0.0 +integration-version: 2.0.0 +type: dynamic +authors: + - name: Elisabetta Iavarone + email: iavarone@itis.swiss + affiliation: IT'IS Foundation +contact: iavarone@itis.swiss +inputs: + input_1: + displayOrder: 1.0 + label: input_files_1 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + fileToKeyMap: + "voila.ipynb": input_1 + input_2: + displayOrder: 2.0 + label: input_files_2 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_3: + displayOrder: 3.0 + label: input_files_3 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_4: + displayOrder: 4.0 + label: input_files_4 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_5: + displayOrder: 5.0 + label: input_files_5 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_6: + displayOrder: 6.0 + label: input_files_6 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_7: + displayOrder: 7.0 + label: input_files_7 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_8: + displayOrder: 8.0 + label: input_files_8 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_9: + displayOrder: 9.0 + label: input_files_9 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_10: + displayOrder: 10.0 + label: input_files_10 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_11: + displayOrder: 11.0 + label: input_files_11 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_12: + displayOrder: 12.0 + label: input_files_12 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_13: + displayOrder: 13.0 + label: input_files_13 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_14: + displayOrder: 14.0 + label: input_files_14 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_15: + displayOrder: 15.0 + label: input_files_15 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_16: + displayOrder: 16.0 + label: input_files_16 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_17: + displayOrder: 17.0 + label: input_files_17 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_18: + displayOrder: 18.0 + label: input_files_18 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_19: + displayOrder: 19.0 + label: input_files_19 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* + input_20: + displayOrder: 20.0 + label: input_files_20 + description: + Any input files. One or several files compressed in a zip will be + downloaded in an inputs folder. + type: data:*/* +outputs: + output_1: + displayOrder: 1.0 + label: Output files 1 + description: Output files uploaded from the outputs folder + type: data:*/* +min-visible-inputs: 2 \ No newline at end of file diff --git a/.osparc/voila-viewer/runtime.yml b/.osparc/voila-viewer/runtime.yml new file mode 100644 index 0000000..6f83724 --- /dev/null +++ b/.osparc/voila-viewer/runtime.yml @@ -0,0 +1,20 @@ +restart-policy: no-restart +settings: + - name: ports + type: int + value: 8888 + - name: constraints + type: string + value: + - node.platform.os == linux + - name: Resources + type: Resources + value: + Limits: + NanoCPUs: 4000000000 + MemoryBytes: 17179869184 +paths-mapping: + inputs_path: /home/jovyan/work/inputs + outputs_path: /home/jovyan/work/outputs + state_paths: + - /home/jovyan/work/workspace diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..70d5e82 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog + +## [1.0.0] - 2023-08-04 +- First version for testing in master + + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ba301eb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM itisfoundation/jupyter-math:2.0.9 as main +LABEL maintainer="iavarone" +USER root + +COPY --chown=$NB_UID:$NB_GID requirements.txt ${NOTEBOOK_BASE_DIR}/requirements.txt +RUN .venv/bin/pip --no-cache install pip-tools && \ + .venv/bin/pip --no-cache install -r ${NOTEBOOK_BASE_DIR}/requirements.txt + +# Copying boot scripts +COPY --chown=$NB_UID:$NB_GID docker /docker + +# Copying source code +COPY --chown=$NB_UID:$NB_GID src /src + +EXPOSE 8888 + +ENTRYPOINT [ "/bin/bash", "/docker/entrypoint.bash" ] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..05d4c6a --- /dev/null +++ b/Makefile @@ -0,0 +1,75 @@ +# minimalistic utility to test and develop locally + +SHELL = /bin/sh +.DEFAULT_GOAL := help + +export DOCKER_IMAGE_NAME ?= voila-viewer +export DOCKER_IMAGE_TAG ?= 1.0.0 + + + +# PYTHON ENVIRON --------------------------------------------------------------------------------------- +.PHONY: devenv +.venv: + @python3 --version + python3 -m venv $@ + # upgrading package managers + $@/bin/pip install --upgrade \ + pip \ + wheel \ + setuptools + +devenv: .venv ## create a python virtual environment with tools to dev, run and tests cookie-cutter + # installing extra tools + @$ "$HOME/.jupyter/custom/custom.js" < .jupyter_config.json < "/opt/conda/share/jupyter/lab/overrides.json" < /dev/null 2>&1 || \ + (echo "ERROR: You must mount '${DY_SIDECAR_PATH_INPUTS}' to deduce user and group ids" && exit 1) +stat "${DY_SIDECAR_PATH_OUTPUTS}" > /dev/null 2>&1 || \ + (echo "ERROR: You must mount '${DY_SIDECAR_PATH_OUTPUTS}' to deduce user and group ids" && exit 1) + +# NOTE: expects docker run ... -v /path/to/input/folder:${DY_SIDECAR_PATH_INPUTS} +# check input/output folders are owned by the same user +if [ "$(stat -c %u "${DY_SIDECAR_PATH_INPUTS}")" -ne "$(stat -c %u "${DY_SIDECAR_PATH_OUTPUTS}")" ] +then + echo "ERROR: '${DY_SIDECAR_PATH_INPUTS}' and '${DY_SIDECAR_PATH_OUTPUTS}' have different user id's. not allowed" && exit 1 +fi +# check input/outputfolders are owned by the same group +if [ "$(stat -c %g "${DY_SIDECAR_PATH_INPUTS}")" -ne "$(stat -c %g "${DY_SIDECAR_PATH_OUTPUTS}")" ] +then + echo "ERROR: '${DY_SIDECAR_PATH_INPUTS}' and '${DY_SIDECAR_PATH_OUTPUTS}' have different group id's. not allowed" && exit 1 +fi + +echo "listing inputs folder ${DY_SIDECAR_PATH_INPUTS}" +ls -lah "${DY_SIDECAR_PATH_INPUTS}" +echo "listing outputs folder ${DY_SIDECAR_PATH_OUTPUTS}" +ls -lah "${DY_SIDECAR_PATH_OUTPUTS}" + +echo "setting correct user id/group id..." +HOST_USERID=$(stat -c %u "${DY_SIDECAR_PATH_INPUTS}") +HOST_GROUPID=$(stat -c %g "${DY_SIDECAR_PATH_INPUTS}") +CONTAINER_GROUPNAME=$(getent group | grep "${HOST_GROUPID}" | cut --delimiter=: --fields=1 || echo "") +echo "CONTAINER_GROUPNAME='$CONTAINER_GROUPNAME'" + +if [ "$HOST_USERID" -eq 0 ] +then + echo "Warning: Folder mounted owned by root user... adding $NB_USER to root..." + addgroup "$NB_USER" root +else + echo "Folder mounted owned by user $HOST_USERID:$HOST_GROUPID-'$CONTAINER_GROUPNAME'..." + # take host's credentials in $NB_USER + if [ -z "$CONTAINER_GROUPNAME" ] + then + echo "Creating new group my$NB_USER" + CONTAINER_GROUPNAME=my$NB_USER + addgroup --gid "$HOST_GROUPID" "$CONTAINER_GROUPNAME" + else + echo "group already exists" + fi + + echo "adding $NB_USER to group $CONTAINER_GROUPNAME..." + usermod --append --groups "$CONTAINER_GROUPNAME" "$NB_USER" + + echo "Chainging owner ship of state directory /home/${NB_USER}/work/workspace" + chown --recursive "$NB_USER" "/home/${NB_USER}/work/workspace" + echo "Chainging owner ship of state directory ${DY_SIDECAR_PATH_INPUTS}" + chown --recursive "$NB_USER" "${DY_SIDECAR_PATH_INPUTS}" + echo "Chainging owner ship of state directory ${DY_SIDECAR_PATH_OUTPUTS}" + chown --recursive "$NB_USER" "${DY_SIDECAR_PATH_OUTPUTS}" +fi + +echo "Removing write permissions from users in placed where they are not allowed to write:" +echo "- /home/${NB_USER}/work" +chmod gu-w "/home/${NB_USER}/work" + +echo +echo "$INFO" "Starting notebook ..." +exec gosu "$NB_USER" /docker/boot_notebook.bash diff --git a/requirements.in b/requirements.in new file mode 100644 index 0000000..46c78ce --- /dev/null +++ b/requirements.in @@ -0,0 +1,2 @@ +flask +dash \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..208fe84 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,38 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --output-file=requirements.txt requirements.in +# +blinker==1.6.2 + # via flask +click==8.1.6 + # via flask +dash==2.9.3 + # via -r requirements.in +dash-core-components==2.0.0 + # via dash +dash-html-components==2.0.0 + # via dash +dash-table==5.0.0 + # via dash +flask==2.3.2 + # via + # -r requirements.in + # dash +itsdangerous==2.1.2 + # via flask +jinja2==3.1.2 + # via flask +markupsafe==2.1.3 + # via + # jinja2 + # werkzeug +packaging==23.1 + # via plotly +plotly==5.15.0 + # via dash +tenacity==8.2.2 + # via plotly +werkzeug==2.3.6 + # via flask diff --git a/src/templates/index.html b/src/templates/index.html new file mode 100644 index 0000000..8b55b77 --- /dev/null +++ b/src/templates/index.html @@ -0,0 +1,48 @@ + + + + + Auto-Reload on Content Change + + + +

Waiting for Jupyter notebook...

+

Add a voila.ipynb file in input 1.

If voila.ipynb is not rendered after connecting input 1, reload the Service UI

+ + + + + + + + \ No newline at end of file diff --git a/src/webserver_input_monitor.py b/src/webserver_input_monitor.py new file mode 100644 index 0000000..ec7d2c9 --- /dev/null +++ b/src/webserver_input_monitor.py @@ -0,0 +1,26 @@ +import flask, requests, multiprocessing +from flask import render_template +from time import sleep +from uuid import uuid4 +import os +from pathlib import Path + +app = flask.Flask(__name__) + +UNIQUE_TOKEN = "_".join([f"{uuid4()}" for x in range(10)]) + +@app.route("/") +def index(): + unique_tracking_token = UNIQUE_TOKEN + return render_template("index.html", unique_tracking_token=unique_tracking_token) + +server = multiprocessing.Process(target=app.run, args=("0.0.0.0", 8888)) + +server.start() + +input_nb = Path(os.environ["DY_SIDECAR_PATH_INPUTS"]).joinpath("input_1/voila.ipynb") +while not os.path.exists(input_nb): + sleep(0.1) + +server.terminate() +server.join()