diff --git a/.github/workflows/env-build.yaml b/.github/workflows/env-build.yaml index 7b9b6dd1..83980fa7 100644 --- a/.github/workflows/env-build.yaml +++ b/.github/workflows/env-build.yaml @@ -3,7 +3,7 @@ name: Docker automatic build and publish on: push: branches: - - main + - upgrade-hub-singleuser env: REGISTRY: ghcr.io diff --git a/vre-singleuser-dev/Dockerfile b/vre-singleuser-dev/Dockerfile new file mode 100644 index 00000000..26f1ffe3 --- /dev/null +++ b/vre-singleuser-dev/Dockerfile @@ -0,0 +1,103 @@ +FROM quay.io/jupyter/scipy-notebook:python-3.11.8 +LABEL author="Image based on the work by Muhammad Aditya Hilmy - ESCAPE WP2 2020" +LABEL maintainer="VRE Team @ CERN 23/24 - E. Garcia, G. Guerrieri" +LABEL org.opencontainers.image.source https://github.com/vre-hub/environments +ARG BUILD_DATE +LABEL org.label-schema.build-date=$BUILD_DATE + +RUN python -m pip install --upgrade pip + +USER $NB_UID + +RUN conda install -y -n base mamba \ + && mamba install -y -c conda-forge python-gfal2 \ + nodejs \ + jupyterlab">4,<5" \ + notebook"<7" \ + jupyterhub \ + jupyterhub-kubespawner \ + jsonschema>4 \ + jupyterlab_server \ + jupyter_server \ + traitlets \ + nbformat \ + ipykernel \ + PyJWT \ + ipywidgets \ + && conda clean --all -f -yft + +# Install jupyterlab extensions +RUN python -m pip install rucio-jupyterlab==1.0.0 \ + && jupyter server extension enable --py rucio_jupyterlab --sys-prefix + +RUN python -m pip install swanoauthrenew==1.0.1 \ + && jupyter server extension enable --py swanoauthrenew --sys-prefix + +RUN python -m pip install reana-jupyterlab \ + && jupyter server extension enable --py reana_jupyterlab --sys-prefix + +RUN git clone https://github.com/vre-hub/zenodo-jupyterlab-extension.git \ + && cd zenodo-jupyterlab-extension \ + && python -m pip install . \ + && jupyter server extension enable --py zenodo_jupyterlab.server --sys-prefix \ + && cd .. \ + && rm -rf zenodo-jupyterlab-extension + +# Custom version of reana-client due to the jsonschema problem +RUN python -m pip install git+https://github.com/mdonadoni/reana-client.git@vre-summer-24 + +USER root + +RUN apt update -y \ + && apt install -y build-essential curl voms-clients-java software-properties-common \ + && apt clean -y \ + && rm /opt/conda/bin/voms-proxy-init \ + && ln -s /usr/bin/voms-proxy-init /opt/conda/bin/voms-proxy-init + +# ESCAPE grid-security +RUN wget -q -O - https://dist.eugridpma.info/distribution/igtf/current/GPG-KEY-EUGridPMA-RPM-3 | apt-key add - + +RUN apt update \ + && add-apt-repository 'deb http://repository.egi.eu/sw/production/cas/1/current egi-igtf core' \ + && apt -y install ca-policy-egi-core + +# VOMS setup +# wget https://indigo-iam.github.io/escape-docs/voms-config/voms-escape.cloud.cnaf.infn.it.vomses -O /etc/vomses/voms-escape.cloud.cnaf.infn.it.vomses +# wget https://indigo-iam.github.io/escape-docs/voms-config/voms-escape.cloud.cnaf.infn.it.lsc -O /etc/grid-security/vomsdir/escape/voms-escape.cloud.cnaf.infn.it.lsc +RUN mkdir -p /etc/vomses \ + && mkdir -p /etc/grid-security/vomsdir/escape \ + && mkdir -p /etc/grid-security/vomsdir/atlas \ + && mkdir -p /etc/grid-security/vomsdir/cms +COPY voms/vomsdir/escape/* /etc/grid-security/vomsdir/escape +COPY voms/vomsdir/atlas/* /etc/grid-security/vomsdir/atlas +COPY voms/vomsdir/cms/* /etc/grid-security/vomsdir/cms +COPY voms/vomses/* /etc/vomses + +# Setup merged CERN CA file on Ubuntu based images. +# This file is contained in the `CERN-bundle.pem` file downloaded using +RUN mkdir /certs \ + && touch /certs/rucio_ca.pem \ + && curl -fsSL 'https://cafiles.cern.ch/cafiles/certificates/CERN%20Root%20Certification%20Authority%202.crt' | openssl x509 -inform DER -out /tmp/cernrootca2.crt \ + && curl -fsSL 'https://cafiles.cern.ch/cafiles/certificates/CERN%20Grid%20Certification%20Authority(1).crt' -o /tmp/cerngridca.crt \ + && curl -fsSL 'https://cafiles.cern.ch/cafiles/certificates/CERN%20Certification%20Authority.crt' -o /tmp/cernca.crt \ + && cat /tmp/cernrootca2.crt >> /certs/rucio_ca.pem \ + && cat /tmp/cerngridca.crt >> /certs/rucio_ca.pem \ + && cat /tmp/cernca.crt >> /certs/rucio_ca.pem \ + && rm /tmp/*.crt \ + && update-ca-certificates + +# Setup extension Rucio instance config +COPY configure-vre.py /usr/local/bin/ +RUN chmod +x /usr/local/bin/configure-vre.py +COPY configure-vre.sh /usr/local/bin/before-notebook.d/ + +RUN mkdir -p /opt/rucio/etc \ + && chown -R $NB_UID /opt/rucio/etc + +ENV JUPYTER_ENABLE_LAB=yes +ENV JUPYTERHUB_SINGLEUSER_APP=jupyter-server + +WORKDIR $HOME +USER $NB_UID + +CMD ["start-notebook.py"] \ No newline at end of file diff --git a/vre-singleuser-dev/README.md b/vre-singleuser-dev/README.md new file mode 100644 index 00000000..d06d92da --- /dev/null +++ b/vre-singleuser-dev/README.md @@ -0,0 +1,41 @@ +# VRE singleuser BASE python 3.11 iamge + +This image based on the original [ESCAPE Data Lake-as-a-Service Singleuser Base Image](https://gitlab.cern.ch/escape-wp2/docker-images/-/tree/master/datalake-singleuser) (restricted acces) which is derived from [quay.io/jupyter/scipy-notebook](https://quay.io/jupyter/scipy-notebook)($\ast$) with some modifications: + +1. Python version is `3.11.8`. +2. The followinf JupyterLab extensions are installed: + * [rucio-jupyterlab](https://pypi.org/project/rucio-jupyterlab) v1.0.0. + * The configuration for the Rucio JupyterLab extension is preset to connect with the ESCAPE Data Lake. + * [reana-jupyterlab](https://github.com/vre-hub/reana-jupyterlab-extension) v1.0.0. + * [zenodo-jupyterlab](https://github.com/vre-hub/zenodo-jupyterlab-extension). + * [swanoauthrenew](https://pypi.org/project/swanoauthrenew/) v1.0.1 to enable renewal of tokens. +3. The image is compatible with [rucio-clients](https://pypi.org/project/rucio-clients) `release-34.6.0` - please report any possible bug or error found. +4. ESCAPE `ca-certificates` and [VOMSes](https://indigo-iam.github.io/escape-docs/) files are preinstalled. +5. [python-gfal2](https://anaconda.org/conda-forge/python-gfal2) and `voms-clients-java` is installed to enable direct Rucio Download functionality. + +($\ast$) Please note that since Oct 2023, Jupyter stopped pushing images to Docker Hub and start using quay.io + +## Using the image as a standalone instance + +If you want to take advantage of the extension's capability in your own machine, you can do a simple Docker run: + +```sh +docker run -p 8888:8888 docker pull ghcr.io/vre-hub/vre-singleuser-py311:latest +``` + +You can also override the default environment values (some of which are specific to the ESCAPE instance, such as the service URLs) specified in the [Dockerfile](Dockerfile) within the singleuser extraEnv specification inside the [zero-to-jupyterhub Helm release](https://github.com/vre-hub/vre/tree/main/infrastructure/cluster/flux) of your deployment. Take a closer look at [configure-vre.py](configure-vre.py) to see all the available envs. Refer to [this configuration guide](https://github.com/rucio/jupyterlab-extension/blob/master/CONFIGURATION.md) for details. + +## Extending the image + +If you want to extend the image and use it as a part of the VRE, there are some things to remember: + +1. To custimize the image please have a look to the [Startup Docker Stack documentation Hooks](https://jupyter-docker-stacks.readthedocs.io/en/latest/using/common.html#startup-hooks). +2. If you need to add a file, make sure that the container user has the necessary permission to access the file. You can use envs `$NB_UID`, `$NB_GID`, `$NB_USER`, and `$HOME` to get the user ID, group ID, username, and home directory respectively. +3. Make sure that the user and workdir values are set to their original values: + +``` +WORKDIR $HOME +USER $NB_UID +``` + +Based on all the previous work done by the ESCAPE Data Lake-as-a-Service group since 2020. diff --git a/vre-singleuser-dev/configure-vre.py b/vre-singleuser-dev/configure-vre.py new file mode 100755 index 00000000..ff379f59 --- /dev/null +++ b/vre-singleuser-dev/configure-vre.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +# Derived from https://gitlab.cern.ch/escape-wp2/docker-images/-/blob/master/datalake-singleuser/bin/configure.py (restricted access). + +import os +import json + +HOME = '/home/jovyan' + +def write_jupyterlab_config(): + file_path = HOME + '/.jupyter/jupyter_server_config.json' + if not os.path.isfile(file_path): + os.makedirs(HOME + '/.jupyter/', exist_ok=True) + else: + config_file = open(file_path, 'r') + config_payload = config_file.read() + config_file.close() + + try: + config_json = json.loads(config_payload) + except: + config_json = {} + +# Looking to the rucio-jupyterlab configuration; https://github.com/rucio/jupyterlab-extension/blob/master/rucio_jupyterlab/config/schema.py#L101 +# either ("destination_rse", "rse_mount_path") either ("rucio_ca_cert") are required env vars, even if they are defined in the jhub manifest. +# Adding 'rucio_base_url' too - from debugging experience + + instance_config = { + "name": os.getenv('RUCIO_NAME', 'default'), + "display_name": os.getenv('RUCIO_DISPLAY_NAME', 'Default Instance'), + "rucio_base_url": os.getenv('RUCIO_BASE_URL', 'DEFAULT rucio base url'), + "rucio_auth_url": os.getenv('RUCIO_AUTH_URL'), + "rucio_webui_url": os.getenv('RUCIO_WEBUI_URL'), + "rucio_ca_cert": os.getenv('RUCIO_CA_CERT'), + "site_name": os.getenv('RUCIO_SITE_NAME'), + "vo": os.getenv('RUCIO_VO'), + "voms_enabled": os.getenv('RUCIO_VOMS_ENABLED', '0') == '1', + "voms_vomses_path": os.getenv('RUCIO_VOMS_VOMSES_PATH'), + "voms_certdir_path": os.getenv('RUCIO_VOMS_CERTDIR_PATH'), + "voms_vomsdir_path": os.getenv('RUCIO_VOMS_VOMSDIR_PATH'), + "destination_rse": os.getenv('RUCIO_DESTINATION_RSE', 'DEFAULT rse destination'), + "rse_mount_path": os.getenv('RUCIO_RSE_MOUNT_PATH', 'DEFAULT rse mount path'), + "replication_rule_lifetime_days": int(os.getenv('RUCIO_REPLICATION_RULE_LIFETIME_DAYS')) if os.getenv('RUCIO_REPLICATION_RULE_LIFETIME_DAYS') else None, + "path_begins_at": int(os.getenv('RUCIO_PATH_BEGINS_AT', '0')), + "mode": os.getenv('RUCIO_MODE', 'replica'), + "wildcard_enabled": os.getenv('RUCIO_WILDCARD_ENABLED', '0') == '1', + "oidc_auth": os.getenv('RUCIO_OIDC_AUTH'), + "oidc_env_name": os.getenv('RUCIO_OIDC_ENV_NAME'), + "oidc_file_name": os.getenv('RUCIO_OIDC_FILE_NAME'), + } + + instance_config = {k: v for k, + v in instance_config.items() if v is not None} + config_json['RucioConfig'] = { + 'instances': [instance_config], + "default_instance": os.getenv('RUCIO_DEFAULT_INSTANCE'), + "default_auth_type": os.getenv('RUCIO_DEFAULT_AUTH_TYPE'), + } + + config_file = open(file_path, 'w') + config_file.write(json.dumps(config_json, indent=2)) + config_file.close() + +def write_ipython_config(): + file_path = HOME + '/.ipython/profile_default/ipython_kernel_config.json' + extension_module = 'rucio_jupyterlab.kernels.ipython' + + if not os.path.isfile(file_path): + os.makedirs(HOME + '/.ipython/profile_default/', exist_ok=True) + else: + config_file = open(file_path, 'r') + config_payload = config_file.read() + config_file.close() + + try: + config_json = json.loads(config_payload) + except: + config_json = {} + + if 'IPKernelApp' not in config_json: + config_json['IPKernelApp'] = {} + + ipkernel_app = config_json['IPKernelApp'] + + if 'extensions' not in ipkernel_app: + ipkernel_app['extensions'] = [] + + if extension_module not in ipkernel_app['extensions']: + ipkernel_app['extensions'].append(extension_module) + + config_file = open(file_path, 'w') + config_file.write(json.dumps(config_json, indent=2)) + config_file.close() + +if __name__ == '__main__': + write_jupyterlab_config() + write_ipython_config() \ No newline at end of file diff --git a/vre-singleuser-dev/configure-vre.sh b/vre-singleuser-dev/configure-vre.sh new file mode 100644 index 00000000..d5250679 --- /dev/null +++ b/vre-singleuser-dev/configure-vre.sh @@ -0,0 +1,3 @@ +#!/bin/bash +set -e +python /usr/local/bin/configure-vre.py \ No newline at end of file diff --git a/vre-singleuser-dev/voms/vomsdir/atlas/voms-atlas-auth.app.cern.ch.lsc b/vre-singleuser-dev/voms/vomsdir/atlas/voms-atlas-auth.app.cern.ch.lsc new file mode 100644 index 00000000..34f79aa1 --- /dev/null +++ b/vre-singleuser-dev/voms/vomsdir/atlas/voms-atlas-auth.app.cern.ch.lsc @@ -0,0 +1,2 @@ +/DC=ch/DC=cern/OU=computers/CN=atlas-auth.web.cern.ch +/DC=ch/DC=cern/CN=CERN Grid Certification Authority diff --git a/vre-singleuser-dev/voms/vomsdir/atlas/voms-atlas-auth.cern.ch.lsc b/vre-singleuser-dev/voms/vomsdir/atlas/voms-atlas-auth.cern.ch.lsc new file mode 100644 index 00000000..dfcbb462 --- /dev/null +++ b/vre-singleuser-dev/voms/vomsdir/atlas/voms-atlas-auth.cern.ch.lsc @@ -0,0 +1,2 @@ +/DC=ch/DC=cern/OU=computers/CN=atlas-auth.cern.ch +/DC=ch/DC=cern/CN=CERN Grid Certification Authority diff --git a/vre-singleuser-dev/voms/vomsdir/cms/voms-cms-auth.app.cern.ch.lsc b/vre-singleuser-dev/voms/vomsdir/cms/voms-cms-auth.app.cern.ch.lsc new file mode 100644 index 00000000..d1e34aaa --- /dev/null +++ b/vre-singleuser-dev/voms/vomsdir/cms/voms-cms-auth.app.cern.ch.lsc @@ -0,0 +1,2 @@ +/DC=ch/DC=cern/OU=computers/CN=cms-auth.web.cern.ch +/DC=ch/DC=cern/CN=CERN Grid Certification Authority diff --git a/vre-singleuser-dev/voms/vomsdir/cms/voms-cms-auth.cern.ch.lsc b/vre-singleuser-dev/voms/vomsdir/cms/voms-cms-auth.cern.ch.lsc new file mode 100644 index 00000000..e09ccce5 --- /dev/null +++ b/vre-singleuser-dev/voms/vomsdir/cms/voms-cms-auth.cern.ch.lsc @@ -0,0 +1,2 @@ +/DC=ch/DC=cern/OU=computers/CN=cms-auth.cern.ch +/DC=ch/DC=cern/CN=CERN Grid Certification Authority diff --git a/vre-singleuser-dev/voms/vomsdir/escape/voms-escape.cloud.cnaf.infn.it.lsc b/vre-singleuser-dev/voms/vomsdir/escape/voms-escape.cloud.cnaf.infn.it.lsc new file mode 100644 index 00000000..e6807ebf --- /dev/null +++ b/vre-singleuser-dev/voms/vomsdir/escape/voms-escape.cloud.cnaf.infn.it.lsc @@ -0,0 +1,2 @@ +/DC=org/DC=terena/DC=tcs/C=IT/ST=Roma/O=Istituto Nazionale di Fisica Nucleare/CN=voms-escape.cloud.cnaf.infn.it +/C=NL/O=GEANT Vereniging/CN=GEANT eScience SSL CA 4 diff --git a/vre-singleuser-dev/voms/vomses/atlas-voms-atlas-auth.app.cern.ch b/vre-singleuser-dev/voms/vomses/atlas-voms-atlas-auth.app.cern.ch new file mode 100644 index 00000000..2db0393f --- /dev/null +++ b/vre-singleuser-dev/voms/vomses/atlas-voms-atlas-auth.app.cern.ch @@ -0,0 +1 @@ +"atlas" "voms-atlas-auth.app.cern.ch" "443" "/DC=ch/DC=cern/OU=computers/CN=atlas-auth.web.cern.ch" "atlas" "24" diff --git a/vre-singleuser-dev/voms/vomses/cms-voms-cms-auth.app.cern.ch b/vre-singleuser-dev/voms/vomses/cms-voms-cms-auth.app.cern.ch new file mode 100644 index 00000000..c9e3ccd0 --- /dev/null +++ b/vre-singleuser-dev/voms/vomses/cms-voms-cms-auth.app.cern.ch @@ -0,0 +1 @@ +"cms" "voms-cms-auth.app.cern.ch" "443" "/DC=ch/DC=cern/OU=computers/CN=cms-auth.web.cern.ch" "cms" "24" diff --git a/vre-singleuser-dev/voms/vomses/escape-voms-escape.cloud.cnaf.infn.it b/vre-singleuser-dev/voms/vomses/escape-voms-escape.cloud.cnaf.infn.it new file mode 100644 index 00000000..ad6ac6ca --- /dev/null +++ b/vre-singleuser-dev/voms/vomses/escape-voms-escape.cloud.cnaf.infn.it @@ -0,0 +1 @@ +"escape" "voms-escape.cloud.cnaf.infn.it" "15000" "/DC=org/DC=terena/DC=tcs/C=IT/ST=Roma/O=Istituto Nazionale di Fisica Nucleare/CN=voms-escape.cloud.cnaf.infn.it" "escape" "24"