From dff38bfd8dc5ecf0c4875f97d4e4a914cd64ed7c Mon Sep 17 00:00:00 2001 From: parham-membari-terradue Date: Tue, 4 Mar 2025 11:22:16 +0100 Subject: [PATCH 01/86] reformat: edit structure --- .../{apphub-configurator => }/LICENSE.txt | 0 config-generator/README.md | 34 +- .../README.md => README2.md} | 0 .../examples/generate_config.py | 157 ---- .../config-generator-eoepca-demo.ipynb | 710 ------------------ config-generator/config-generator.ipynb | 573 -------------- .../config-generator-eoepca-demo.ipynb | 32 +- .../examples/config-generator.ipynb | 23 +- .../examples/generate-config.sh | 0 .../{ => examples}/generate_config.py | 2 +- config-generator/generate-config.sh | 4 - config-generator/helpers.py | 62 -- config-generator/models.py | 157 ---- .../{apphub-configurator => }/pyproject.toml | 23 +- .../src/apphub_configurator/__about__.py | 0 .../src/apphub_configurator/__init__.py | 0 .../src/apphub_configurator/helpers.py | 0 .../src/apphub_configurator/models.py | 0 .../tests/__init__.py | 0 19 files changed, 74 insertions(+), 1703 deletions(-) rename config-generator/{apphub-configurator => }/LICENSE.txt (100%) rename config-generator/{apphub-configurator/README.md => README2.md} (100%) delete mode 100644 config-generator/apphub-configurator/examples/generate_config.py delete mode 100644 config-generator/config-generator-eoepca-demo.ipynb delete mode 100644 config-generator/config-generator.ipynb rename config-generator/{apphub-configurator => }/examples/config-generator-eoepca-demo.ipynb (96%) rename config-generator/{apphub-configurator => }/examples/config-generator.ipynb (96%) rename config-generator/{apphub-configurator => }/examples/generate-config.sh (100%) rename config-generator/{ => examples}/generate_config.py (99%) delete mode 100755 config-generator/generate-config.sh delete mode 100644 config-generator/helpers.py delete mode 100644 config-generator/models.py rename config-generator/{apphub-configurator => }/pyproject.toml (68%) rename config-generator/{apphub-configurator => }/src/apphub_configurator/__about__.py (100%) rename config-generator/{apphub-configurator => }/src/apphub_configurator/__init__.py (100%) rename config-generator/{apphub-configurator => }/src/apphub_configurator/helpers.py (100%) rename config-generator/{apphub-configurator => }/src/apphub_configurator/models.py (100%) rename config-generator/{apphub-configurator => }/tests/__init__.py (100%) diff --git a/config-generator/apphub-configurator/LICENSE.txt b/config-generator/LICENSE.txt similarity index 100% rename from config-generator/apphub-configurator/LICENSE.txt rename to config-generator/LICENSE.txt diff --git a/config-generator/README.md b/config-generator/README.md index 8a4a3c1..f11268d 100644 --- a/config-generator/README.md +++ b/config-generator/README.md @@ -1,5 +1,35 @@ ## ApplicationHub configuration generator -This folder contains a notebook and the python modules to support the generation of ApplicationHub configurations. -Create a Python environment with the dependencies listed in the file `requirements.txt` and open the notebook `config-generator.ipynb` \ No newline at end of file + + + + +# apphub-configurator + +[![PyPI - Version](https://img.shields.io/pypi/v/apphub-configurator.svg)](https://pypi.org/project/apphub-configurator) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/apphub-configurator.svg)](https://pypi.org/project/apphub-configurator) + +----- + +## Table of Contents + +- [Installation](#installation) +- [Overview](#overview) +- [Examples](#Examples) +- [License](#license) + +## Installation +Create a hatch environment with the dependencies listed in the file `pyproject.toml` and open the notebook [config-generator.ipynb](examples/config-generator.ipynb) + +```console +pip install apphub-configurator +``` +## Overview +This package contains a notebook and the python modules to support the generation of ApplicationHub configurations for a minikube cluster. For more information about ApplicationHub please check this [link](https://github.com/EOEPCA/application-hub-context) + +## Examples: +The [example](https://github.com/EOEPCA/application-hub-context/config-generator/apphub-configurator/examples) folder contains a notebook and the python modules to support the generation of ApplicationHub configurations. +## License + +`apphub-configurator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. diff --git a/config-generator/apphub-configurator/README.md b/config-generator/README2.md similarity index 100% rename from config-generator/apphub-configurator/README.md rename to config-generator/README2.md diff --git a/config-generator/apphub-configurator/examples/generate_config.py b/config-generator/apphub-configurator/examples/generate_config.py deleted file mode 100644 index 4847780..0000000 --- a/config-generator/apphub-configurator/examples/generate_config.py +++ /dev/null @@ -1,157 +0,0 @@ -from models import * -import yaml -import os -from loguru import logger -from helpers import ( - load_config_map, - load_manifests, - create_init_container, - load_init_script, -) -import click - -storage_class_rwo = "standard" -storage_class_rwx = "standard" -profiles = [] -workspace_volume_size = "50Gi" -calrissian_volume_size = "50Gi" -image = "eoepca/pde-code-server:develop" -node_selector = {} - -# get the current directory -current_dir = os.path.dirname(os.path.realpath(__file__)) - -# load the manifests - -# localstack_manifest -localstack_manifest = load_manifests( - name="localstack", - key="localstack", - file_path=os.path.join(current_dir, "manifests/manifest.yaml"), -) - -# Dask Gateway manifest -dask_gateway_manifest = load_manifests( - name="dask-gateway", - key="dask-gateway", - file_path=os.path.join(current_dir, "manifests/dask-gateway.yaml"), -) - -kaniko_manifest = load_manifests( - name="kaniko", - key="kaniko", - file_path=os.path.join(current_dir, "manifests/kaniko.yaml"), -) -# volumes - -workspace_volume = Volume( - name="workspace-volume", - size=workspace_volume_size, - claim_name="workspace-claim", - mount_path="/workspace", - storage_class=storage_class_rwo, - access_modes=["ReadWriteOnce"], - volume_mount=VolumeMount(name="workspace-volume", mount_path="/workspace"), - persist=True, -) - -calrissian_volume = Volume( - name="calrissian-volume", - claim_name="calrissian-claim", - size=calrissian_volume_size, - storage_class=storage_class_rwx, - access_modes=["ReadWriteMany"], - volume_mount=VolumeMount(name="calrissian-volume", mount_path="/calrissian"), - persist=False, -) - - -bash_login_cm = load_config_map( - name="bash-login", - key="bash-login", - file_name=os.path.join(current_dir, "config-maps/bash-login"), - mount_path="/etc/profile.d/bash-login.sh", -) - -bash_rc_cm = load_config_map( - name="bash-rc", - key="bash-rc", - file_name=os.path.join(current_dir, "config-maps/bash-rc"), - mount_path="/workspace/.bashrc", -) - -init_cm = load_init_script(os.path.join(current_dir, "config-maps/init.sh")) - -init_container = create_init_container( - image=image, - volume=workspace_volume, - mount_path="/calrissian", -) - -profile_1 = Profile( - id=f"profile_1", - groups=["group-a", "group-b"], - definition=ProfileDefinition( - display_name="Coder demo init script", - description="This profile is used to demonstrate the use of an init script", - slug="profile_1", - default=False, - kubespawner_override=KubespawnerOverride( - cpu_guarantee=1, - cpu_limit=2, - mem_guarantee="4G", - mem_limit="6G", - image=image, - ), - ), - node_selector=node_selector, - volumes=[calrissian_volume, workspace_volume], - config_maps=[ - init_cm, - ConfigMap( - name="dask-gateway-config", - key="gateway", - mount_path="/etc/dask/gateway.yaml", - readonly=True, - ), - ConfigMap( - name="dask-gateway-config-ws", - key="gateway", - mount_path="/workspace/.config/dask/gateway.yaml", - readonly=True, - ), - bash_login_cm, - bash_rc_cm, - ], - pod_env_vars={ - "HOME": "/workspace", - "CONDA_ENVS_PATH": "/workspace/.envs", - "CONDARC": "/workspace/.condarc", - "XDG_RUNTIME_DIR": "/workspace/.local", - "CODE_SERVER_WS": "/workspace/mastering-app-package", - "DASK_GATEWAY": "http://traefik-dask-gw-jupyter-{{ spawner.user.name }}-dask-gateway.jupyter-{{ spawner.user.name }}.svc.cluster.local:80", - }, - init_containers=[init_container], - manifests=[localstack_manifest, dask_gateway_manifest, kaniko_manifest], - env_from_config_maps=["my-config"], - env_from_secrets=["my-secret", "data-by-name"], - secret_mounts=[ - SecretMount( - name="aws-credentials-{{ spawner.user.name }}", mount_path="/workspace/.aws" - ), - SecretMount(name="data-by-name", mount_path="/workspace/.data-by-name"), - SecretMount( - name="eoepca-plus-secret-ro", - mount_path="/workspace/.docker/config.json", - sub_path=".dockerconfigjson", - ), - ], - image_pull_secrets=[ImagePullSecret(name="eoepca-plus-secret-ro")], -) - -profiles.append(profile_1) - -config = Config(profiles=profiles) - -with open("files/hub/config.yml", "w") as file: - yaml.dump(config.dict(), file, width=200) diff --git a/config-generator/config-generator-eoepca-demo.ipynb b/config-generator/config-generator-eoepca-demo.ipynb deleted file mode 100644 index 57905c8..0000000 --- a/config-generator/config-generator-eoepca-demo.ipynb +++ /dev/null @@ -1,710 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import yaml\n", - "from models import *\n", - "import os" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Configuration\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "storage_class_rwo = \"managed-nfs-storage\"\n", - "storage_class_rwx = \"managed-nfs-storage\"\n", - "\n", - "workspace_volume_size = \"50Gi\"\n", - "calrissian_volume_size = \"50Gi\"\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Volumes\n", - "\n", - "Create the Volumes" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Workspace Volume\n", - "\n", - "The workspace volume is persisted." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "workspace_volume = Volume(\n", - " name=\"workspace-volume\",\n", - " size=workspace_volume_size,\n", - " claim_name=\"workspace-claim\",\n", - " mount_path=\"/workspace\",\n", - " storage_class=storage_class_rwo,\n", - " access_modes=[\"ReadWriteOnce\"],\n", - " volume_mount=VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"),\n", - " persist=False,\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Calrissian Volume\n", - "\n", - "This is a RWX volume, not persisted" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "calrissian_volume = Volume(\n", - " name=\"calrissian-volume\",\n", - " claim_name=\"calrissian-claim\",\n", - " size=calrissian_volume_size,\n", - " storage_class=storage_class_rwx,\n", - " access_modes=[\"ReadWriteMany\"],\n", - " volume_mount=VolumeMount(name=\"calrissian-volume\", mount_path=\"/calrissian\"),\n", - " persist=False,\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ConfigMaps\n", - "\n", - "These configmaps are mounted as files on the pod." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### bash login\n", - "\n", - "This file is used for the JupyterLab Terminal configuration" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "with open(\"config-maps/bash-login\", \"r\") as f:\n", - " content = f.read()\n", - "\n", - "bash_login_cm = ConfigMap(\n", - " name=\"bash-login\",\n", - " key=\"bash-login\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/workspace/.bash_login\",\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### bash.rc\n" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "with open(\"config-maps/bash-rc\", \"r\") as f:\n", - " content = f.read()\n", - "bash_rc_cm = ConfigMap(\n", - " name=\"bash-rc\",\n", - " key=\"bash-rc\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/workspace/.bashrc\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## bash login\n" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "with open(\"config-maps/bash-login\", \"r\") as f:\n", - " content = f.read()\n", - "\n", - "bash_login_cm = ConfigMap(\n", - " name=\"bash-login\",\n", - " key=\"bash-login\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/workspace/.bash_login\",\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Profiles" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "profiles = []" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Coder" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "coders = {\n", - " \"coder1\": {\n", - " \"display_name\": \"Code Server Small\",\n", - " \"slug\": \"ellip_studio_coder_slug_s\",\n", - " \"cpu_limit\": 2,\n", - " \"mem_limit\": \"8G\",\n", - " },\n", - " \"coder2\": {\n", - " \"display_name\": \"Code Server Medium\",\n", - " \"slug\": \"ellip_studio_coder_slug_m\",\n", - " \"cpu_limit\": 4,\n", - " \"mem_limit\": \"12G\",\n", - " },\n", - "}\n", - "\n", - "for key, value in coders.items():\n", - " coder_definition = ProfileDefinition(\n", - " display_name=value[\"display_name\"],\n", - " slug=value[\"slug\"],\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_limit=value[\"cpu_limit\"],\n", - " mem_limit=value[\"mem_limit\"],\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " ),\n", - " )\n", - "\n", - " coder_profile = Profile(\n", - " id=f\"profile_studio_{key}\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=coder_definition,\n", - " node_selector={},\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[\n", - " bash_rc_cm,\n", - " ],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " },\n", - " )\n", - "\n", - " profiles.append(coder_profile)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## init.sh script" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "with open(\"./config-maps/init.sh\", \"r\") as f:\n", - " content = f.read()\n", - "\n", - "init_cm = ConfigMap(\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - " default_mode=\"0660\",\n", - ")\n", - "\n", - "\n", - "init_context_volume_mount = InitContainerVolumeMount(\n", - " mount_path=\"/opt/init/.init.sh\", name=\"init\", sub_path=\"init\"\n", - ")\n", - "init_container = InitContainer(\n", - " name=\"init-file-on-volume\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " command=[\"sh\", \"-c\", \"sh /opt/init/.init.sh\"],\n", - " volume_mounts=[\n", - " VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"),\n", - " init_context_volume_mount,\n", - " ],\n", - ")\n", - "\n", - "eoepca_demo_init_script_profile = Profile(\n", - " id=f\"profile_demo_init_script\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Coder demo init script\",\n", - " description=\"This profile is used to demonstrate the use of an init script\",\n", - " slug=\"eoepca_demo_init_script\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[init_cm],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " \"CONDARC\": \"/workspace/.condarc\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"CODE_SERVER_WS\": \"/workspace/mastering-app-package\",\n", - " },\n", - " init_containers=[init_container],\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "profiles.append(eoepca_demo_init_script_profile)\n", - "\n", - "eoepca_demo_init_script_profile = Profile(\n", - " id=f\"profile_demo_init_script\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Coder demo init script\",\n", - " description=\"This profile is used to demonstrate the use of an init script\",\n", - " slug=\"eoepca_demo_init_script\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[init_cm],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " \"CONDARC\": \"/workspace/.condarc\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"CODE_SERVER_WS\": \"/workspace/mastering-app-package\",\n", - " },\n", - " init_containers=[init_container],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## JupyterLab" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "image = \"jupyter/scipy-notebook\"\n", - "\n", - "\n", - "eoepca_jupyter_lab_profile = Profile(\n", - " id=\"profile_jupyter_lab\",\n", - " groups=[\"group-c\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Jupyter Lab\",\n", - " description=\"Jupyter Lab with Python 3.11\",\n", - " slug=\"eoepca_jupyter_lab\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[workspace_volume],\n", - " config_maps=[],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", - " },\n", - ")\n", - "\n", - "profiles.append(eoepca_jupyter_lab_profile)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Image pull secret\n" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "image_pull_secret = ImagePullSecret(\n", - " name=\"cr-config\",\n", - " persist=False,\n", - " data=\"ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9\",\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "image = \"cr.terradue.com/eoepca-plus/scipy-notebook@sha256:f339a9fa98d3d0c1fa8d7cc850e7f5a46845781f49bee86aacba059669d02d54\"\n", - "image = \"eoepca/iat-jupyterlab:develop\"\n", - "\n", - "eoepca_jupyter_lab_profile_2 = Profile(\n", - " id=\"profile_jupyter_lab_2\",\n", - " groups=[\"group-c\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Jupyter Lab - profile 2\",\n", - " description=\"Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret\",\n", - " slug=\"eoepca_jupyter_lab_2\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[workspace_volume],\n", - " config_maps=[],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", - " },\n", - " image_pull_secrets=[image_pull_secret],\n", - ")\n", - "\n", - "profiles.append(eoepca_jupyter_lab_profile_2)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Stage-in/out" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "with open(\"./config-maps/init-stac.sh\", \"r\") as f:\n", - " content = f.read()\n", - "\n", - "init_cm = ConfigMap(\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [], - "source": [ - "with open(\"manifests/manifest.yaml\", \"r\") as f:\n", - " content = yaml.safe_load_all(f.read())\n", - "\n", - "\n", - "\n", - "localstack_manifest = Manifest(\n", - " name=\"manifests\", key=\"manifests\", readonly=True, persist=False, content=[e for e in content]\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## e-learning" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "image = \"docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40\"\n", - "\n", - "coder_profile_stac = Profile(\n", - " id=f\"profile_studio_coder_stac\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Understanding STAC for input/output data modelling\",\n", - " description=\"Understand the role of STAC in input/output data manifests in EO data processing workflows\",\n", - " slug=\"eoepca_coder_slug_stac\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[workspace_volume],\n", - " config_maps=[init_cm, bash_rc_cm, bash_login_cm],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " \"CONDARC\": \"/workspace/.condarc\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_CONFIG_HOME\": \"/workspace/.local\",\n", - " \"XDG_DATA_HOME\": \"/workspace/.local/share/\",\n", - " \"CWLTOOL_OPTIONS\": \"--podman\", \n", - " \"CODE_SERVER_WS\": \"/workspace/stac-eoap\",\n", - " \"AWS_DEFAULT_REGION\": \"us-east-1\",\n", - " \"AWS_ACCESS_KEY_ID\": \"test\",\n", - " \"AWS_SECRET_ACCESS_KEY\": \"test\",\n", - " },\n", - " role_bindings=[],\n", - " init_containers=[init_container],\n", - " image_pull_secrets=[image_pull_secret],\n", - " manifests=[localstack_manifest],\n", - ")\n", - "\n", - "profiles.append(coder_profile_stac)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## QGIS" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "with open(\"./config-maps/init-qgis.sh\", \"r\") as f:\n", - " content = f.read()\n", - "\n", - "init_qgis_cm = ConfigMap(\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - ")\n", - "\n", - "image = \"eoepca/iga-remote-desktop-qgis:1.1.3\"\n", - "\n", - "qgis_profile = Profile(\n", - " id=\"profile_studio_desktop_qgis\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"QGIS on a Remote Desktop\",\n", - " description=\"Spatial visualization and decision-making tools for everyone\",\n", - " slug=\"eoepca_desktop_qgis\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_limit=2,\n", - " mem_limit=\"2G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[workspace_volume],\n", - " config_maps=[bash_rc_cm, bash_login_cm],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\"\n", - " },\n", - " default_url=\"desktop\",\n", - " init_containers=[]\n", - " )\n", - "\n", - "profiles.append(qgis_profile)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Configuration" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [], - "source": [ - "config = Config(\n", - " profiles=profiles\n", - ")\n", - "\n", - "with open(\n", - " \"../eoepca-demo/config.yml\", \"w\"\n", - ") as file:\n", - " yaml.dump(config.dict(), file)" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [], - "source": [ - "# profiles" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": ".env-config-generator", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.14" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/config-generator/config-generator.ipynb b/config-generator/config-generator.ipynb deleted file mode 100644 index 9afb17b..0000000 --- a/config-generator/config-generator.ipynb +++ /dev/null @@ -1,573 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import yaml\n", - "from models import *\n", - "import os" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "with open(\"manifests/manifest.yaml\", \"r\") as f:\n", - " content = yaml.safe_load_all(f.read())\n", - "\n", - "\n", - "\n", - "localstack_manifest = Manifest(\n", - " name=\"manifests\", key=\"manifests\", readonly=True, persist=False, content=[e for e in content]\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Configuration\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "storage_class_rwo = \"standard\"\n", - "storage_class_rwx = \"standard\"\n", - "\n", - "workspace_volume_size = \"50Gi\"\n", - "calrissian_volume_size = \"50Gi\"\n", - "\n", - "node_selector = {} #\"k8s.scaleway.com/pool-name\": \"application-hub\"}\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Volumes\n", - "\n", - "Create the Volumes" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Workspace Volume\n", - "\n", - "The workspace volume is persisted." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "workspace_volume = Volume(\n", - " name=\"workspace-volume\",\n", - " size=workspace_volume_size,\n", - " claim_name=\"workspace-claim\",\n", - " mount_path=\"/workspace\",\n", - " storage_class=storage_class_rwo,\n", - " access_modes=[\"ReadWriteOnce\"],\n", - " volume_mount=VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"),\n", - " persist=True,\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Calrissian Volume\n", - "\n", - "This is a RWX volume, not persisted" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "calrissian_volume = Volume(\n", - " name=\"calrissian-volume\",\n", - " claim_name=\"calrissian-claim\",\n", - " size=calrissian_volume_size,\n", - " storage_class=storage_class_rwx,\n", - " access_modes=[\"ReadWriteMany\"],\n", - " volume_mount=VolumeMount(name=\"calrissian-volume\", mount_path=\"/calrissian\"),\n", - " persist=False,\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ConfigMaps\n", - "\n", - "These configmaps are mounted as files on the pod." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### bash login\n", - "\n", - "This file is used for the JupyterLab Terminal configuration" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "with open(\"config-maps/bash-login\", \"r\") as f:\n", - " content = f.read()\n", - "\n", - "bash_login_cm = ConfigMap(\n", - " name=\"bash-login\",\n", - " key=\"bash-login\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=True,\n", - " mount_path=\"/workspace/.bash_login\",\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### bash.rc\n" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "with open(\"config-maps/bash-rc\", \"r\") as f:\n", - " content = f.read()\n", - "bash_rc_cm = ConfigMap(\n", - " name=\"bash-rc\",\n", - " key=\"bash-rc\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=True,\n", - " mount_path=\"/workspace/.bashrc\",\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Profiles" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "profiles = []" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Coder" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "coders = {\n", - " \"coder1\": {\n", - " \"display_name\": \"Code Server Small\",\n", - " \"slug\": \"eoepca_coder_slug_s\",\n", - " \"cpu_limit\": 2,\n", - " \"mem_limit\": \"8G\",\n", - " },\n", - " \"coder2\": {\n", - " \"display_name\": \"Code Server Medium\",\n", - " \"slug\": \"eoepca_coder_slug_m\",\n", - " \"cpu_limit\": 4,\n", - " \"mem_limit\": \"12G\",\n", - " },\n", - "}\n", - "\n", - "for key, value in coders.items():\n", - " coder_definition = ProfileDefinition(\n", - " display_name=value[\"display_name\"],\n", - " slug=value[\"slug\"],\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_limit=value[\"cpu_limit\"],\n", - " mem_limit=value[\"mem_limit\"],\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " ),\n", - " )\n", - "\n", - " coder_profile = Profile(\n", - " id=f\"profile_studio_{key}\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=coder_definition,\n", - " node_selector=node_selector,\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[\n", - " bash_rc_cm,\n", - " ],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \" /workspace/.envs\",\n", - " },\n", - " manifests=[localstack_manifest],\n", - " env_from_config_maps=[\"my-config\"],\n", - " env_from_secrets=[\"my-secret\", \"data-by-name\"],\n", - " secret_mounts=[SecretMount(name=\"aws-credentials-{{ spawner.user.name }}\", mount_path=\"/workspace/.aws\"), \n", - " SecretMount(name=\"data-by-name\", mount_path=\"/workspace/.data-by-name\"), \n", - " SecretMount(name=\"eoepca-plus-secret-ro\", mount_path=\"/workspace/.docker\"),],\n", - " image_pull_secrets=[ImagePullSecret(name=\"eoepca-plus-secret-ro\")],\n", - " )\n", - "\n", - " profiles.append(coder_profile)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## init.sh script" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "with open(\"./config-maps/init.sh\", \"r\") as f:\n", - " content = f.read()\n", - "\n", - "init_cm = ConfigMap(\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - " default_mode=\"0660\",\n", - ")\n", - "\n", - "\n", - "init_context_volume_mount = InitContainerVolumeMount(\n", - " mount_path=\"/opt/init/.init.sh\", name=\"init\", sub_path=\"init\"\n", - ")\n", - "init_container = InitContainer(\n", - " name=\"init-file-on-volume\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " command=[\"sh\", \"-c\", \"sh /opt/init/.init.sh\"],\n", - " volume_mounts=[\n", - " VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"),\n", - " init_context_volume_mount,\n", - " ],\n", - ")\n", - "\n", - "eoepca_demo_init_script_profile = Profile(\n", - " id=f\"profile_demo_init_script\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Coder demo init script\",\n", - " description=\"This profile is used to demonstrate the use of an init script\",\n", - " slug=\"eoepca_demo_init_script\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " ),\n", - " ),\n", - " node_selector=node_selector,\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[init_cm],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " \"CONDARC\": \"/workspace/.condarc\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"CODE_SERVER_WS\": \"/workspace/mastering-app-package\",\n", - " },\n", - " init_containers=[init_container],\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "profiles.append(eoepca_demo_init_script_profile)\n", - "\n", - "eoepca_demo_init_script_profile = Profile(\n", - " id=f\"profile_demo_init_script\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Coder demo init script\",\n", - " description=\"This profile is used to demonstrate the use of an init script\",\n", - " slug=\"eoepca_demo_init_script\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " ),\n", - " ),\n", - " node_selector=node_selector,\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[init_cm],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " \"CONDARC\": \"/workspace/.condarc\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"CODE_SERVER_WS\": \"/workspace/mastering-app-package\",\n", - " },\n", - " init_containers=[init_container],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## JupyterLab" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "image = \"jupyter/scipy-notebook\"\n", - "\n", - "\n", - "eoepca_jupyter_lab_profile = Profile(\n", - " id=\"profile_jupyter_lab\",\n", - " groups=[\"group-c\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Jupyter Lab\",\n", - " description=\"Jupyter Lab with Python 3.11\",\n", - " slug=\"eoepca_jupyter_lab\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector=node_selector,\n", - " volumes=[workspace_volume],\n", - " config_maps=[],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", - " },\n", - ")\n", - "\n", - "profiles.append(eoepca_jupyter_lab_profile)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Image pull secret\n" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "image_pull_secret = ImagePullSecret(\n", - " name=\"cr-config\",\n", - " persist=False,\n", - " data=\"\",\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "with open(\"manifests/manifest.yaml\", \"r\") as f:\n", - " content = yaml.safe_load_all(f.read())\n", - "\n", - "\n", - "\n", - "localstack_manifest = Manifest(\n", - " name=\"manifests\", key=\"manifests\", readonly=True, persist=False, content=[e for e in content]\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "image = \"jupyter/scipy-notebook\"\n", - "\n", - "\n", - "eoepca_jupyter_lab_profile_2 = Profile(\n", - " id=\"profile_jupyter_lab_2\",\n", - " groups=[\"group-c\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Jupyter Lab - profile 2\",\n", - " description=\"Jupyter Lab with Python 3.11 - demoes the use of an image pull secret\",\n", - " slug=\"eoepca_jupyter_lab_2\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector=node_selector,\n", - " volumes=[workspace_volume],\n", - " config_maps=[],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", - " },\n", - " image_pull_secrets=[image_pull_secret],\n", - " manifests=[localstack_manifest],\n", - ")\n", - "\n", - "profiles.append(eoepca_jupyter_lab_profile_2)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Configuration" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [], - "source": [ - "config = Config(\n", - " profiles=profiles\n", - ")\n", - "\n", - "with open(\n", - " \"../files/hub/config.yml\", \"w\"\n", - ") as file:\n", - " yaml.dump(config.dict(), file)" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "# profiles" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": ".env-config-generator", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.14" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/config-generator/apphub-configurator/examples/config-generator-eoepca-demo.ipynb b/config-generator/examples/config-generator-eoepca-demo.ipynb similarity index 96% rename from config-generator/apphub-configurator/examples/config-generator-eoepca-demo.ipynb rename to config-generator/examples/config-generator-eoepca-demo.ipynb index 3e8d5ae..b0b4eed 100644 --- a/config-generator/apphub-configurator/examples/config-generator-eoepca-demo.ipynb +++ b/config-generator/examples/config-generator-eoepca-demo.ipynb @@ -2,12 +2,12 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import yaml\n", - "from models import *\n", + "from src.apphub_configurator.models import *\n", "import os" ] }, @@ -120,11 +120,11 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "with open(\"config-maps/bash-login\", \"r\") as f:\n", + "with open(\"../config-maps/bash-login\", \"r\") as f:\n", " content = f.read()\n", "\n", "bash_login_cm = ConfigMap(\n", @@ -147,11 +147,11 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "with open(\"config-maps/bash-rc\", \"r\") as f:\n", + "with open(\"../config-maps/bash-rc\", \"r\") as f:\n", " content = f.read()\n", "bash_rc_cm = ConfigMap(\n", " name=\"bash-rc\",\n", @@ -172,11 +172,11 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "with open(\"config-maps/bash-login\", \"r\") as f:\n", + "with open(\"../config-maps/bash-login\", \"r\") as f:\n", " content = f.read()\n", "\n", "bash_login_cm = ConfigMap(\n", @@ -274,11 +274,11 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "with open(\"./config-maps/init.sh\", \"r\") as f:\n", + "with open(\"../config-maps/init.sh\", \"r\") as f:\n", " content = f.read()\n", "\n", "init_cm = ConfigMap(\n", @@ -486,11 +486,11 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "with open(\"./config-maps/init-stac.sh\", \"r\") as f:\n", + "with open(\"../config-maps/init-stac.sh\", \"r\") as f:\n", " content = f.read()\n", "\n", "init_cm = ConfigMap(\n", @@ -505,11 +505,11 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "with open(\"manifests/manifest.yaml\", \"r\") as f:\n", + "with open(\"../manifests/manifest.yaml\", \"r\") as f:\n", " content = yaml.safe_load_all(f.read())\n", "\n", "\n", @@ -588,11 +588,11 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "with open(\"./config-maps/init-qgis.sh\", \"r\") as f:\n", + "with open(\"../config-maps/init-qgis.sh\", \"r\") as f:\n", " content = f.read()\n", "\n", "init_qgis_cm = ConfigMap(\n", diff --git a/config-generator/apphub-configurator/examples/config-generator.ipynb b/config-generator/examples/config-generator.ipynb similarity index 96% rename from config-generator/apphub-configurator/examples/config-generator.ipynb rename to config-generator/examples/config-generator.ipynb index 095a292..ac616bd 100644 --- a/config-generator/apphub-configurator/examples/config-generator.ipynb +++ b/config-generator/examples/config-generator.ipynb @@ -1,14 +1,5 @@ { "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!pip install apphub-configurator" - ] - }, { "cell_type": "code", "execution_count": null, @@ -16,7 +7,7 @@ "outputs": [], "source": [ "import yaml\n", - "from apphub_configurator.models import *\n", + "from src.apphub_configurator.models import *\n", "import os" ] }, @@ -26,7 +17,7 @@ "metadata": {}, "outputs": [], "source": [ - "with open(\"manifests/manifest.yaml\", \"r\") as f:\n", + "with open(\"../manifests/manifest.yaml\", \"r\") as f:\n", " content = yaml.safe_load_all(f.read())\n", "\n", "\n", @@ -154,7 +145,7 @@ "metadata": {}, "outputs": [], "source": [ - "with open(\"config-maps/bash-login\", \"r\") as f:\n", + "with open(\"../config-maps/bash-login\", \"r\") as f:\n", " content = f.read()\n", "\n", "bash_login_cm = ConfigMap(\n", @@ -181,7 +172,7 @@ "metadata": {}, "outputs": [], "source": [ - "with open(\"config-maps/bash-rc\", \"r\") as f:\n", + "with open(\"../config-maps/bash-rc\", \"r\") as f:\n", " content = f.read()\n", "bash_rc_cm = ConfigMap(\n", " name=\"bash-rc\",\n", @@ -294,7 +285,7 @@ "metadata": {}, "outputs": [], "source": [ - "with open(\"./config-maps/init.sh\", \"r\") as f:\n", + "with open(\"../config-maps/init.sh\", \"r\") as f:\n", " content = f.read()\n", "\n", "init_cm = ConfigMap(\n", @@ -460,7 +451,7 @@ "metadata": {}, "outputs": [], "source": [ - "with open(\"manifests/manifest.yaml\", \"r\") as f:\n", + "with open(\"../manifests/manifest.yaml\", \"r\") as f:\n", " content = yaml.safe_load_all(f.read())\n", "\n", "\n", @@ -529,7 +520,7 @@ "source": [ "config = Config(profiles=profiles)\n", "\n", - "with open(\"../files/hub/config.yml\", \"w\") as file:\n", + "with open(\"../../files/hub/config.yml\", \"w\") as file:\n", " yaml.dump(config.dict(), file)" ] }, diff --git a/config-generator/apphub-configurator/examples/generate-config.sh b/config-generator/examples/generate-config.sh similarity index 100% rename from config-generator/apphub-configurator/examples/generate-config.sh rename to config-generator/examples/generate-config.sh diff --git a/config-generator/generate_config.py b/config-generator/examples/generate_config.py similarity index 99% rename from config-generator/generate_config.py rename to config-generator/examples/generate_config.py index 4847780..de5e4a3 100644 --- a/config-generator/generate_config.py +++ b/config-generator/examples/generate_config.py @@ -1,4 +1,4 @@ -from models import * +from src.apphub_configurator.models import * import yaml import os from loguru import logger diff --git a/config-generator/generate-config.sh b/config-generator/generate-config.sh deleted file mode 100755 index 279e056..0000000 --- a/config-generator/generate-config.sh +++ /dev/null @@ -1,4 +0,0 @@ -#exit 0 -export PYTHONPATH=$PWD/`dirname $0`/. - -.env-config-generator/bin/python3 -m generate_config \ No newline at end of file diff --git a/config-generator/helpers.py b/config-generator/helpers.py deleted file mode 100644 index 1ae9d98..0000000 --- a/config-generator/helpers.py +++ /dev/null @@ -1,62 +0,0 @@ -import yaml - -from models import ( - Volume, - InitContainer, - VolumeMount, - ConfigMap, - InitContainerVolumeMount, - Manifest, -) - - -def load_config_map(name, key, file_name, mount_path): - with open(file_name, "r") as f: - content = f.read() - return ConfigMap( - name=name, - key=key, - content=content, - readonly=True, - persist=True, - mount_path=mount_path, - ) - - -def load_manifests(name, key, file_path): - with open(file_path, "r") as f: - content = yaml.safe_load_all(f.read()) - return Manifest( - name=name, key=key, readonly=True, persist=False, content=[e for e in content] - ) - - -def create_init_container(image: str, volume: Volume, mount_path: str) -> InitContainer: - - init_context_volume_mount = InitContainerVolumeMount( - mount_path="/opt/init/.init.sh", name="init", sub_path="init" - ) - - return InitContainer( - name="init-file-on-volume", - image=image, - command=["sh", "-c", "sh /opt/init/.init.sh"], - volume_mounts=[ - VolumeMount(name=volume.name, mount_path=mount_path), - init_context_volume_mount, - ], - ) - - -def load_init_script(file_name: str) -> ConfigMap: - with open(file_name, "r") as f: - content = f.read() - return ConfigMap( - name="init", - key="init", - content=content, - readonly=True, - persist=False, - mount_path="/opt/init/.init.sh", - default_mode="0660", - ) diff --git a/config-generator/models.py b/config-generator/models.py deleted file mode 100644 index 4715396..0000000 --- a/config-generator/models.py +++ /dev/null @@ -1,157 +0,0 @@ -from enum import Enum -from typing import Dict, List, Optional, Union - -from pydantic import BaseModel - - -class ConfigMapKeyRef(BaseModel): - name: str - key: str - - -class ConfigMapEnvVarReference(BaseModel): - from_config_map: ConfigMapKeyRef - - -class SubjectKind(str, Enum): - service_account = "ServiceAccount" - user = "User" - - -class Verb(str, Enum): - get = "get" - list = "list" - watch = "watch" - create = "create" - update = "update" - patch = "patch" - delete = "delete" - deletecollection = "deletecollection" - - -class Subject(BaseModel): - name: str - kind: SubjectKind - - -class Role(BaseModel): - name: str - resources: List[str] - verbs: List[Verb] - api_groups: Optional[List[str]] = [""] - - -class RoleBinding(BaseModel): - name: str - subjects: List[Subject] - role: Role - persist: bool = True - - -class VolumeMount(BaseModel): - """volume mount object""" - - name: str - mount_path: str - - -class InitContainerVolumeMount(VolumeMount): - sub_path: str - - -class Volume(BaseModel): - """volume object""" - - name: str - claim_name: str - size: str - storage_class: str - access_modes: List[str] - volume_mount: VolumeMount - persist: bool - - -class Manifest(BaseModel): - name: str - key: str - content: Optional[List[Dict]] = None - persist: Optional[bool] = True - - -class ConfigMap(BaseModel): - """config map object""" - - name: str - key: str - mount_path: Optional[str] = None - default_mode: Optional[str] = None - readonly: bool - content: Optional[str] = None - persist: Optional[bool] = True - - -class KubespawnerOverride(BaseModel): - """kubespawner override object""" - - cpu_limit: int - cpu_guarantee: Optional[int] = None - mem_limit: str - mem_guarantee: Optional[str] = None - image: str - extra_resource_limits: Optional[dict] = {} - extra_resource_guarantees: Optional[dict] = {} - - -class InitContainer(BaseModel): - name: str - image: str - command: List[str] - volume_mounts: list[VolumeMount | InitContainerVolumeMount] - - -class ProfileDefinition(BaseModel): - """profile definition object""" - - display_name: str - description: Optional[str] = None - slug: str - default: bool - kubespawner_override: KubespawnerOverride - - -class ImagePullSecret(BaseModel): - name: str - persist: bool = True - data: Optional[str] = None - - -class SecretMount(BaseModel): - name: str - mount_path: str - sub_path: Optional[str] = None - - -class Profile(BaseModel): - """profile object""" - - id: str - groups: List[str] - definition: ProfileDefinition - config_maps: Optional[List[ConfigMap]] = None - volumes: Optional[List[Volume]] = None - pod_env_vars: Optional[Dict[str, Union[str, ConfigMapEnvVarReference]]] = None - default_url: Optional[str] = None - node_selector: dict - role_bindings: Optional[List[RoleBinding]] = None - image_pull_secrets: Optional[List[ImagePullSecret]] = [] - init_containers: Optional[List[InitContainer]] = [] - manifests: Optional[List[Manifest]] = None - env_from_config_maps: Optional[List[str]] = None - env_from_secrets: Optional[List[str]] = None - secret_mounts: Optional[List[SecretMount]] = None - - -class Config(BaseModel): - """config object""" - - profiles: List[Profile] diff --git a/config-generator/apphub-configurator/pyproject.toml b/config-generator/pyproject.toml similarity index 68% rename from config-generator/apphub-configurator/pyproject.toml rename to config-generator/pyproject.toml index 3cbea85..bf4326f 100644 --- a/config-generator/apphub-configurator/pyproject.toml +++ b/config-generator/pyproject.toml @@ -21,13 +21,13 @@ classifiers = [ "Programming Language :: Python :: 3.12", ] -dependencies = ["pydantic", "pyyaml"] +dependencies = [] [project.urls] -Documentation = "https://github.com/EOEPCA/application-hub-context/tree/ESAEOEPCA-236/config-generator/apphub-configurator#readme" +Documentation = "https://github.com/EOEPCA/application-hub-context/config-generator/apphub-configurator#readme" Issues = "https://github.com/EOEPCA/application-hub-context/issues" -Source = "https://github.com/EOEPCA/application-hub-context/tree/ESAEOEPCA-236/config-generator/apphub-configurator" -Examples = "https://github.com/EOEPCA/application-hub-context/tree/ESAEOEPCA-236/config-generator/apphub-configurator/examples" +Source = "https://github.com/EOEPCA/application-hub-context/config-generator/apphub-configurator" +Examples = "https://github.com/EOEPCA/application-hub-context/config-generator/examples" [tool.hatch.version] path = "src/apphub_configurator/__about__.py" @@ -37,9 +37,22 @@ allow-direct-references = true [tool.hatch.build] packages = ["src/apphub_configurator"] +examples = ["examples"] + [tool.hatch.envs.default] -dependencies = ["pytest", "pytest-cov"] +path = "/code/hatch/envs/apphub_configurator" +[tool.hatch.envs.prod] +path = "/code/hatch/envs/apphub_configurator" +dependencies = ["pytest", + "pytest-cov", + "pydantic", + "pyyaml", + "ipykernel", + "jupyter"] + +[[tool.hatch.envs.test.matrix]] +python = ["3.10", "3.11", "3.12"] # Coverage settings [tool.coverage.run] diff --git a/config-generator/apphub-configurator/src/apphub_configurator/__about__.py b/config-generator/src/apphub_configurator/__about__.py similarity index 100% rename from config-generator/apphub-configurator/src/apphub_configurator/__about__.py rename to config-generator/src/apphub_configurator/__about__.py diff --git a/config-generator/apphub-configurator/src/apphub_configurator/__init__.py b/config-generator/src/apphub_configurator/__init__.py similarity index 100% rename from config-generator/apphub-configurator/src/apphub_configurator/__init__.py rename to config-generator/src/apphub_configurator/__init__.py diff --git a/config-generator/apphub-configurator/src/apphub_configurator/helpers.py b/config-generator/src/apphub_configurator/helpers.py similarity index 100% rename from config-generator/apphub-configurator/src/apphub_configurator/helpers.py rename to config-generator/src/apphub_configurator/helpers.py diff --git a/config-generator/apphub-configurator/src/apphub_configurator/models.py b/config-generator/src/apphub_configurator/models.py similarity index 100% rename from config-generator/apphub-configurator/src/apphub_configurator/models.py rename to config-generator/src/apphub_configurator/models.py diff --git a/config-generator/apphub-configurator/tests/__init__.py b/config-generator/tests/__init__.py similarity index 100% rename from config-generator/apphub-configurator/tests/__init__.py rename to config-generator/tests/__init__.py From 1e182ada88b1f1ff20c03f7fa17b5e4751a609fb Mon Sep 17 00:00:00 2001 From: parham-membari-terradue Date: Tue, 4 Mar 2025 11:22:44 +0100 Subject: [PATCH 02/86] delete redundant README --- config-generator/README2.md | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 config-generator/README2.md diff --git a/config-generator/README2.md b/config-generator/README2.md deleted file mode 100644 index 993873d..0000000 --- a/config-generator/README2.md +++ /dev/null @@ -1,27 +0,0 @@ -# apphub-configurator - -[![PyPI - Version](https://img.shields.io/pypi/v/apphub-configurator.svg)](https://pypi.org/project/apphub-configurator) -[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/apphub-configurator.svg)](https://pypi.org/project/apphub-configurator) - ------ - -## Table of Contents - -- [Installation](#installation) -- [Overview](#overview) -- [Examples](#Examples) -- [License](#license) - -## Installation - -```console -pip install apphub-configurator -``` -## Overview -This package contains a notebook and the python modules to support the generation of ApplicationHub configurations for a minikube cluster. For more information about ApplicationHub please check this [link](https://github.com/EOEPCA/application-hub-context) - -## Examples: -You can find examples of how to use this package from this [link](https://github.com/EOEPCA/application-hub-context/config-generator/apphub-configurator/examples) -## License - -`apphub-configurator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. From 71c22a78c499835aa97a5098e58f2568b2f202f5 Mon Sep 17 00:00:00 2001 From: parham-membari-terradue Date: Tue, 4 Mar 2025 11:29:45 +0100 Subject: [PATCH 03/86] delete requirements.txt --- config-generator/requirements.txt | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 config-generator/requirements.txt diff --git a/config-generator/requirements.txt b/config-generator/requirements.txt deleted file mode 100644 index 3c47a85..0000000 --- a/config-generator/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -ipykernel -pydantic -pyyaml \ No newline at end of file From 919ca1037a5fd1ee04fa82fab192b14df96fe467 Mon Sep 17 00:00:00 2001 From: parham-membari-terradue Date: Tue, 4 Mar 2025 12:35:30 +0100 Subject: [PATCH 04/86] feat: edit paths --- .../config-generator-eoepca-demo.ipynb | 91 ++++++++++++------- .../examples/config-generator.ipynb | 88 ++++++++++++------ config-generator/examples/generate_config.py | 34 ++++--- 3 files changed, 133 insertions(+), 80 deletions(-) diff --git a/config-generator/examples/config-generator-eoepca-demo.ipynb b/config-generator/examples/config-generator-eoepca-demo.ipynb index b0b4eed..99ae4ac 100644 --- a/config-generator/examples/config-generator-eoepca-demo.ipynb +++ b/config-generator/examples/config-generator-eoepca-demo.ipynb @@ -2,13 +2,27 @@ "cells": [ { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'/home/p/Desktop/p/Terradue/EOEPCA/application-hub-context/config-generator/examples'" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "import yaml\n", - "from src.apphub_configurator.models import *\n", - "import os" + "from apphub_configurator.models import *\n", + "from pathlib import Path\n", + "import os\n", + "current_dir = os.getcwd()\n", + "current_dir" ] }, { @@ -22,7 +36,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -55,7 +69,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -83,7 +97,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -120,11 +134,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ - "with open(\"../config-maps/bash-login\", \"r\") as f:\n", + "bash_login_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/bash-login\") \n", + "with open(bash_login_file_path, \"r\") as f:\n", " content = f.read()\n", "\n", "bash_login_cm = ConfigMap(\n", @@ -147,11 +162,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ - "with open(\"../config-maps/bash-rc\", \"r\") as f:\n", + "bash_rc_cm_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/bash-rc\") \n", + "with open(bash_rc_cm_file_path, \"r\") as f:\n", " content = f.read()\n", "bash_rc_cm = ConfigMap(\n", " name=\"bash-rc\",\n", @@ -172,11 +188,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ - "with open(\"../config-maps/bash-login\", \"r\") as f:\n", + "bash_login_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/bash-login\") \n", + "with open(bash_login_file_path, \"r\") as f:\n", " content = f.read()\n", "\n", "bash_login_cm = ConfigMap(\n", @@ -199,7 +216,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -216,7 +233,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -274,11 +291,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ - "with open(\"../config-maps/init.sh\", \"r\") as f:\n", + "init_cm_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/init.sh\") \n", + "with open(init_cm_file_path, \"r\") as f:\n", " content = f.read()\n", "\n", "init_cm = ConfigMap(\n", @@ -337,7 +355,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -382,7 +400,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -427,7 +445,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ @@ -440,7 +458,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 20, "metadata": {}, "outputs": [], "source": [ @@ -486,11 +504,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": {}, "outputs": [], "source": [ - "with open(\"../config-maps/init-stac.sh\", \"r\") as f:\n", + "init_stac_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/init-stac.sh\")\n", + "with open(init_stac_file_path, \"r\") as f:\n", " content = f.read()\n", "\n", "init_cm = ConfigMap(\n", @@ -505,11 +524,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "metadata": {}, "outputs": [], "source": [ - "with open(\"../manifests/manifest.yaml\", \"r\") as f:\n", + "localstack_manifest_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"manifests/manifest.yaml\") \n", + "with open(localstack_manifest_path, \"r\") as f:\n", " content = yaml.safe_load_all(f.read())\n", "\n", "\n", @@ -531,7 +551,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 23, "metadata": {}, "outputs": [], "source": [ @@ -588,11 +608,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 24, "metadata": {}, "outputs": [], "source": [ - "with open(\"../config-maps/init-qgis.sh\", \"r\") as f:\n", + "init_qgis_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/init-qgis.sh\") \n", + "with open(init_qgis_file_path, \"r\") as f:\n", " content = f.read()\n", "\n", "init_qgis_cm = ConfigMap(\n", @@ -641,14 +662,14 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ "config = Config(profiles=profiles)\n", - "\n", - "with open(\"../eoepca-demo/config.yml\", \"w\") as file:\n", - " yaml.dump(config.dict(), file)" + "eoepca_demo_config_path = str(Path(current_dir).parent.parent / 'eoepca-demo' / 'config.yml')\n", + "with open(eoepca_demo_config_path, \"w\") as file:\n", + " yaml.dump(config.model_dump(), file)" ] }, { @@ -684,7 +705,7 @@ ], "metadata": { "kernelspec": { - "display_name": ".env-config-generator", + "display_name": "apphub_configurator", "language": "python", "name": "python3" }, @@ -698,7 +719,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.12.2" }, "orig_nbformat": 4 }, diff --git a/config-generator/examples/config-generator.ipynb b/config-generator/examples/config-generator.ipynb index ac616bd..04f19ab 100644 --- a/config-generator/examples/config-generator.ipynb +++ b/config-generator/examples/config-generator.ipynb @@ -2,22 +2,37 @@ "cells": [ { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'/home/p/Desktop/p/Terradue/EOEPCA/application-hub-context/config-generator/examples'" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "import yaml\n", - "from src.apphub_configurator.models import *\n", - "import os" + "from apphub_configurator.models import *\n", + "import os\n", + "from pathlib import Path\n", + "current_dir = os.getcwd()\n", + "current_dir" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ - "with open(\"../manifests/manifest.yaml\", \"r\") as f:\n", + "localstack_manifest_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"manifests/manifest.yaml\") \n", + "with open(localstack_manifest_path, \"r\") as f:\n", " content = yaml.safe_load_all(f.read())\n", "\n", "\n", @@ -41,7 +56,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -76,7 +91,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -104,7 +119,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -141,11 +156,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ - "with open(\"../config-maps/bash-login\", \"r\") as f:\n", + "bash_login_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/bash-login\") \n", + "with open(bash_login_file_path, \"r\") as f:\n", " content = f.read()\n", "\n", "bash_login_cm = ConfigMap(\n", @@ -168,11 +184,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ - "with open(\"../config-maps/bash-rc\", \"r\") as f:\n", + "bash_rc_cm_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/bash-rc\") \n", + "with open(bash_rc_cm_file_path, \"r\") as f:\n", " content = f.read()\n", "bash_rc_cm = ConfigMap(\n", " name=\"bash-rc\",\n", @@ -194,7 +211,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -211,7 +228,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -281,11 +298,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ - "with open(\"../config-maps/init.sh\", \"r\") as f:\n", + "init_cm_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/init.sh\") \n", + "with open(init_cm_file_path, \"r\") as f:\n", " content = f.read()\n", "\n", "init_cm = ConfigMap(\n", @@ -344,7 +362,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -389,7 +407,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -434,7 +452,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ @@ -447,11 +465,11 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ - "with open(\"../manifests/manifest.yaml\", \"r\") as f:\n", + "with open(localstack_manifest_path, \"r\") as f:\n", " content = yaml.safe_load_all(f.read())\n", "\n", "\n", @@ -466,7 +484,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -514,13 +532,23 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/tmp/ipykernel_153398/2113836574.py:5: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/\n", + " yaml.dump(config.dict(), file)\n" + ] + } + ], "source": [ "config = Config(profiles=profiles)\n", + "config_file_path = str(Path(current_dir).parent.parent / 'files' / 'hub' / 'config.yml')\n", "\n", - "with open(\"../../files/hub/config.yml\", \"w\") as file:\n", + "with open(config_file_path, \"w\") as file:\n", " yaml.dump(config.dict(), file)" ] }, @@ -557,7 +585,7 @@ ], "metadata": { "kernelspec": { - "display_name": "base", + "display_name": "apphub_configurator", "language": "python", "name": "python3" }, @@ -571,7 +599,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.12.2" }, "orig_nbformat": 4 }, diff --git a/config-generator/examples/generate_config.py b/config-generator/examples/generate_config.py index de5e4a3..663f116 100644 --- a/config-generator/examples/generate_config.py +++ b/config-generator/examples/generate_config.py @@ -1,8 +1,9 @@ -from src.apphub_configurator.models import * +from apphub_configurator.models import * import yaml +from pathlib import Path import os from loguru import logger -from helpers import ( +from apphub_configurator.helpers import ( load_config_map, load_manifests, create_init_container, @@ -22,28 +23,28 @@ current_dir = os.path.dirname(os.path.realpath(__file__)) # load the manifests - +localstack_manifest_path = os.path.join(current_dir.replace("/examples",""), "manifests/manifest.yaml") # localstack_manifest localstack_manifest = load_manifests( name="localstack", key="localstack", - file_path=os.path.join(current_dir, "manifests/manifest.yaml"), + file_path=localstack_manifest_path, ) +dask_gateway_manifest_path = os.path.join(current_dir.replace("/examples",""), "manifests/dask-gateway.yaml") # Dask Gateway manifest dask_gateway_manifest = load_manifests( name="dask-gateway", key="dask-gateway", - file_path=os.path.join(current_dir, "manifests/dask-gateway.yaml"), + file_path=dask_gateway_manifest_path, ) - +kaniko_manifest_path = os.path.join(current_dir.replace("/examples",""), "manifests/kaniko.yaml") kaniko_manifest = load_manifests( name="kaniko", key="kaniko", - file_path=os.path.join(current_dir, "manifests/kaniko.yaml"), + file_path=kaniko_manifest_path, ) # volumes - workspace_volume = Volume( name="workspace-volume", size=workspace_volume_size, @@ -65,22 +66,23 @@ persist=False, ) - +bash_login_file_path = os.path.join(current_dir.replace("/examples",""), "config-maps/bash-login") bash_login_cm = load_config_map( name="bash-login", key="bash-login", - file_name=os.path.join(current_dir, "config-maps/bash-login"), + file_name=bash_login_file_path, mount_path="/etc/profile.d/bash-login.sh", ) +bash_rc_cm_file_path = os.path.join(current_dir.replace("/examples",""), "config-maps/bash-rc") bash_rc_cm = load_config_map( name="bash-rc", key="bash-rc", - file_name=os.path.join(current_dir, "config-maps/bash-rc"), + file_name=bash_rc_cm_file_path, mount_path="/workspace/.bashrc", ) - -init_cm = load_init_script(os.path.join(current_dir, "config-maps/init.sh")) +init_cm_file_path = os.path.join(current_dir.replace("/examples",""), "config-maps/init.sh") +init_cm = load_init_script(init_cm_file_path) init_container = create_init_container( image=image, @@ -152,6 +154,8 @@ profiles.append(profile_1) config = Config(profiles=profiles) +config_file_path = str(Path(current_dir).parent.parent / 'files' / 'hub' / 'config.yml') + -with open("files/hub/config.yml", "w") as file: - yaml.dump(config.dict(), file, width=200) +with open(config_file_path, "w") as file: + yaml.dump(config.model_dump(), file, width=200) From 476b9477d4c48b3c665ca81101858a75571b7127 Mon Sep 17 00:00:00 2001 From: parham-membari-terradue Date: Tue, 4 Mar 2025 12:40:31 +0100 Subject: [PATCH 05/86] feat: edit paths --- .../config-generator-eoepca-demo.ipynb | 66 ++++++++----------- .../examples/config-generator.ipynb | 58 +++++++--------- config-generator/examples/generate_config.py | 14 ++-- 3 files changed, 59 insertions(+), 79 deletions(-) diff --git a/config-generator/examples/config-generator-eoepca-demo.ipynb b/config-generator/examples/config-generator-eoepca-demo.ipynb index 99ae4ac..3131ede 100644 --- a/config-generator/examples/config-generator-eoepca-demo.ipynb +++ b/config-generator/examples/config-generator-eoepca-demo.ipynb @@ -2,26 +2,16 @@ "cells": [ { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'/home/p/Desktop/p/Terradue/EOEPCA/application-hub-context/config-generator/examples'" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "import yaml\n", "from apphub_configurator.models import *\n", "from pathlib import Path\n", "import os\n", "current_dir = os.getcwd()\n", + "parent_dir = current_dir.replace(\"/examples\",\"\")\n", "current_dir" ] }, @@ -36,7 +26,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -69,7 +59,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -97,7 +87,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -134,11 +124,11 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ - "bash_login_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/bash-login\") \n", + "bash_login_file_path = os.path.join(parent_dir, \"config-maps/bash-login\") \n", "with open(bash_login_file_path, \"r\") as f:\n", " content = f.read()\n", "\n", @@ -162,11 +152,11 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ - "bash_rc_cm_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/bash-rc\") \n", + "bash_rc_cm_file_path = os.path.join(parent_dir, \"config-maps/bash-rc\") \n", "with open(bash_rc_cm_file_path, \"r\") as f:\n", " content = f.read()\n", "bash_rc_cm = ConfigMap(\n", @@ -188,11 +178,11 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ - "bash_login_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/bash-login\") \n", + "bash_login_file_path = os.path.join(parent_dir, \"config-maps/bash-login\") \n", "with open(bash_login_file_path, \"r\") as f:\n", " content = f.read()\n", "\n", @@ -216,7 +206,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -233,7 +223,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -291,11 +281,11 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ - "init_cm_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/init.sh\") \n", + "init_cm_file_path = os.path.join(parent_dir, \"config-maps/init.sh\") \n", "with open(init_cm_file_path, \"r\") as f:\n", " content = f.read()\n", "\n", @@ -355,7 +345,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -400,7 +390,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -445,7 +435,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -458,7 +448,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -504,11 +494,11 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ - "init_stac_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/init-stac.sh\")\n", + "init_stac_file_path = os.path.join(parent_dir, \"config-maps/init-stac.sh\")\n", "with open(init_stac_file_path, \"r\") as f:\n", " content = f.read()\n", "\n", @@ -524,11 +514,11 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 16, "metadata": {}, "outputs": [], "source": [ - "localstack_manifest_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"manifests/manifest.yaml\") \n", + "localstack_manifest_path = os.path.join(parent_dir, \"manifests/manifest.yaml\") \n", "with open(localstack_manifest_path, \"r\") as f:\n", " content = yaml.safe_load_all(f.read())\n", "\n", @@ -551,7 +541,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -608,11 +598,11 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ - "init_qgis_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/init-qgis.sh\") \n", + "init_qgis_file_path = os.path.join(parent_dir, \"config-maps/init-qgis.sh\") \n", "with open(init_qgis_file_path, \"r\") as f:\n", " content = f.read()\n", "\n", @@ -662,7 +652,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ diff --git a/config-generator/examples/config-generator.ipynb b/config-generator/examples/config-generator.ipynb index 04f19ab..dce506a 100644 --- a/config-generator/examples/config-generator.ipynb +++ b/config-generator/examples/config-generator.ipynb @@ -2,36 +2,26 @@ "cells": [ { "cell_type": "code", - "execution_count": 19, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'/home/p/Desktop/p/Terradue/EOEPCA/application-hub-context/config-generator/examples'" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "import yaml\n", "from apphub_configurator.models import *\n", "import os\n", "from pathlib import Path\n", "current_dir = os.getcwd()\n", + "parent_dir = current_dir.replace(\"/examples\",\"\")\n", "current_dir" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ - "localstack_manifest_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"manifests/manifest.yaml\") \n", + "localstack_manifest_path = os.path.join(parent_dir, \"manifests/manifest.yaml\") \n", "with open(localstack_manifest_path, \"r\") as f:\n", " content = yaml.safe_load_all(f.read())\n", "\n", @@ -56,7 +46,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -91,7 +81,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -119,7 +109,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -156,11 +146,11 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ - "bash_login_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/bash-login\") \n", + "bash_login_file_path = os.path.join(parent_dir, \"config-maps/bash-login\") \n", "with open(bash_login_file_path, \"r\") as f:\n", " content = f.read()\n", "\n", @@ -184,11 +174,11 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ - "bash_rc_cm_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/bash-rc\") \n", + "bash_rc_cm_file_path = os.path.join(parent_dir, \"config-maps/bash-rc\") \n", "with open(bash_rc_cm_file_path, \"r\") as f:\n", " content = f.read()\n", "bash_rc_cm = ConfigMap(\n", @@ -211,7 +201,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -228,7 +218,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -298,11 +288,11 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ - "init_cm_file_path = os.path.join(current_dir.replace(\"/examples\",\"\"), \"config-maps/init.sh\") \n", + "init_cm_file_path = os.path.join(parent_dir, \"config-maps/init.sh\") \n", "with open(init_cm_file_path, \"r\") as f:\n", " content = f.read()\n", "\n", @@ -362,7 +352,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -407,7 +397,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -452,7 +442,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -465,7 +455,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -484,7 +474,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -532,14 +522,14 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "/tmp/ipykernel_153398/2113836574.py:5: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/\n", + "/tmp/ipykernel_174531/2113836574.py:5: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/\n", " yaml.dump(config.dict(), file)\n" ] } @@ -554,7 +544,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ diff --git a/config-generator/examples/generate_config.py b/config-generator/examples/generate_config.py index 663f116..ad8b51d 100644 --- a/config-generator/examples/generate_config.py +++ b/config-generator/examples/generate_config.py @@ -21,9 +21,9 @@ # get the current directory current_dir = os.path.dirname(os.path.realpath(__file__)) - +parent_dir = current_dir.replace("/examples","") # load the manifests -localstack_manifest_path = os.path.join(current_dir.replace("/examples",""), "manifests/manifest.yaml") +localstack_manifest_path = os.path.join(parent_dir, "manifests/manifest.yaml") # localstack_manifest localstack_manifest = load_manifests( name="localstack", @@ -31,14 +31,14 @@ file_path=localstack_manifest_path, ) -dask_gateway_manifest_path = os.path.join(current_dir.replace("/examples",""), "manifests/dask-gateway.yaml") +dask_gateway_manifest_path = os.path.join(parent_dir, "manifests/dask-gateway.yaml") # Dask Gateway manifest dask_gateway_manifest = load_manifests( name="dask-gateway", key="dask-gateway", file_path=dask_gateway_manifest_path, ) -kaniko_manifest_path = os.path.join(current_dir.replace("/examples",""), "manifests/kaniko.yaml") +kaniko_manifest_path = os.path.join(parent_dir, "manifests/kaniko.yaml") kaniko_manifest = load_manifests( name="kaniko", key="kaniko", @@ -66,7 +66,7 @@ persist=False, ) -bash_login_file_path = os.path.join(current_dir.replace("/examples",""), "config-maps/bash-login") +bash_login_file_path = os.path.join(parent_dir, "config-maps/bash-login") bash_login_cm = load_config_map( name="bash-login", key="bash-login", @@ -74,14 +74,14 @@ mount_path="/etc/profile.d/bash-login.sh", ) -bash_rc_cm_file_path = os.path.join(current_dir.replace("/examples",""), "config-maps/bash-rc") +bash_rc_cm_file_path = os.path.join(parent_dir, "config-maps/bash-rc") bash_rc_cm = load_config_map( name="bash-rc", key="bash-rc", file_name=bash_rc_cm_file_path, mount_path="/workspace/.bashrc", ) -init_cm_file_path = os.path.join(current_dir.replace("/examples",""), "config-maps/init.sh") +init_cm_file_path = os.path.join(parent_dir, "config-maps/init.sh") init_cm = load_init_script(init_cm_file_path) init_container = create_init_container( From 747adb00902f2901c25be8fa03419c5a61e55f1a Mon Sep 17 00:00:00 2001 From: parham-membari-terradue Date: Tue, 4 Mar 2025 12:47:01 +0100 Subject: [PATCH 06/86] feat: clean coding --- .../config-generator-eoepca-demo.ipynb | 21 +++++++++++++----- .../examples/config-generator.ipynb | 22 ++++++++++++++----- config-generator/examples/generate_config.py | 5 +++-- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/config-generator/examples/config-generator-eoepca-demo.ipynb b/config-generator/examples/config-generator-eoepca-demo.ipynb index 3131ede..af5aa61 100644 --- a/config-generator/examples/config-generator-eoepca-demo.ipynb +++ b/config-generator/examples/config-generator-eoepca-demo.ipynb @@ -2,16 +2,27 @@ "cells": [ { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "PosixPath('/home/p/Desktop/p/Terradue/EOEPCA/application-hub-context/config-generator/examples')" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "import yaml\n", "from apphub_configurator.models import *\n", "from pathlib import Path\n", "import os\n", - "current_dir = os.getcwd()\n", - "parent_dir = current_dir.replace(\"/examples\",\"\")\n", + "current_dir = Path(os.getcwd())\n", + "parent_dir = current_dir.parent\n", "current_dir" ] }, diff --git a/config-generator/examples/config-generator.ipynb b/config-generator/examples/config-generator.ipynb index dce506a..36f1e11 100644 --- a/config-generator/examples/config-generator.ipynb +++ b/config-generator/examples/config-generator.ipynb @@ -2,16 +2,28 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "PosixPath('/home/p/Desktop/p/Terradue/EOEPCA/application-hub-context/config-generator/examples')" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "import yaml\n", "from apphub_configurator.models import *\n", "import os\n", "from pathlib import Path\n", - "current_dir = os.getcwd()\n", - "parent_dir = current_dir.replace(\"/examples\",\"\")\n", + "\n", + "current_dir = Path(os.getcwd())\n", + "parent_dir = current_dir.parent\n", "current_dir" ] }, @@ -529,7 +541,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/tmp/ipykernel_174531/2113836574.py:5: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/\n", + "/tmp/ipykernel_181750/2113836574.py:5: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/\n", " yaml.dump(config.dict(), file)\n" ] } diff --git a/config-generator/examples/generate_config.py b/config-generator/examples/generate_config.py index ad8b51d..7fecb8b 100644 --- a/config-generator/examples/generate_config.py +++ b/config-generator/examples/generate_config.py @@ -20,8 +20,9 @@ node_selector = {} # get the current directory -current_dir = os.path.dirname(os.path.realpath(__file__)) -parent_dir = current_dir.replace("/examples","") +current_dir = Path(os.path.dirname(os.path.realpath(__file__))) +parent_dir = current_dir.parent + # load the manifests localstack_manifest_path = os.path.join(parent_dir, "manifests/manifest.yaml") # localstack_manifest From 385ad419d33d6a2715d5c8e048246929961b396d Mon Sep 17 00:00:00 2001 From: parham-membari-terradue Date: Tue, 4 Mar 2025 12:54:48 +0100 Subject: [PATCH 07/86] docs: update docs --- config-generator/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/config-generator/README.md b/config-generator/README.md index f11268d..ec322e4 100644 --- a/config-generator/README.md +++ b/config-generator/README.md @@ -20,10 +20,11 @@ - [License](#license) ## Installation -Create a hatch environment with the dependencies listed in the file `pyproject.toml` and open the notebook [config-generator.ipynb](examples/config-generator.ipynb) +Create a hatch environment with the dependencies listed in the file `pyproject.toml`. ```console -pip install apphub-configurator +hatch shell prod +hatch -e prod run python -m ipykernel install --user --name=apphub_configurator --display-name "apphub_configurator" ``` ## Overview This package contains a notebook and the python modules to support the generation of ApplicationHub configurations for a minikube cluster. For more information about ApplicationHub please check this [link](https://github.com/EOEPCA/application-hub-context) From fa7ac38c22fbce54ed8661ea3582d598fbd67e74 Mon Sep 17 00:00:00 2001 From: parham-membari-terradue Date: Tue, 4 Mar 2025 12:54:51 +0100 Subject: [PATCH 08/86] docs: update docs --- config-generator/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-generator/README.md b/config-generator/README.md index ec322e4..be33f38 100644 --- a/config-generator/README.md +++ b/config-generator/README.md @@ -30,7 +30,7 @@ hatch -e prod run python -m ipykernel install --user --name=apphub_configurator This package contains a notebook and the python modules to support the generation of ApplicationHub configurations for a minikube cluster. For more information about ApplicationHub please check this [link](https://github.com/EOEPCA/application-hub-context) ## Examples: -The [example](https://github.com/EOEPCA/application-hub-context/config-generator/apphub-configurator/examples) folder contains a notebook and the python modules to support the generation of ApplicationHub configurations. +The [example](./examples/) folder contains a notebook and the python modules to support the generation of ApplicationHub configurations. ## License `apphub-configurator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. From 8a83bce31c273097311be281c38d7e46c5a19822 Mon Sep 17 00:00:00 2001 From: Fabrice Brito Date: Tue, 4 Mar 2025 14:52:57 +0100 Subject: [PATCH 09/86] fix in bash-rc --- config-generator/config-maps/bash-rc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config-generator/config-maps/bash-rc b/config-generator/config-maps/bash-rc index 3f643a6..5098e90 100644 --- a/config-generator/config-maps/bash-rc +++ b/config-generator/config-maps/bash-rc @@ -26,4 +26,6 @@ fi a={{spawner.user.name}} -alias aws="aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566" \ No newline at end of file +alias aws="aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566" + +export PATH=/workspace/.local/bin:$PATH \ No newline at end of file From 5c0479adf7cb07546cbd86b54d06bd0ed8e28736 Mon Sep 17 00:00:00 2001 From: Fabrice Brito Date: Tue, 4 Mar 2025 14:59:27 +0100 Subject: [PATCH 10/86] fixes skaffold config --- skaffold.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skaffold.yaml b/skaffold.yaml index 54d1aed..559145f 100644 --- a/skaffold.yaml +++ b/skaffold.yaml @@ -31,7 +31,7 @@ profiles: hooks: before: - host: - command: ["sh", "-c", "config-generator/generate-config.sh"] + command: ["sh", "-c", "config-generator/examples/generate-config.sh"] os: [darwin, linux] manifests: rawYaml: From 79087f07e42c5ad9f606a385c175c0e3a6b62e7c Mon Sep 17 00:00:00 2001 From: mlongobardo-gituname Date: Fri, 21 Mar 2025 17:39:12 +0100 Subject: [PATCH 11/86] first commit --- README.md | 137 +++++++++++++++++++++++++++++++++++++ config-generator/README.md | 19 +++-- docs/index.md | 11 ++- 3 files changed, 154 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index b826c57..adb8ea6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,140 @@ # ApplicationHub Context +Application Hub enables to manage the delivery of work environments and tools for a wide range of user tasks, such as develop, host, execute and perform exploratory analysis of EO applications, all managed within a single, unified Cloud infrastructure. +This capability is offered through: + +- **JupyterLab**: for interactive analysis of EO data +- **Code-Server**: for browser-based coding environments +- **Custom-Dashboards**: for specialized visualizations or user-defined apps such as QGIS, SNAP, Streamlit +- **Multi-User/Profile**: environment—users can be grouped, resource tuned and use different container images + +An Application is a kubernetes pod exposing a service via a port and accessed via a web browser. +Application pods are managed by JupyterHub, a deployment that proxies applications such as JupyterLab or Code Server acting as a SaaS solution for remote desktop applications such as QGIS, SNAP or dashboards. +JupyterHub provides profiles based on the user groups. +An Application pod runs in a dedicated namespace that may have configurations such as ConfigMaps. +The Application pod contextualization targets providing: + +- **runtime environment variables** +- **runtime configuration files such as S3 configuration files or docker config files** +- **volumes mounted such as the workspace volume** +- **Multi-User/Profile** + +The Application pod contextualization takes as input a 'profile' and is handled by the kube spawner providing: + +- **authentication/authorization** +- **pre-spawn and post-stop hooks** + +# SNAP via Remote Desktop + +This interactive application provides a remote desktop with SNAP via JupyterHub. +It relies on the [jupyter-remote-desktop-proxy](https://github.com/jupyterhub/jupyter-remote-desktop-proxy) + +## ApplicationHubConfiguration + +Below an example of configuration in the ApplicationHub: + +```yaml +- id: profile_iga_remote_desktop_snap + groups: + - group-1 + definition: + display_name: SNAP + slug: iga_remote_desktop_snap + default: False + kubespawner_override: + cpu_limit: 1 + mem_limit: 4G + image: eoepca/iga-remote-desktop_snap + default_url: "desktop" + node_selector: + k8s.provider.com/pool-name: node-pool-a +``` + +# Remote desktop +This interactive application provides a remote desktop via JupyterHub. +It relies on the [jupyter-remote-desktop-proxy](https://github.com/jupyterhub/jupyter-remote-desktop-proxy) + +## ApplicationHubConfiguration +Below an example of configuration in the ApplicationHub: + +```yaml +- id: profile_iga_remote_desktop + groups: + - group-1 + definition: + display_name: Remote Desktop + slug: iga_remote_desktop + default: False + kubespawner_override: + cpu_limit: 1 + mem_limit: 4G + image: eoepca/iga-remote-desktop + default_url: "desktop" + node_selector: + k8s.provider.com/pool-name: node-pool-a +``` + + +# QGIS via Remote Desktop +This interactive application provides a remote desktop with QGIS via JupyterHub. +It relies on the [jupyter-remote-desktop-proxy](https://github.com/jupyterhub/jupyter-remote-desktop-proxy) + +## ApplicationHubConfiguration +Below an example of configuration in the ApplicationHub: + +```yaml +- id: profile_iga_remote_desktop_qgis + groups: + - group-1 + definition: + display_name: QGIS + slug: iga_remote_desktop_qgis + default: False + kubespawner_override: + cpu_limit: 1 + mem_limit: 4G + image: eoepca/iga-remote-desktop_qgis + default_url: "desktop" + node_selector: + k8s.provider.com/pool-name: node-pool-a +``` + +In the application Dockerfile, after having pre-installed with pip install jhsingle-native-proxy>=0.0.9, the execution entrypoint in the Dockerfile is typically: + +CMD ["jhsingle-native-proxy", "--destport", "", "", "", "{--}server.port", "{port}", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", ""] + +CMD ["jhsingle-native-proxy", "--destport", "8505", "streamlit", "hello", "{--}server.port", "8888", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", "8888"] + + +Params breakdown: + +--destport: specifies the destination port number where the target APPLICATION (in this case, Streamlit) will run. + +--authtype: none disables authentication, meaning the application will not require authentication through JupyterHub. + +--port $port: This sets the PROXY server's external port number to $port. + +The {--} syntax is used to pass arguments to the Streamlit command. +{--}server.port {port}: server.port {port} sets the port number on which the Streamlit SERVER will run. Equivalently {--}bind-addr 0.0.0.0:$port + +{--}server.headless: True ensures that Streamlit runs in headless mode, meaning it does not require a graphical user interface (GUI). + +{--}server.enableCORS: False disables Cross-Origin Resource Sharing (CORS), which is useful when running behind a proxy. + + +Generalised command: +``` +jhsingle-native-proxy --destport $destport --authtype none {--}server.port {port} {--}server.headless True {--}server.enableCORS False --port $port +``` + +In addition to the usual jhsingle-native-proxy params list, an application, e.g. PDE Code Server, can leverage a user data path mapping and its VS code loading, e.g. + +``` +CODE_SERVER_WS="/workspace" +jhsingle-native-proxy --port 8888 --destport $destport code-server {--}auth none {--}bind-addr 0.0.0.0:$destport {--}user-data-dir /workspace $CODE_SERVER_WS +``` + [![Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.](https://www.repostatus.org/badges/latest/wip.svg)](https://www.repostatus.org/#wip) + + + diff --git a/config-generator/README.md b/config-generator/README.md index be33f38..49ce362 100644 --- a/config-generator/README.md +++ b/config-generator/README.md @@ -1,9 +1,17 @@ ## ApplicationHub configuration generator - - - - +The AppHub configuration generator is the helper that eases the generation of the desired configuration selecting tools among the predefined ones. +They include: + +- **aws-cli**: CLI used for interfacing with an S3 service +- **Stars**: CLI used for EO data stage-in from S3 or external repos and Data stage-out to S3 bucket +- **cwltool**: CWL runner using local resources +- **calrissian**: CWL runner on Kubernetes +- **cwl-wrapper**: Utility to add stage-in/out nodes to an Application Package +- **podman**: Container engine +- **git**: CLI used for interfacing with git-based software repositories +- **dvc**: Data versioning +- **oras**: Client for OCI compliant repositories # apphub-configurator @@ -11,7 +19,6 @@ [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/apphub-configurator.svg)](https://pypi.org/project/apphub-configurator) ----- - ## Table of Contents - [Installation](#installation) @@ -31,6 +38,6 @@ This package contains a notebook and the python modules to support the generatio ## Examples: The [example](./examples/) folder contains a notebook and the python modules to support the generation of ApplicationHub configurations. -## License +## License `apphub-configurator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. diff --git a/docs/index.md b/docs/index.md index bbccec4..6c404dd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -55,21 +55,20 @@ This hook contextualises the Application pod to spawn and: ```python def pre_spawn_hook(spawner): - spawner.http_timeout = 600 profile_slug = spawner.user_options.get("profile", None) env = os.environ["JUPYTERHUB_ENV"].lower() + spawner.environment["CALRISSIAN_POD_NAME"] = f"jupyter-{spawner.user.name}-{env}" + spawner.log.info(f"Using profile slug {profile_slug}") namespace = f"{namespace_prefix}-{spawner.user.name}" workspace = DefaultApplicationHubContext( - namespace=namespace, - spawner=spawner, - config_path=config_path + namespace=namespace, spawner=spawner, config_path=config_path, skip_namespace_check=False, ) workspace.initialise() @@ -99,9 +98,7 @@ def post_stop_hook(spawner): namespace = f"jupyter-{spawner.user.name}" workspace = DefaultApplicationHubContext( - namespace=namespace, - spawner=spawner, - config_path=config_path + namespace=namespace, spawner=spawner, config_path=config_path ) spawner.log.info("Dispose in post stop hook") workspace.dispose() From 31de90f303b341af8d9eba8df9e7b3427000e0ec Mon Sep 17 00:00:00 2001 From: pmembari Date: Thu, 27 Mar 2025 17:37:08 +0100 Subject: [PATCH 12/86] ci: create pipy package --- .github/workflows/package.yaml | 46 ++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/package.yaml diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml new file mode 100644 index 0000000..f1d80f0 --- /dev/null +++ b/.github/workflows/package.yaml @@ -0,0 +1,46 @@ +name: Publish to PyPI + +on: + push: + branches: [ "main" ] + release: + types: [published] + pull_request: + branches: [develop, main] + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-latest + environment: release + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + cache: 'pip' + - name: Install dependencies + run: | + # python -m pip install --upgrade pip + pip install hatch + - name: Build package + run: hatch build config-generator/apphub-configurator/ + + - name: Test package + run: hatch -e test run nose2 --verbose + - name: Publish package distributions to PyPI (main) + if: github.ref == 'refs/heads/main' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://upload.pypi.org/legacy/ + - name: Publish package distributions to PyPI (develop) + if: github.ref == 'refs/heads/develop' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ \ No newline at end of file From 4cad07a9281b34601e52e9659f0fcb3e4f301b2e Mon Sep 17 00:00:00 2001 From: pmembari Date: Thu, 27 Mar 2025 17:37:57 +0100 Subject: [PATCH 13/86] fix: edit pyproject with correct Terradue email --- config-generator/apphub-configurator/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config-generator/apphub-configurator/pyproject.toml b/config-generator/apphub-configurator/pyproject.toml index 3cbea85..e480390 100644 --- a/config-generator/apphub-configurator/pyproject.toml +++ b/config-generator/apphub-configurator/pyproject.toml @@ -11,7 +11,7 @@ requires-python = ">=3.10" license = "Apache-2.0" keywords = ["kubernetes", "configuration", "EOEPCA"] authors = [ - { name = "Parham Membari", email = "parham.membari@terradue.com" } + { name = "Terradue", email = "info@terradue.com" } ] classifiers = [ "Development Status :: 4 - Beta", From b571955e25bbd24bcaa2fe7f031c441aa4510050 Mon Sep 17 00:00:00 2001 From: pmembari Date: Thu, 27 Mar 2025 17:38:37 +0100 Subject: [PATCH 14/86] ci: create pipy package --- .github/workflows/package.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index f1d80f0..2568a9d 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -2,7 +2,7 @@ name: Publish to PyPI on: push: - branches: [ "main" ] + branches: [ "ESAEOEPCA-236" ] release: types: [published] pull_request: From 5c73bc696243b84c6a634bcaf4ecec8c1aa60fef Mon Sep 17 00:00:00 2001 From: pmembari Date: Thu, 27 Mar 2025 17:50:20 +0100 Subject: [PATCH 15/86] ci: remove test --- .github/workflows/package.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index 2568a9d..2c5581c 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -32,8 +32,6 @@ jobs: - name: Build package run: hatch build config-generator/apphub-configurator/ - - name: Test package - run: hatch -e test run nose2 --verbose - name: Publish package distributions to PyPI (main) if: github.ref == 'refs/heads/main' uses: pypa/gh-action-pypi-publish@release/v1 From b91f81a592de0b62d6e11f52f93305c357f11764 Mon Sep 17 00:00:00 2001 From: pmembari Date: Thu, 27 Mar 2025 17:53:22 +0100 Subject: [PATCH 16/86] ci: add branch --- .github/workflows/package.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index 2c5581c..31e411c 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -6,7 +6,7 @@ on: release: types: [published] pull_request: - branches: [develop, main] + branches: [develop, main, ESAEOEPCA-236] permissions: contents: read From 64f512a71c6e063809dcbd649a5d73a9f98fdd1b Mon Sep 17 00:00:00 2001 From: pmembari Date: Thu, 27 Mar 2025 17:54:29 +0100 Subject: [PATCH 17/86] ci: add branch --- .github/workflows/package.yaml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index 31e411c..9c47d94 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -6,7 +6,7 @@ on: release: types: [published] pull_request: - branches: [develop, main, ESAEOEPCA-236] + branches: [develop, main] permissions: contents: read @@ -38,7 +38,16 @@ jobs: with: repository-url: https://upload.pypi.org/legacy/ - name: Publish package distributions to PyPI (develop) - if: github.ref == 'refs/heads/develop' + if: github.ref == 'refs/heads/develop' uses: pypa/gh-action-pypi-publish@release/v1 with: - repository-url: https://test.pypi.org/legacy/ \ No newline at end of file + repository-url: https://test.pypi.org/legacy/ + + - name: Publish package distributions to PyPI (ESAEOEPCA-236) + if: github.ref == 'refs/heads/ESAEOEPCA-236' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + + \ No newline at end of file From 89745187bd957ff424d73c1a3216d9327a660028 Mon Sep 17 00:00:00 2001 From: parham-membari-terradue Date: Mon, 31 Mar 2025 12:05:59 +0200 Subject: [PATCH 18/86] ci: edit package ci --- .github/workflows/package.yaml | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index 9c47d94..cab9bbe 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -2,7 +2,7 @@ name: Publish to PyPI on: push: - branches: [ "ESAEOEPCA-236" ] + branches: [ "ESAEOEPCA-236", "patch-1" ] release: types: [published] pull_request: @@ -38,16 +38,7 @@ jobs: with: repository-url: https://upload.pypi.org/legacy/ - name: Publish package distributions to PyPI (develop) - if: github.ref == 'refs/heads/develop' + if: github.ref != 'refs/heads/main' uses: pypa/gh-action-pypi-publish@release/v1 with: - repository-url: https://test.pypi.org/legacy/ - - - name: Publish package distributions to PyPI (ESAEOEPCA-236) - if: github.ref == 'refs/heads/ESAEOEPCA-236' - uses: pypa/gh-action-pypi-publish@release/v1 - with: - repository-url: https://test.pypi.org/legacy/ - - - \ No newline at end of file + repository-url: https://test.pypi.org/legacy/ \ No newline at end of file From 03026a03f7e9f5fb88805fbe462e8e0657620d05 Mon Sep 17 00:00:00 2001 From: "Michele L." <37748899+xmichele@users.noreply.github.com> Date: Mon, 31 Mar 2025 12:30:11 +0200 Subject: [PATCH 19/86] Update README.md with app-level description --- README.md | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index adb8ea6..24cd45a 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ An Application pod runs in a dedicated namespace that may have configurations su The Application pod contextualization targets providing: - **runtime environment variables** -- **runtime configuration files such as S3 configuration files or docker config files** -- **volumes mounted such as the workspace volume** +- **runtime configuration files, such as S3 configuration files or docker config files** +- **volumes mounted, such as the workspace volume** - **Multi-User/Profile** The Application pod contextualization takes as input a 'profile' and is handled by the kube spawner providing: @@ -98,11 +98,35 @@ Below an example of configuration in the ApplicationHub: node_selector: k8s.provider.com/pool-name: node-pool-a ``` +Breakdown: -In the application Dockerfile, after having pre-installed with pip install jhsingle-native-proxy>=0.0.9, the execution entrypoint in the Dockerfile is typically: +- id: the profile identifier of your app +- groups: the group list containing the users that can use the declared app +- definition: display name, reference slug identifying the app, cpu/ram requirements alloted for it, reference docker image for the app-level +- default_url: default uri where to find the app +- node_selector: identifies on which node pool the app is executed + +To have a compliant candidate app to be added to the AppHub, it must comply with the proper Dockerfile definition before adding this into the configuration. +In addition it must be pushed on a registry accessible from the AppHub deployment, e.g. eoepca/iga-remote-desktop_qgis in the example just above. +Typically the Dockerfile starts with eoepca/iga-remote-desktop base image and then the specific app-level dependencies are added + +``` +FROM eoepca/iga-remote-desktop:1.1.2 +USER root + +#Install your app with dependencies and add to PATH + +RUN chown -R $NB_UID:$NB_GID $HOME +USER $NB_USER +``` + + +In the Dockerfile, after having pre-installed with pip install jhsingle-native-proxy>=0.0.9, the execution entrypoint in the Dockerfile is typically: CMD ["jhsingle-native-proxy", "--destport", "", "", "", "{--}server.port", "{port}", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", ""] +E.g. + CMD ["jhsingle-native-proxy", "--destport", "8505", "streamlit", "hello", "{--}server.port", "8888", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", "8888"] @@ -114,7 +138,7 @@ Params breakdown: --port $port: This sets the PROXY server's external port number to $port. -The {--} syntax is used to pass arguments to the Streamlit command. +The {--} syntax is used to pass arguments to the app, e.g. Streamlit, command itself: {--}server.port {port}: server.port {port} sets the port number on which the Streamlit SERVER will run. Equivalently {--}bind-addr 0.0.0.0:$port {--}server.headless: True ensures that Streamlit runs in headless mode, meaning it does not require a graphical user interface (GUI). @@ -127,14 +151,10 @@ Generalised command: jhsingle-native-proxy --destport $destport --authtype none {--}server.port {port} {--}server.headless True {--}server.enableCORS False --port $port ``` -In addition to the usual jhsingle-native-proxy params list, an application, e.g. PDE Code Server, can leverage a user data path mapping and its VS code loading, e.g. +In addition to the usual jhsingle-native-proxy params list, an application, e.g. as PDE Code Server, can leverage a user data path mapping and its VS code loading, e.g. with {--}user-data-dir and CODE_SERVER_WS: ``` CODE_SERVER_WS="/workspace" jhsingle-native-proxy --port 8888 --destport $destport code-server {--}auth none {--}bind-addr 0.0.0.0:$destport {--}user-data-dir /workspace $CODE_SERVER_WS ``` -[![Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.](https://www.repostatus.org/badges/latest/wip.svg)](https://www.repostatus.org/#wip) - - - From 563547d8e38ced878a65462a1dc7e76032126fb9 Mon Sep 17 00:00:00 2001 From: "Michele L." <37748899+xmichele@users.noreply.github.com> Date: Mon, 31 Mar 2025 15:59:24 +0200 Subject: [PATCH 20/86] Update README.md with reorganisation of the charters and jhsingle-native vs jupyter-server based Dockerfiles --- README.md | 57 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 24cd45a..67dfd40 100644 --- a/README.md +++ b/README.md @@ -74,13 +74,10 @@ Below an example of configuration in the ApplicationHub: k8s.provider.com/pool-name: node-pool-a ``` - -# QGIS via Remote Desktop -This interactive application provides a remote desktop with QGIS via JupyterHub. -It relies on the [jupyter-remote-desktop-proxy](https://github.com/jupyterhub/jupyter-remote-desktop-proxy) - ## ApplicationHubConfiguration -Below an example of configuration in the ApplicationHub: +Below an example of configuration in the ApplicationHub +This interactive application provides a remote desktop with QGIS via JupyterHub. +It relies on the [jupyter-remote-desktop-proxy](https://github.com/jupyterhub/jupyter-remote-desktop-proxy) and [app-repo](https://github.com/EOEPCA/iga-remote-desktop-qgis/) ```yaml - id: profile_iga_remote_desktop_qgis @@ -101,17 +98,26 @@ Below an example of configuration in the ApplicationHub: Breakdown: - id: the profile identifier of your app -- groups: the group list containing the users that can use the declared app +- groups: the group list containing the users groups that can use the declared app - definition: display name, reference slug identifying the app, cpu/ram requirements alloted for it, reference docker image for the app-level - default_url: default uri where to find the app - node_selector: identifies on which node pool the app is executed -To have a compliant candidate app to be added to the AppHub, it must comply with the proper Dockerfile definition before adding this into the configuration. -In addition it must be pushed on a registry accessible from the AppHub deployment, e.g. eoepca/iga-remote-desktop_qgis in the example just above. +To have a compliant candidate app to be added to the AppHub, it must comply with the proper Dockerfile definition before reference its produced docker image into the configuration. +In addition it must be pushed on a registry accessible from the AppHub deployment. +These images expose a service on a given port. +There are two options to have JupyterHub to proxy these applications: +- jupyter-server-proxy +- jhsingle-native-proxy + +# jupyter-server-proxy approach +The jupyter-server-proxy exposes the application alongside with the JupyterLab instance whilst jhsingle-native-proxy proxies the application without a running JupyterLab instance. + +In example the eoepca/iga-remote-desktop_qgis uses the approach with jupyter-server-proxy. Typically the Dockerfile starts with eoepca/iga-remote-desktop base image and then the specific app-level dependencies are added ``` -FROM eoepca/iga-remote-desktop:1.1.2 +FROM eoepca/iga-remote-desktop USER root #Install your app with dependencies and add to PATH @@ -120,14 +126,32 @@ RUN chown -R $NB_UID:$NB_GID $HOME USER $NB_USER ``` +# jhsingle-native-proxy approach +The jhsingle-native-proxy approach instead is based on a Dockerfile following this flow: + +``` +FROM python +RUN pip3 install \ + jhsingle-native-proxy>=0.0.9 \ + + +# create a user, since we don't want to run as root +RUN useradd -m jovyan +ENV HOME=/home/jovyan +WORKDIR $HOME +USER jovyan + +EXPOSE 8888 +CMD ["jhsingle-native-proxy", "--destport", "8505", "streamlit", "hello", "{--}server.port", "{port}", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", "8888"] +``` -In the Dockerfile, after having pre-installed with pip install jhsingle-native-proxy>=0.0.9, the execution entrypoint in the Dockerfile is typically: +So after having pip installed the jhsingle-native-proxy, the execution entrypoint in the Dockerfile is typically: -CMD ["jhsingle-native-proxy", "--destport", "", "", "", "{--}server.port", "{port}", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", ""] +CMD ["jhsingle-native-proxy", "--destport", "", "", "", "{--}server.port", "{port}", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", ""] E.g. -CMD ["jhsingle-native-proxy", "--destport", "8505", "streamlit", "hello", "{--}server.port", "8888", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", "8888"] +CMD ["jhsingle-native-proxy", "--destport", "8505", "streamlit", "hello", "{--}server.port", "8888", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", "8888"] Params breakdown: @@ -146,12 +170,7 @@ The {--} syntax is used to pass arguments to the app, e.g. Streamlit, command it {--}server.enableCORS: False disables Cross-Origin Resource Sharing (CORS), which is useful when running behind a proxy. -Generalised command: -``` -jhsingle-native-proxy --destport $destport --authtype none {--}server.port {port} {--}server.headless True {--}server.enableCORS False --port $port -``` - -In addition to the usual jhsingle-native-proxy params list, an application, e.g. as PDE Code Server, can leverage a user data path mapping and its VS code loading, e.g. with {--}user-data-dir and CODE_SERVER_WS: +In addition to the usual jhsingle-native-proxy params list an could require an user data path mapping and its VS code loading, e.g. with {--}user-data-dir & CODE_SERVER_WS: ``` CODE_SERVER_WS="/workspace" From cb7bb671756b15e3ea0b1ca84a9862e5405fb9c9 Mon Sep 17 00:00:00 2001 From: "Michele L." <37748899+xmichele@users.noreply.github.com> Date: Mon, 31 Mar 2025 16:07:41 +0200 Subject: [PATCH 21/86] Update README.md for formatting --- README.md | 67 ++++++++++--------------------------------------------- 1 file changed, 12 insertions(+), 55 deletions(-) diff --git a/README.md b/README.md index 67dfd40..34aa9a6 100644 --- a/README.md +++ b/README.md @@ -24,39 +24,10 @@ The Application pod contextualization takes as input a 'profile' and is handled - **authentication/authorization** - **pre-spawn and post-stop hooks** -# SNAP via Remote Desktop -This interactive application provides a remote desktop with SNAP via JupyterHub. -It relies on the [jupyter-remote-desktop-proxy](https://github.com/jupyterhub/jupyter-remote-desktop-proxy) - -## ApplicationHubConfiguration - -Below an example of configuration in the ApplicationHub: - -```yaml -- id: profile_iga_remote_desktop_snap - groups: - - group-1 - definition: - display_name: SNAP - slug: iga_remote_desktop_snap - default: False - kubespawner_override: - cpu_limit: 1 - mem_limit: 4G - image: eoepca/iga-remote-desktop_snap - default_url: "desktop" - node_selector: - k8s.provider.com/pool-name: node-pool-a -``` - -# Remote desktop -This interactive application provides a remote desktop via JupyterHub. -It relies on the [jupyter-remote-desktop-proxy](https://github.com/jupyterhub/jupyter-remote-desktop-proxy) - -## ApplicationHubConfiguration -Below an example of configuration in the ApplicationHub: +# ApplicationHubConfiguration +Below an example of [configuration](https://github.com/EOEPCA/helm-charts-dev/blob/develop/charts/application-hub/files/hub/config.yml) section in the ApplicationHub: ```yaml - id: profile_iga_remote_desktop groups: @@ -74,27 +45,6 @@ Below an example of configuration in the ApplicationHub: k8s.provider.com/pool-name: node-pool-a ``` -## ApplicationHubConfiguration -Below an example of configuration in the ApplicationHub -This interactive application provides a remote desktop with QGIS via JupyterHub. -It relies on the [jupyter-remote-desktop-proxy](https://github.com/jupyterhub/jupyter-remote-desktop-proxy) and [app-repo](https://github.com/EOEPCA/iga-remote-desktop-qgis/) - -```yaml -- id: profile_iga_remote_desktop_qgis - groups: - - group-1 - definition: - display_name: QGIS - slug: iga_remote_desktop_qgis - default: False - kubespawner_override: - cpu_limit: 1 - mem_limit: 4G - image: eoepca/iga-remote-desktop_qgis - default_url: "desktop" - node_selector: - k8s.provider.com/pool-name: node-pool-a -``` Breakdown: - id: the profile identifier of your app @@ -110,10 +60,12 @@ There are two options to have JupyterHub to proxy these applications: - jupyter-server-proxy - jhsingle-native-proxy -# jupyter-server-proxy approach +## jupyter-server-proxy approach The jupyter-server-proxy exposes the application alongside with the JupyterLab instance whilst jhsingle-native-proxy proxies the application without a running JupyterLab instance. In example the eoepca/iga-remote-desktop_qgis uses the approach with jupyter-server-proxy. +It relies on the [jupyter-remote-desktop-proxy](https://github.com/jupyterhub/jupyter-remote-desktop-proxy) and [app-repo](https://github.com/EOEPCA/iga-remote-desktop-qgis/) + Typically the Dockerfile starts with eoepca/iga-remote-desktop base image and then the specific app-level dependencies are added ``` @@ -126,7 +78,7 @@ RUN chown -R $NB_UID:$NB_GID $HOME USER $NB_USER ``` -# jhsingle-native-proxy approach +## jhsingle-native-proxy approach The jhsingle-native-proxy approach instead is based on a Dockerfile following this flow: ``` @@ -145,14 +97,19 @@ EXPOSE 8888 CMD ["jhsingle-native-proxy", "--destport", "8505", "streamlit", "hello", "{--}server.port", "{port}", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", "8888"] ``` +It relies on the [jhsingle-native-proxy](https://github.com/ideonate/jhsingle-native-proxy) and [app-repo](https://github.com/EOEPCA/iga-streamlit-demo) + So after having pip installed the jhsingle-native-proxy, the execution entrypoint in the Dockerfile is typically: +``` CMD ["jhsingle-native-proxy", "--destport", "", "", "", "{--}server.port", "{port}", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", ""] +``` E.g. +``` CMD ["jhsingle-native-proxy", "--destport", "8505", "streamlit", "hello", "{--}server.port", "8888", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", "8888"] - +``` Params breakdown: From a2396d69ea6a4bd210231728bd6b29fe792dcd9e Mon Sep 17 00:00:00 2001 From: "Michele L." <37748899+xmichele@users.noreply.github.com> Date: Mon, 31 Mar 2025 16:23:02 +0200 Subject: [PATCH 22/86] Update README.md with docker auth for images pulling --- README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 34aa9a6..8bd0ecc 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Breakdown: To have a compliant candidate app to be added to the AppHub, it must comply with the proper Dockerfile definition before reference its produced docker image into the configuration. In addition it must be pushed on a registry accessible from the AppHub deployment. -These images expose a service on a given port. +These images must expose a service on a given port. There are two options to have JupyterHub to proxy these applications: - jupyter-server-proxy - jhsingle-native-proxy @@ -134,3 +134,26 @@ CODE_SERVER_WS="/workspace" jhsingle-native-proxy --port 8888 --destport $destport code-server {--}auth none {--}bind-addr 0.0.0.0:$destport {--}user-data-dir /workspace $CODE_SERVER_WS ``` + +## Private app images pulling +The [docker-config](https://github.com/EOEPCA/helm-charts-dev/blob/f9c77a1e850c8e061de8b113ddcdcfd367b7fc0e/charts/application-hub/files/hub/config.yml#L198) configmap contains container registry authorization definitions. +It is a config map mounted on the pod. +This file enables the pulling of the app images from container registries. + +Example: +``` +{ + "auths": { + "my-private-registry.com": { + "auth": "am…g1Ml4=" + }, + "docker-co.domain.com": { + "auth": "Zm…Q==" + } + } +} +``` + +In this example my-private-registry.com and docker-co.domain.com are added together with their credentials to let the apphub deployment pull some of their images as propedeutic action before the app exposure. + + From c1c4476a2c9cf76f1da0b6560c9571d28827d65f Mon Sep 17 00:00:00 2001 From: fzingaretti <31729673+fzingaretti@users.noreply.github.com> Date: Tue, 1 Apr 2025 11:01:39 +0200 Subject: [PATCH 23/86] Updated requirements.txt with psycopg2 --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index e35fc56..06c5314 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ kubernetes==31.0.0 loguru==0.7.2 +psycopg2==2.9.10 From 2385290ce8ed9e50cc464644b3518471b50335e9 Mon Sep 17 00:00:00 2001 From: fzingaretti <31729673+fzingaretti@users.noreply.github.com> Date: Tue, 1 Apr 2025 11:13:52 +0200 Subject: [PATCH 24/86] updated upload-artifact version from. 3 to 3.1.3 --- .github/workflows/.github-ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/.github-ci.yaml b/.github/workflows/.github-ci.yaml index 81eb52a..8fae34d 100644 --- a/.github/workflows/.github-ci.yaml +++ b/.github/workflows/.github-ci.yaml @@ -75,7 +75,7 @@ jobs: # Step 8: Upload Docker Image tar.gz as an artifact - name: Upload Docker Image Artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v3.1.3 with: name: docker-image-tar path: ${{ env.docker_image_application }}_${{ env.docker_image_version }}.tar.gz @@ -168,4 +168,4 @@ jobs: docker tag "${tag}" "docker.io/${{ env.docker_tag }}" docker tag "${tag}" "docker.io/${{ env.docker_tag_latest }}" docker push "docker.io/${{ env.docker_tag }}" - docker push "docker.io/${{ env.docker_tag_latest }}" \ No newline at end of file + docker push "docker.io/${{ env.docker_tag_latest }}" From f2f352cfb2aaa782dca7bc5f54bd787161db308c Mon Sep 17 00:00:00 2001 From: fzingaretti <31729673+fzingaretti@users.noreply.github.com> Date: Tue, 1 Apr 2025 11:16:01 +0200 Subject: [PATCH 25/86] Updated upload-artifact version --- .github/workflows/.github-ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/.github-ci.yaml b/.github/workflows/.github-ci.yaml index 8fae34d..0cf56c0 100644 --- a/.github/workflows/.github-ci.yaml +++ b/.github/workflows/.github-ci.yaml @@ -75,7 +75,7 @@ jobs: # Step 8: Upload Docker Image tar.gz as an artifact - name: Upload Docker Image Artifact - uses: actions/upload-artifact@v3.1.3 + uses: actions/upload-artifact@v4.6.2 with: name: docker-image-tar path: ${{ env.docker_image_application }}_${{ env.docker_image_version }}.tar.gz From 762ae8c99b037df70a225249b57479d392483d3e Mon Sep 17 00:00:00 2001 From: fzingaretti <31729673+fzingaretti@users.noreply.github.com> Date: Tue, 1 Apr 2025 11:25:19 +0200 Subject: [PATCH 26/86] Added postgresql-devel to the Dockerfile --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 598c405..851c12a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,7 @@ RUN microdnf update -y && \ gcc \ libcurl-devel \ openssl-devel \ + postgresql-devel \ && microdnf clean all # Installation of configurable-http-proxy via npm @@ -55,4 +56,4 @@ RUN cd /tmp && python3 setup.py install USER ${NB_USER} # Command to start jupyterhub -CMD ["jupyterhub", "--config", "/etc/jupyterhub/jupyterhub_config.py"] \ No newline at end of file +CMD ["jupyterhub", "--config", "/etc/jupyterhub/jupyterhub_config.py"] From cf2cc6c1704c3a56e50008a93cabd3ab6c6f18d7 Mon Sep 17 00:00:00 2001 From: fzingaretti <31729673+fzingaretti@users.noreply.github.com> Date: Tue, 1 Apr 2025 11:29:06 +0200 Subject: [PATCH 27/86] Updated download-artifact version --- .github/workflows/.github-ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/.github-ci.yaml b/.github/workflows/.github-ci.yaml index 0cf56c0..da5c940 100644 --- a/.github/workflows/.github-ci.yaml +++ b/.github/workflows/.github-ci.yaml @@ -128,7 +128,7 @@ jobs: # Step 5: Download Docker Image tar.gz Artifact - name: Download Docker Image Artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4.2.1 with: name: docker-image-tar From 659d6d35a686722842f25deb999f40d2d5f34f26 Mon Sep 17 00:00:00 2001 From: pmembari Date: Tue, 1 Apr 2025 16:37:42 +0200 Subject: [PATCH 28/86] feat: edit hatch env path --- .gitignore | 3 ++- .../examples/config-generator-eoepca-demo.ipynb | 6 +++--- config-generator/examples/config-generator.ipynb | 8 ++++---- config-generator/pyproject.toml | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index ec0db41..6994ab4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ values.yaml build _README.md dist -.env-config-generator \ No newline at end of file +.env-config-generator +hatch \ No newline at end of file diff --git a/config-generator/examples/config-generator-eoepca-demo.ipynb b/config-generator/examples/config-generator-eoepca-demo.ipynb index af5aa61..038eb37 100644 --- a/config-generator/examples/config-generator-eoepca-demo.ipynb +++ b/config-generator/examples/config-generator-eoepca-demo.ipynb @@ -8,7 +8,7 @@ { "data": { "text/plain": [ - "PosixPath('/home/p/Desktop/p/Terradue/EOEPCA/application-hub-context/config-generator/examples')" + "PosixPath('/home/t2/Desktop/p/EOEPCA/application-hub-context/config-generator/examples')" ] }, "execution_count": 1, @@ -706,7 +706,7 @@ ], "metadata": { "kernelspec": { - "display_name": "apphub_configurator", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -720,7 +720,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" + "version": "3.11.5" }, "orig_nbformat": 4 }, diff --git a/config-generator/examples/config-generator.ipynb b/config-generator/examples/config-generator.ipynb index 36f1e11..d59cd24 100644 --- a/config-generator/examples/config-generator.ipynb +++ b/config-generator/examples/config-generator.ipynb @@ -8,7 +8,7 @@ { "data": { "text/plain": [ - "PosixPath('/home/p/Desktop/p/Terradue/EOEPCA/application-hub-context/config-generator/examples')" + "PosixPath('/home/t2/Desktop/p/EOEPCA/application-hub-context/config-generator/examples')" ] }, "execution_count": 1, @@ -541,7 +541,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "/tmp/ipykernel_181750/2113836574.py:5: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/\n", + "/tmp/ipykernel_1697007/2113836574.py:5: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.11/migration/\n", " yaml.dump(config.dict(), file)\n" ] } @@ -587,7 +587,7 @@ ], "metadata": { "kernelspec": { - "display_name": "apphub_configurator", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -601,7 +601,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" + "version": "3.11.5" }, "orig_nbformat": 4 }, diff --git a/config-generator/pyproject.toml b/config-generator/pyproject.toml index 60265a9..fa221fd 100644 --- a/config-generator/pyproject.toml +++ b/config-generator/pyproject.toml @@ -41,9 +41,9 @@ examples = ["examples"] [tool.hatch.envs.default] -path = "/code/hatch/envs/apphub_configurator" +path = "hatch/envs/apphub_configurator" [tool.hatch.envs.prod] -path = "/code/hatch/envs/apphub_configurator" +path = "hatch/envs/apphub_configurator" dependencies = ["pytest", "pytest-cov", "pydantic", From b44ddc490bb61a4ce5ce338f220d89ad14dbfbe0 Mon Sep 17 00:00:00 2001 From: mlongobardo-gituname Date: Tue, 1 Apr 2025 17:09:00 +0200 Subject: [PATCH 29/86] generalising the app definition in confg.yaml --- README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8bd0ecc..471f3da 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,30 @@ The Application pod contextualization takes as input a 'profile' and is handled # ApplicationHubConfiguration -Below an example of [configuration](https://github.com/EOEPCA/helm-charts-dev/blob/develop/charts/application-hub/files/hub/config.yml) section in the ApplicationHub: +The [config-generator](./config-generator/) folder contains the hatch module for the jupyter notebook to support the generation of ApplicationHub configurations and its documentation is in the dedicated readme. +An example of its output is [config.yml](./files/hub/config.yml) +The definition of the application in the config.yml is addressed according to this pattern: + +```yaml +- id: + groups: + - + - + definition: + display_name: + slug: + default: + kubespawner_override: + cpu_limit: + mem_limit: + image: + default_url: + node_selector: + k8s.provider.com/pool-name: +``` + +E.g. + ```yaml - id: profile_iga_remote_desktop groups: From 96507af6e883200b1b96f932bf3030c0066a6600 Mon Sep 17 00:00:00 2001 From: mlongobardo-gituname Date: Tue, 1 Apr 2025 17:12:35 +0200 Subject: [PATCH 30/86] detail generalised sections --- README.md | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 471f3da..46798ee 100644 --- a/README.md +++ b/README.md @@ -116,8 +116,8 @@ ENV HOME=/home/jovyan WORKDIR $HOME USER jovyan -EXPOSE 8888 -CMD ["jhsingle-native-proxy", "--destport", "8505", "streamlit", "hello", "{--}server.port", "{port}", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", "8888"] +EXPOSE +CMD ["jhsingle-native-proxy", "--destport", "", "", "", "{--}server.port", "{port}", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", ""] ``` It relies on the [jhsingle-native-proxy](https://github.com/ideonate/jhsingle-native-proxy) and [app-repo](https://github.com/EOEPCA/iga-streamlit-demo) @@ -163,7 +163,21 @@ The [docker-config](https://github.com/EOEPCA/helm-charts-dev/blob/f9c77a1e850c8 It is a config map mounted on the pod. This file enables the pulling of the app images from container registries. +``` +{ + "auths": { + "my-registry-1.com": { + "auth": "" + }, + "my-registry-1.com": { + "auth": "" + } + } +} +``` + Example: + ``` { "auths": { @@ -177,6 +191,6 @@ Example: } ``` -In this example my-private-registry.com and docker-co.domain.com are added together with their credentials to let the apphub deployment pull some of their images as propedeutic action before the app exposure. +In this example my-private-registry.com and docker-co.domain.com are added together with their credentials to let the AppHub deployment pull some of their images as propedeutic action before the app exposure. From 82e7a6d4ff75e1a56284ceab01584ea7f831c7ce Mon Sep 17 00:00:00 2001 From: pmembari Date: Tue, 1 Apr 2025 17:52:51 +0200 Subject: [PATCH 31/86] docs: initial docs --- .../examples/config-generator-eoepca-demo.ipynb | 5 +++-- config-generator/examples/config-generator.ipynb | 9 ++++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/config-generator/examples/config-generator-eoepca-demo.ipynb b/config-generator/examples/config-generator-eoepca-demo.ipynb index 038eb37..f9b4e0a 100644 --- a/config-generator/examples/config-generator-eoepca-demo.ipynb +++ b/config-generator/examples/config-generator-eoepca-demo.ipynb @@ -184,7 +184,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## bash login\n" + "## bash login ...\n" ] }, { @@ -658,7 +658,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Configuration" + "## Configuration\n", + "The code below dump a `config.yaml` containing the information of App Hub." ] }, { diff --git a/config-generator/examples/config-generator.ipynb b/config-generator/examples/config-generator.ipynb index d59cd24..e12c89d 100644 --- a/config-generator/examples/config-generator.ipynb +++ b/config-generator/examples/config-generator.ipynb @@ -295,7 +295,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## init.sh script" + "## init.sh script ..." ] }, { @@ -362,6 +362,13 @@ ")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Better code" + ] + }, { "cell_type": "code", "execution_count": 11, From 4d9a7a661a594fa968c5acbb7496ec47c16624d4 Mon Sep 17 00:00:00 2001 From: mlongobardo-gituname Date: Wed, 2 Apr 2025 14:55:37 +0200 Subject: [PATCH 32/86] ESAEOEPCA-243 dedicated md for apphub config and its integration in mkdocs yml --- README.md | 169 ---------------------------------- docs/apphub-config-general.md | 166 +++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 3 files changed, 167 insertions(+), 169 deletions(-) create mode 100644 docs/apphub-config-general.md diff --git a/README.md b/README.md index 46798ee..39cb890 100644 --- a/README.md +++ b/README.md @@ -25,172 +25,3 @@ The Application pod contextualization takes as input a 'profile' and is handled - **pre-spawn and post-stop hooks** -# ApplicationHubConfiguration - -The [config-generator](./config-generator/) folder contains the hatch module for the jupyter notebook to support the generation of ApplicationHub configurations and its documentation is in the dedicated readme. -An example of its output is [config.yml](./files/hub/config.yml) -The definition of the application in the config.yml is addressed according to this pattern: - -```yaml -- id: - groups: - - - - - definition: - display_name: - slug: - default: - kubespawner_override: - cpu_limit: - mem_limit: - image: - default_url: - node_selector: - k8s.provider.com/pool-name: -``` - -E.g. - -```yaml -- id: profile_iga_remote_desktop - groups: - - group-1 - definition: - display_name: Remote Desktop - slug: iga_remote_desktop - default: False - kubespawner_override: - cpu_limit: 1 - mem_limit: 4G - image: eoepca/iga-remote-desktop - default_url: "desktop" - node_selector: - k8s.provider.com/pool-name: node-pool-a -``` - -Breakdown: - -- id: the profile identifier of your app -- groups: the group list containing the users groups that can use the declared app -- definition: display name, reference slug identifying the app, cpu/ram requirements alloted for it, reference docker image for the app-level -- default_url: default uri where to find the app -- node_selector: identifies on which node pool the app is executed - -To have a compliant candidate app to be added to the AppHub, it must comply with the proper Dockerfile definition before reference its produced docker image into the configuration. -In addition it must be pushed on a registry accessible from the AppHub deployment. -These images must expose a service on a given port. -There are two options to have JupyterHub to proxy these applications: -- jupyter-server-proxy -- jhsingle-native-proxy - -## jupyter-server-proxy approach -The jupyter-server-proxy exposes the application alongside with the JupyterLab instance whilst jhsingle-native-proxy proxies the application without a running JupyterLab instance. - -In example the eoepca/iga-remote-desktop_qgis uses the approach with jupyter-server-proxy. -It relies on the [jupyter-remote-desktop-proxy](https://github.com/jupyterhub/jupyter-remote-desktop-proxy) and [app-repo](https://github.com/EOEPCA/iga-remote-desktop-qgis/) - -Typically the Dockerfile starts with eoepca/iga-remote-desktop base image and then the specific app-level dependencies are added - -``` -FROM eoepca/iga-remote-desktop -USER root - -#Install your app with dependencies and add to PATH - -RUN chown -R $NB_UID:$NB_GID $HOME -USER $NB_USER -``` - -## jhsingle-native-proxy approach -The jhsingle-native-proxy approach instead is based on a Dockerfile following this flow: - -``` -FROM python -RUN pip3 install \ - jhsingle-native-proxy>=0.0.9 \ - - -# create a user, since we don't want to run as root -RUN useradd -m jovyan -ENV HOME=/home/jovyan -WORKDIR $HOME -USER jovyan - -EXPOSE -CMD ["jhsingle-native-proxy", "--destport", "", "", "", "{--}server.port", "{port}", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", ""] -``` - -It relies on the [jhsingle-native-proxy](https://github.com/ideonate/jhsingle-native-proxy) and [app-repo](https://github.com/EOEPCA/iga-streamlit-demo) - -So after having pip installed the jhsingle-native-proxy, the execution entrypoint in the Dockerfile is typically: - -``` -CMD ["jhsingle-native-proxy", "--destport", "", "", "", "{--}server.port", "{port}", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", ""] -``` - -E.g. - -``` -CMD ["jhsingle-native-proxy", "--destport", "8505", "streamlit", "hello", "{--}server.port", "8888", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", "8888"] -``` - -Params breakdown: - ---destport: specifies the destination port number where the target APPLICATION (in this case, Streamlit) will run. - ---authtype: none disables authentication, meaning the application will not require authentication through JupyterHub. - ---port $port: This sets the PROXY server's external port number to $port. - -The {--} syntax is used to pass arguments to the app, e.g. Streamlit, command itself: -{--}server.port {port}: server.port {port} sets the port number on which the Streamlit SERVER will run. Equivalently {--}bind-addr 0.0.0.0:$port - -{--}server.headless: True ensures that Streamlit runs in headless mode, meaning it does not require a graphical user interface (GUI). - -{--}server.enableCORS: False disables Cross-Origin Resource Sharing (CORS), which is useful when running behind a proxy. - - -In addition to the usual jhsingle-native-proxy params list an could require an user data path mapping and its VS code loading, e.g. with {--}user-data-dir & CODE_SERVER_WS: - -``` -CODE_SERVER_WS="/workspace" -jhsingle-native-proxy --port 8888 --destport $destport code-server {--}auth none {--}bind-addr 0.0.0.0:$destport {--}user-data-dir /workspace $CODE_SERVER_WS -``` - - -## Private app images pulling -The [docker-config](https://github.com/EOEPCA/helm-charts-dev/blob/f9c77a1e850c8e061de8b113ddcdcfd367b7fc0e/charts/application-hub/files/hub/config.yml#L198) configmap contains container registry authorization definitions. -It is a config map mounted on the pod. -This file enables the pulling of the app images from container registries. - -``` -{ - "auths": { - "my-registry-1.com": { - "auth": "" - }, - "my-registry-1.com": { - "auth": "" - } - } -} -``` - -Example: - -``` -{ - "auths": { - "my-private-registry.com": { - "auth": "am…g1Ml4=" - }, - "docker-co.domain.com": { - "auth": "Zm…Q==" - } - } -} -``` - -In this example my-private-registry.com and docker-co.domain.com are added together with their credentials to let the AppHub deployment pull some of their images as propedeutic action before the app exposure. - - diff --git a/docs/apphub-config-general.md b/docs/apphub-config-general.md new file mode 100644 index 0000000..cd1e1c8 --- /dev/null +++ b/docs/apphub-config-general.md @@ -0,0 +1,166 @@ +# ApplicationHubConfiguration + +The config-generator folder contains the hatch module for the jupyter notebook to support the generation of ApplicationHub configurations and its documentation is in the dedicated readme. +The definition of an application in the config.yml is addressed according to this pattern: + +```yaml +- id: + groups: + - + - + definition: + display_name: + slug: + default: + kubespawner_override: + cpu_limit: + mem_limit: + image: + default_url: + node_selector: + k8s.provider.com/pool-name: +``` + +E.g. + +```yaml +- id: profile_iga_remote_desktop + groups: + - group-1 + definition: + display_name: Remote Desktop + slug: iga_remote_desktop + default: False + kubespawner_override: + cpu_limit: 1 + mem_limit: 4G + image: eoepca/iga-remote-desktop + default_url: "desktop" + node_selector: + k8s.provider.com/pool-name: node-pool-a +``` + +Breakdown: + +- id: the profile identifier of your app +- groups: the group list containing the users groups that can use the declared app +- definition: display name, reference slug identifying the app, cpu/ram requirements alloted for it, reference docker image for the app-level +- default_url: default uri where to find the app +- node_selector: identifies on which node pool the app is executed + +To have a compliant candidate app to be added to the AppHub, it must comply with the proper Dockerfile definition before reference its produced docker image into the configuration. +In addition it must be pushed on a registry accessible from the AppHub deployment. +These images must expose a service on a given port. +There are two options to have JupyterHub to proxy these applications: +- jupyter-server-proxy +- jhsingle-native-proxy + +## jupyter-server-proxy approach +The jupyter-server-proxy exposes the application alongside with the JupyterLab instance whilst jhsingle-native-proxy proxies the application without a running JupyterLab instance. + +In example the eoepca/iga-remote-desktop_qgis uses the approach with jupyter-server-proxy. +It relies on the [jupyter-remote-desktop-proxy](https://github.com/jupyterhub/jupyter-remote-desktop-proxy) and [app-repo](https://github.com/EOEPCA/iga-remote-desktop-qgis/) + +Typically the Dockerfile starts with eoepca/iga-remote-desktop base image and then the specific app-level dependencies are added + +``` +FROM eoepca/iga-remote-desktop +USER root + +#Install your app with dependencies and add to PATH + +RUN chown -R $NB_UID:$NB_GID $HOME +USER $NB_USER +``` + +## jhsingle-native-proxy approach +The jhsingle-native-proxy approach instead is based on a Dockerfile following this flow: + +``` +FROM python +RUN pip3 install \ + jhsingle-native-proxy>=0.0.9 \ + + +# create a user, since we don't want to run as root +RUN useradd -m jovyan +ENV HOME=/home/jovyan +WORKDIR $HOME +USER jovyan + +EXPOSE +CMD ["jhsingle-native-proxy", "--destport", "", "", "", "{--}server.port", "{port}", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", ""] +``` + +It relies on the [jhsingle-native-proxy](https://github.com/ideonate/jhsingle-native-proxy) and [app-repo](https://github.com/EOEPCA/iga-streamlit-demo) + +So after having pip installed the jhsingle-native-proxy, the execution entrypoint in the Dockerfile is typically: + +``` +CMD ["jhsingle-native-proxy", "--destport", "", "", "", "{--}server.port", "{port}", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", ""] +``` + +E.g. + +``` +CMD ["jhsingle-native-proxy", "--destport", "8505", "streamlit", "hello", "{--}server.port", "8888", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", "8888"] +``` + +Params breakdown: + +--destport: specifies the destination port number where the target APPLICATION (in this case, Streamlit) will run. + +--authtype: none disables authentication, meaning the application will not require authentication through JupyterHub. + +--port $port: This sets the PROXY server's external port number to $port. + +The {--} syntax is used to pass arguments to the app, e.g. Streamlit, command itself: +{--}server.port {port}: server.port {port} sets the port number on which the Streamlit SERVER will run. Equivalently {--}bind-addr 0.0.0.0:$port + +{--}server.headless: True ensures that Streamlit runs in headless mode, meaning it does not require a graphical user interface (GUI). + +{--}server.enableCORS: False disables Cross-Origin Resource Sharing (CORS), which is useful when running behind a proxy. + + +In addition to the usual jhsingle-native-proxy params list an could require an user data path mapping and its VS code loading, e.g. with {--}user-data-dir & CODE_SERVER_WS: + +``` +CODE_SERVER_WS="/workspace" +jhsingle-native-proxy --port 8888 --destport $destport code-server {--}auth none {--}bind-addr 0.0.0.0:$destport {--}user-data-dir /workspace $CODE_SERVER_WS +``` + + +## Private app images pulling +The [docker-config](https://github.com/EOEPCA/helm-charts-dev/blob/f9c77a1e850c8e061de8b113ddcdcfd367b7fc0e/charts/application-hub/files/hub/config.yml#L198) configmap contains container registry authorization definitions. +It is a config map mounted on the pod. +This file enables the pulling of the app images from container registries. + +``` +{ + "auths": { + "my-registry-1.com": { + "auth": "" + }, + "my-registry-1.com": { + "auth": "" + } + } +} +``` + +Example: + +``` +{ + "auths": { + "my-private-registry.com": { + "auth": "am…g1Ml4=" + }, + "docker-co.domain.com": { + "auth": "Zm…Q==" + } + } +} +``` + +In this example my-private-registry.com and docker-co.domain.com are added together with their credentials to let the AppHub deployment pull some of their images as propedeutic action before the app exposure. diff --git a/mkdocs.yml b/mkdocs.yml index ee86f92..8d04fcb 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,3 +26,4 @@ nav: - Kubernetes: 'k8s.md' - Configuration: 'configuration.md' - JupyterHub API: 'jupyterhub-api.md' + - Config: 'apphub-config-general.md' From 1df792e388934a6e1fc5595339c409c901cc4936 Mon Sep 17 00:00:00 2001 From: parham-membari-terradue Date: Wed, 2 Apr 2025 15:47:40 +0200 Subject: [PATCH 33/86] feat: edit generate-config sh refrence --- skaffold.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skaffold.yaml b/skaffold.yaml index 54d1aed..559145f 100644 --- a/skaffold.yaml +++ b/skaffold.yaml @@ -31,7 +31,7 @@ profiles: hooks: before: - host: - command: ["sh", "-c", "config-generator/generate-config.sh"] + command: ["sh", "-c", "config-generator/examples/generate-config.sh"] os: [darwin, linux] manifests: rawYaml: From b868823c325fcc4b12701b2568a3069410be4e79 Mon Sep 17 00:00:00 2001 From: parham-membari-terradue Date: Wed, 2 Apr 2025 15:48:52 +0200 Subject: [PATCH 34/86] feat: update documentation --- .../config-generator-eoepca-demo.ipynb | 359 +++++++++++------- 1 file changed, 213 insertions(+), 146 deletions(-) diff --git a/config-generator/examples/config-generator-eoepca-demo.ipynb b/config-generator/examples/config-generator-eoepca-demo.ipynb index f9b4e0a..de3d7be 100644 --- a/config-generator/examples/config-generator-eoepca-demo.ipynb +++ b/config-generator/examples/config-generator-eoepca-demo.ipynb @@ -1,5 +1,28 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Generate App Hub's configuration\n", + "\n", + "This notebook aim to produce a `config.yml` file to generate all the configuration needs to be set for deployment of App Hub on a remote cluster using Devops tools. \n", + "\n", + "This Notebook can be used for the following requirements:\n", + "\n", + "* Initial dependencies\n", + "* Create the configuration for kubernetes' `PV`s, and `PVC`s(e.g. workspace volume, and calrissian volume)\n", + "* Generate the configuration for kubernetes' config maps\n", + "* Creation of different Profiles \n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initial dependencies" + ] + }, { "cell_type": "code", "execution_count": 1, @@ -8,7 +31,7 @@ { "data": { "text/plain": [ - "PosixPath('/home/t2/Desktop/p/EOEPCA/application-hub-context/config-generator/examples')" + "PosixPath('/home/p/Desktop/p/Terradue/EOEPCA/application-hub-context/config-generator/examples')" ] }, "execution_count": 1, @@ -21,6 +44,8 @@ "from apphub_configurator.models import *\n", "from pathlib import Path\n", "import os\n", + "import json\n", + "from pprint import pprint\n", "current_dir = Path(os.getcwd())\n", "parent_dir = current_dir.parent\n", "current_dir" @@ -31,7 +56,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Configuration\n", + "### Configuration\n", "\n" ] }, @@ -55,7 +80,10 @@ "source": [ "## Volumes\n", "\n", - "Create the Volumes" + "In this section, the user will provide the data class objects for creation of volume:\n", + "- Volume for workspace\n", + "- Volume for calrissian\n", + "These two configuration will be use " ] }, { @@ -64,26 +92,37 @@ "metadata": {}, "source": [ "### Workspace Volume\n", - "\n", - "The workspace volume is persisted." + "In this section, the user will providing the configuration of a kubernetes Volume for development environment (i.e workspace). It is important to mention that this volume must keep data persistently. therefore, the user set `persist=True`" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "workspace_volume = Volume(\n", + "workspace_volume = Volume( # type: ignore\n", " name=\"workspace-volume\",\n", " size=workspace_volume_size,\n", " claim_name=\"workspace-claim\",\n", " mount_path=\"/workspace\",\n", " storage_class=storage_class_rwo,\n", " access_modes=[\"ReadWriteOnce\"],\n", - " volume_mount=VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"),\n", - " persist=False,\n", - ")" + " volume_mount=VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"), # type: ignore\n", + " persist=True,\n", + ")\n", + "workspace_volume" ] }, { @@ -92,25 +131,36 @@ "metadata": {}, "source": [ "### Calrissian Volume\n", - "\n", - "This is a RWX volume, not persisted" + "In this section, the user will configure a **Kubernetes Volume** for Calrissian jobs. Since the job runs within Calrissian and does not require data retention on the Calrissian pod, therefore the user should set `persist=False`. " ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "calrissian_volume = Volume(\n", + "calrissian_volume = Volume( # type: ignore\n", " name=\"calrissian-volume\",\n", " claim_name=\"calrissian-claim\",\n", " size=calrissian_volume_size,\n", " storage_class=storage_class_rwx,\n", " access_modes=[\"ReadWriteMany\"],\n", - " volume_mount=VolumeMount(name=\"calrissian-volume\", mount_path=\"/calrissian\"),\n", + " volume_mount=VolumeMount(name=\"calrissian-volume\", mount_path=\"/calrissian\"), # type: ignore\n", " persist=False,\n", - ")" + ")\n", + "calrissian_volume" ] }, { @@ -120,7 +170,12 @@ "source": [ "## ConfigMaps\n", "\n", - "These configmaps are mounted as files on the pod." + "In this section, the user will provide the configuration for some kubernetes configmaps that is commonly used in this notebook including:\n", + "- bash login Configmap\n", + "- bash rc Configmap\n", + "- QGIS Configmap\n", + "- Stage-in/out Configmap\n", + "These configmaps will be mounted as files on different pods. " ] }, { @@ -130,7 +185,7 @@ "source": [ "### bash login\n", "\n", - "This file is used for the JupyterLab Terminal configuration" + "This configmap file aims to configure the Terminal across different profiles. " ] }, { @@ -143,7 +198,7 @@ "with open(bash_login_file_path, \"r\") as f:\n", " content = f.read()\n", "\n", - "bash_login_cm = ConfigMap(\n", + "bash_login_cm = ConfigMap( # type: ignore\n", " name=\"bash-login\",\n", " key=\"bash-login\",\n", " content=content,\n", @@ -158,7 +213,10 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### bash.rc\n" + "### bash-rc \n", + "This section explains how we configure a **Kubernetes ConfigMap** to manage the `.bashrc` file inside a pod. This config map will:\n", + "- It provides useful aliases and environment settings for users.\n", + "- It ensures a consistent environment across different pods." ] }, { @@ -170,7 +228,7 @@ "bash_rc_cm_file_path = os.path.join(parent_dir, \"config-maps/bash-rc\") \n", "with open(bash_rc_cm_file_path, \"r\") as f:\n", " content = f.read()\n", - "bash_rc_cm = ConfigMap(\n", + "bash_rc_cm = ConfigMap( # type: ignore\n", " name=\"bash-rc\",\n", " key=\"bash-rc\",\n", " content=content,\n", @@ -184,7 +242,8 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## bash login ...\n" + "### QGIS Configmap\n", + "The cell below will provide a Configmap configuration for QGIS setup." ] }, { @@ -193,26 +252,26 @@ "metadata": {}, "outputs": [], "source": [ - "bash_login_file_path = os.path.join(parent_dir, \"config-maps/bash-login\") \n", - "with open(bash_login_file_path, \"r\") as f:\n", + "init_qgis_file_path = os.path.join(parent_dir, \"config-maps/init-qgis.sh\") \n", + "with open(init_qgis_file_path, \"r\") as f:\n", " content = f.read()\n", "\n", - "bash_login_cm = ConfigMap(\n", - " name=\"bash-login\",\n", - " key=\"bash-login\",\n", + "init_qgis_cm = ConfigMap( # type: ignore\n", + " name=\"init\",\n", + " key=\"init\",\n", " content=content,\n", " readonly=True,\n", " persist=False,\n", - " mount_path=\"/workspace/.bash_login\",\n", + " mount_path=\"/opt/init/.init.sh\",\n", ")" ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "## Profiles" + "### Stage-in/out Configmap\n", + "The cell below will provide a Configmap configuration for stage-in/out for [e-learning](#e-learning) profile." ] }, { @@ -220,6 +279,68 @@ "execution_count": 8, "metadata": {}, "outputs": [], + "source": [ + "init_stac_file_path = os.path.join(parent_dir, \"config-maps/init-stac.sh\")\n", + "with open(init_stac_file_path, \"r\") as f:\n", + " content = f.read()\n", + "\n", + "init_stac_cm = ConfigMap( # type: ignore\n", + " name=\"init\",\n", + " key=\"init\",\n", + " content=content,\n", + " readonly=True,\n", + " persist=False,\n", + " mount_path=\"/opt/init/.init.sh\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Coder init Configmap\n", + "The cell below will provide a Configmap configuration for stage-in/out for [coder](#coder) profile." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "init_cm_file_path = os.path.join(parent_dir, \"config-maps/init.sh\") \n", + "with open(init_cm_file_path, \"r\") as f:\n", + " content = f.read()\n", + "\n", + "init_coder_cm = ConfigMap(\n", + " name=\"init\",\n", + " key=\"init\",\n", + " content=content,\n", + " readonly=True,\n", + " persist=False,\n", + " mount_path=\"/opt/init/.init.sh\",\n", + " default_mode=\"0660\",\n", + ")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Profiles\n", + "In the following section, the user will generate the configuration for different profiles on a remote cluster including:\n", + "- [Coder](#coder)\n", + "- [Jupyter Lab](#jupyterlab)\n", + "- [e-learning](#e-learning)\n", + "- [QGIS](#qgis)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], "source": [ "profiles = []" ] @@ -234,9 +355,21 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "coders = {\n", " \"coder1\": {\n", @@ -254,18 +387,18 @@ "}\n", "\n", "for key, value in coders.items():\n", - " coder_definition = ProfileDefinition(\n", + " coder_definition = ProfileDefinition( # type: ignore\n", " display_name=value[\"display_name\"],\n", " slug=value[\"slug\"],\n", " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", " cpu_limit=value[\"cpu_limit\"],\n", " mem_limit=value[\"mem_limit\"],\n", " image=\"eoepca/pde-code-server:develop\",\n", " ),\n", " )\n", "\n", - " coder_profile = Profile(\n", + " coder_profile = Profile( # type: ignore\n", " id=f\"profile_studio_{key}\",\n", " groups=[\"group-a\", \"group-b\"],\n", " definition=coder_definition,\n", @@ -280,59 +413,60 @@ " },\n", " )\n", "\n", - " profiles.append(coder_profile)" + " profiles.append(coder_profile)\n", + "profiles" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## init.sh script" + "### init.sh script" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "init_cm_file_path = os.path.join(parent_dir, \"config-maps/init.sh\") \n", - "with open(init_cm_file_path, \"r\") as f:\n", - " content = f.read()\n", - "\n", - "init_cm = ConfigMap(\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - " default_mode=\"0660\",\n", - ")\n", "\n", "\n", - "init_context_volume_mount = InitContainerVolumeMount(\n", + "init_context_volume_mount = InitContainerVolumeMount( # type: ignore\n", " mount_path=\"/opt/init/.init.sh\", name=\"init\", sub_path=\"init\"\n", ")\n", - "init_container = InitContainer(\n", + "init_container = InitContainer( # type: ignore\n", " name=\"init-file-on-volume\",\n", " image=\"eoepca/pde-code-server:develop\",\n", " command=[\"sh\", \"-c\", \"sh /opt/init/.init.sh\"],\n", " volume_mounts=[\n", - " VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"),\n", + " VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"), # type: ignore\n", " init_context_volume_mount,\n", " ],\n", ")\n", "\n", - "eoepca_demo_init_script_profile = Profile(\n", + "eoepca_demo_init_script_profile = Profile( # type: ignore\n", " id=f\"profile_demo_init_script\",\n", " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", + " definition=ProfileDefinition( # type: ignore\n", " display_name=\"Coder demo init script\",\n", " description=\"This profile is used to demonstrate the use of an init script\",\n", " slug=\"eoepca_demo_init_script\",\n", " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", " cpu_guarantee=1,\n", " cpu_limit=2,\n", " mem_guarantee=\"4G\",\n", @@ -342,7 +476,7 @@ " ),\n", " node_selector={},\n", " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[init_cm],\n", + " config_maps=[init_coder_cm],\n", " pod_env_vars={\n", " \"HOME\": \"/workspace\",\n", " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", @@ -351,72 +485,36 @@ " \"CODE_SERVER_WS\": \"/workspace/mastering-app-package\",\n", " },\n", " init_containers=[init_container],\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ + ")\n", "profiles.append(eoepca_demo_init_script_profile)\n", - "\n", - "eoepca_demo_init_script_profile = Profile(\n", - " id=f\"profile_demo_init_script\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Coder demo init script\",\n", - " description=\"This profile is used to demonstrate the use of an init script\",\n", - " slug=\"eoepca_demo_init_script\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[init_cm],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " \"CONDARC\": \"/workspace/.condarc\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"CODE_SERVER_WS\": \"/workspace/mastering-app-package\",\n", - " },\n", - " init_containers=[init_container],\n", - ")" + "profiles" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## JupyterLab" + "### JupyterLab" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "image = \"jupyter/scipy-notebook\"\n", "\n", "\n", - "eoepca_jupyter_lab_profile = Profile(\n", + "eoepca_jupyter_lab_profile = Profile( # type: ignore\n", " id=\"profile_jupyter_lab\",\n", " groups=[\"group-c\"],\n", - " definition=ProfileDefinition(\n", + " definition=ProfileDefinition( # type: ignore\n", " display_name=\"Jupyter Lab\",\n", " description=\"Jupyter Lab with Python 3.11\",\n", " slug=\"eoepca_jupyter_lab\",\n", " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", " cpu_guarantee=1,\n", " cpu_limit=2,\n", " mem_guarantee=\"4G\",\n", @@ -441,16 +539,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Image pull secret\n" + "Image pull secret\n" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "image_pull_secret = ImagePullSecret(\n", + "image_pull_secret = ImagePullSecret( # type: ignore\n", " name=\"cr-config\",\n", " persist=False,\n", " data=\"ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9\",\n", @@ -496,32 +594,12 @@ "profiles.append(eoepca_jupyter_lab_profile_2)" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Stage-in/out" - ] - }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "init_stac_file_path = os.path.join(parent_dir, \"config-maps/init-stac.sh\")\n", - "with open(init_stac_file_path, \"r\") as f:\n", - " content = f.read()\n", - "\n", - "init_cm = ConfigMap(\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - ")" - ] + "source": [] }, { "cell_type": "code", @@ -547,12 +625,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## e-learning" + "### e-learning" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -576,7 +654,7 @@ " ),\n", " node_selector={},\n", " volumes=[workspace_volume],\n", - " config_maps=[init_cm, bash_rc_cm, bash_login_cm],\n", + " config_maps=[init_stac_cm, bash_rc_cm, bash_login_cm],\n", " pod_env_vars={\n", " \"HOME\": \"/workspace\",\n", " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", @@ -604,27 +682,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## QGIS" + "### QGIS" ] }, { "cell_type": "code", - "execution_count": 18, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "init_qgis_file_path = os.path.join(parent_dir, \"config-maps/init-qgis.sh\") \n", - "with open(init_qgis_file_path, \"r\") as f:\n", - " content = f.read()\n", "\n", - "init_qgis_cm = ConfigMap(\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - ")\n", "\n", "image = \"eoepca/iga-remote-desktop-qgis:1.1.3\"\n", "\n", @@ -658,7 +725,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Configuration\n", + "## Write Configuration\n", "The code below dump a `config.yaml` containing the information of App Hub." ] }, @@ -707,7 +774,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "apphub_configurator", "language": "python", "name": "python3" }, @@ -721,7 +788,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.12.2" }, "orig_nbformat": 4 }, From 833ba8eb579deaa22c8c82000170c0a1673d2543 Mon Sep 17 00:00:00 2001 From: liviodonnini <47381409+liviodonnini@users.noreply.github.com> Date: Wed, 2 Apr 2025 16:37:11 +0200 Subject: [PATCH 35/86] Update setup.cfg --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 03d2509..b4a096b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,4 @@ [metadata] name = application-context-hub version = 1.3.0 + From 26679dbaaa821b1cf4ec08f52c28a8b8588ddcdd Mon Sep 17 00:00:00 2001 From: liviodonnini <47381409+liviodonnini@users.noreply.github.com> Date: Wed, 2 Apr 2025 16:42:09 +0200 Subject: [PATCH 36/86] Update setup.cfg --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index b4a096b..3f7496a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,4 @@ [metadata] name = application-context-hub -version = 1.3.0 +version = 1.3.1 From df6e9f3a305afb69f84d67e084c6f982cc4f254d Mon Sep 17 00:00:00 2001 From: mlongobardo-gituname Date: Wed, 2 Apr 2025 17:06:43 +0200 Subject: [PATCH 37/86] new refinements + added of a generalised section of an appHub application --- docs/apphub-config-general.md | 240 ++++++++++++++++++++++++++++++---- 1 file changed, 214 insertions(+), 26 deletions(-) diff --git a/docs/apphub-config-general.md b/docs/apphub-config-general.md index cd1e1c8..4a449ca 100644 --- a/docs/apphub-config-general.md +++ b/docs/apphub-config-general.md @@ -19,43 +19,231 @@ The definition of an application in the config.yml is addressed according to thi default_url: node_selector: k8s.provider.com/pool-name: + config_maps: + - name: + key: + mount_path: + default_mode: + readonly: + pod_env_vars: + mykey1: "myvalue1" + mykey2: "myvalue2" + mykey3: + from_config_map: + name: mykeyname + key: mykey3 + - name: my-inlined-file-name + key: my-inlined-file-key + content: |- + #inline-content + mount_path: my-inlined-file-path + default_mode: my-inlined-access-mode + readonly: my-inlined-file-read-mode + volumes: + - name: my-volume-name + claim_name: my-volume-claim-name + size: my-claim-size-in-Gi + storage_class: + access_modes: + - "" + volume_mount: + name: "volume-mount-name" + mount_path: "volume-mount-destination-path" + persist: + ``` -E.g. +Breakdown: + +- **id**: the profile identifier of your app +- **groups**: the group list containing the users groups that can use the declared app +- **definition**: display name, reference slug identifying the app, cpu/ram requirements alloted for it, reference docker image for the app-level +- **default_url**: default uri where to find the app +- **node_selector**: identifies on which node pool the app is executed +- **config-maps**: definition of env variables expressed as ':' or config/secret files together with their mount_path, access, default_mode +- **volumes**: handle the volumes, their persistency, their kubernetes access type (e.g. ReadWriteOnce, ReadWriteMany, ...), their size claim and their mount_path + + +## Remote Desktop + ```yaml -- id: profile_iga_remote_desktop - groups: - - group-1 +- id: profile_studio_desktop_native + groups: + - group-b definition: - display_name: Remote Desktop - slug: iga_remote_desktop + display_name: IGA - Remote Desktop base + slug: ellip_studio_desktop_native default: False kubespawner_override: cpu_limit: 1 mem_limit: 4G - image: eoepca/iga-remote-desktop + image: eoepca/iga-remote-desktop:develop default_url: "desktop" - node_selector: - k8s.provider.com/pool-name: node-pool-a + node_selector: {} + config_maps: + - name: aws-credentials + key: aws-credentials + mount_path: /home/jovyan/.aws/credentials + default_mode: "0660" + readonly: true + - name: aws-config + key: aws-config + mount_path: /home/jovyan/.aws/config + default_mode: "0660" + readonly: true + - name: docker-config + key: docker-config + mount_path: /home/jovyan/.docker/config.json + default_mode: "0660" + readonly: true + volumes: + - name: volume-workspace + claim_name: claim-workspace + size: 10Gi + storage_class: "standard" + access_modes: + - "ReadWriteOnce" + volume_mount: + name: volume-workspace + mount_path: "/workspace" + persist: true ``` -Breakdown: +## JupyterLab + +```yaml +- id: profile_studio_labs + groups: + - group-c + definition: + display_name: IAT - Interactive Analysis Tool (JupyterLab) + slug: studio_labs_slug + default: False + kubespawner_override: + cpu_limit: 1 + mem_limit: 4G + image: eoepca/iat-jupyterlab:main + default_url: "lab" + config_maps: + - name: aws-credentials + key: aws-credentials + mount_path: /home/jovyan/.aws/credentials + default_mode: "0660" + readonly: true + - name: aws-config + key: aws-config + mount_path: /home/jovyan/.aws/config + default_mode: "0660" + readonly: true + - name: docker-config + key: docker-config + mount_path: /home/jovyan/.docker/config.json + default_mode: "0660" + readonly: true + - name: new-cm + key: new-cm + mount_path: /home/jovyan/new-cm + default_mode: "0660" + readonly: true + content: |- + Hello World! + persist: false + volumes: + - name: volume-workspace + claim_name: claim-workspace + size: 10Gi + storage_class: "standard" + access_modes: + - "ReadWriteOnce" + volume_mount: + name: volume-workspace + mount_path: "/workspace" + persist: true + pod_env_vars: + A: "10" + B: "20" + node_selector: {} + role_bindings: + - name: pod_reader_role_binding + subjects: + - name: default + kind: ServiceAccount + role: + name: pod_reader_role + api_group: rbac.authorization.k8s.io + verbs: + - get + - list + - watch + resources: + - pods + persist: false +``` + +## CodeServer -- id: the profile identifier of your app -- groups: the group list containing the users groups that can use the declared app -- definition: display name, reference slug identifying the app, cpu/ram requirements alloted for it, reference docker image for the app-level -- default_url: default uri where to find the app -- node_selector: identifies on which node pool the app is executed +```yaml +- id: profile_studio_coder + groups: + - group-b + definition: + display_name: PDE - Processor Development Environment (Code Server) + slug: ellip_studio_coder_slug + default: False + kubespawner_override: + cpu_limit: 1 + mem_limit: 8G + image: eoepca/pde-code-server@sha256:98b77ef39830aec162d9a30311957a48f2b4010930e999969656db11fa788a1b + node_selector: {} + volumes: + - name: volume-workspace + claim_name: claim-dev + size: 10Gi + storage_class: "standard" + access_modes: + - "ReadWriteOnce" + volume_mount: + name: volume-workspace + mount_path: "/workspace" + persist: true + configMaps: + - name: bash-rc + key: bash-rc + content: |- + alias ll="ls -l" + . /home/jovyan/.bashrc + mount_path: /workspace/.bashrc + default_mode: "0660" + readonly: true + - name: aws-credentials + key: aws-credentials + mount_path: /home/jovyan/.aws/credentials + default_mode: "0660" + readonly: true + - name: aws-config + key: aws-config + mount_path: /home/jovyan/.aws/config + default_mode: "0660" + readonly: true + - name: docker-config + key: docker-config + mount_path: /home/jovyan/.docker/config.json + default_mode: "0660" + readonly: true +``` To have a compliant candidate app to be added to the AppHub, it must comply with the proper Dockerfile definition before reference its produced docker image into the configuration. In addition it must be pushed on a registry accessible from the AppHub deployment. These images must expose a service on a given port. There are two options to have JupyterHub to proxy these applications: -- jupyter-server-proxy -- jhsingle-native-proxy -## jupyter-server-proxy approach +- **jupyter-server-proxy** +- **jhsingle-native-proxy** + + + +## Jupyter-server-proxy Dockerfile approach The jupyter-server-proxy exposes the application alongside with the JupyterLab instance whilst jhsingle-native-proxy proxies the application without a running JupyterLab instance. In example the eoepca/iga-remote-desktop_qgis uses the approach with jupyter-server-proxy. @@ -73,7 +261,7 @@ RUN chown -R $NB_UID:$NB_GID $HOME USER $NB_USER ``` -## jhsingle-native-proxy approach +## Jhsingle-native-proxy Dockerfile approach The jhsingle-native-proxy approach instead is based on a Dockerfile following this flow: ``` @@ -108,21 +296,21 @@ CMD ["jhsingle-native-proxy", "--destport", "8505", "streamlit", Params breakdown: ---destport: specifies the destination port number where the target APPLICATION (in this case, Streamlit) will run. +**--destport**: specifies the destination port number where the target APPLICATION (in this case, Streamlit) will run. ---authtype: none disables authentication, meaning the application will not require authentication through JupyterHub. +**--authtype**: none disables authentication, meaning the application will not require authentication through JupyterHub. ---port $port: This sets the PROXY server's external port number to $port. +**--port**: This sets the PROXY server's external port number to $port. The {--} syntax is used to pass arguments to the app, e.g. Streamlit, command itself: {--}server.port {port}: server.port {port} sets the port number on which the Streamlit SERVER will run. Equivalently {--}bind-addr 0.0.0.0:$port -{--}server.headless: True ensures that Streamlit runs in headless mode, meaning it does not require a graphical user interface (GUI). +**{--}server.headless**: True ensures that Streamlit runs in headless mode, meaning it does not require a graphical user interface (GUI). -{--}server.enableCORS: False disables Cross-Origin Resource Sharing (CORS), which is useful when running behind a proxy. +**{--}server.enableCORS**: False disables Cross-Origin Resource Sharing (CORS), which is useful when running behind a proxy. -In addition to the usual jhsingle-native-proxy params list an could require an user data path mapping and its VS code loading, e.g. with {--}user-data-dir & CODE_SERVER_WS: +In addition to the usual jhsingle-native-proxy params list an could require an user data path mapping and its VS code loading, e.g. with **{--}user-data-dir** & CODE\_SERVER\_WS: ``` CODE_SERVER_WS="/workspace" @@ -141,7 +329,7 @@ This file enables the pulling of the app images from container registries. "my-registry-1.com": { "auth": "" }, - "my-registry-1.com": { + "my-registry-2.com": { "auth": "" } } From bafb9d701b29eb3f8f625aed4c5d65e82c47ec98 Mon Sep 17 00:00:00 2001 From: pmembari Date: Thu, 3 Apr 2025 12:13:36 +0200 Subject: [PATCH 38/86] docs: documenation for config generator --- .../config-generator-eoepca-demo.ipynb | 797 ------------ .../examples/config-generator.ipynb | 1158 +++++++++++++---- 2 files changed, 940 insertions(+), 1015 deletions(-) delete mode 100644 config-generator/examples/config-generator-eoepca-demo.ipynb diff --git a/config-generator/examples/config-generator-eoepca-demo.ipynb b/config-generator/examples/config-generator-eoepca-demo.ipynb deleted file mode 100644 index de3d7be..0000000 --- a/config-generator/examples/config-generator-eoepca-demo.ipynb +++ /dev/null @@ -1,797 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Generate App Hub's configuration\n", - "\n", - "This notebook aim to produce a `config.yml` file to generate all the configuration needs to be set for deployment of App Hub on a remote cluster using Devops tools. \n", - "\n", - "This Notebook can be used for the following requirements:\n", - "\n", - "* Initial dependencies\n", - "* Create the configuration for kubernetes' `PV`s, and `PVC`s(e.g. workspace volume, and calrissian volume)\n", - "* Generate the configuration for kubernetes' config maps\n", - "* Creation of different Profiles \n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Initial dependencies" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "PosixPath('/home/p/Desktop/p/Terradue/EOEPCA/application-hub-context/config-generator/examples')" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import yaml\n", - "from apphub_configurator.models import *\n", - "from pathlib import Path\n", - "import os\n", - "import json\n", - "from pprint import pprint\n", - "current_dir = Path(os.getcwd())\n", - "parent_dir = current_dir.parent\n", - "current_dir" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Configuration\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "storage_class_rwo = \"managed-nfs-storage\"\n", - "storage_class_rwx = \"managed-nfs-storage\"\n", - "\n", - "workspace_volume_size = \"50Gi\"\n", - "calrissian_volume_size = \"50Gi\"" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Volumes\n", - "\n", - "In this section, the user will provide the data class objects for creation of volume:\n", - "- Volume for workspace\n", - "- Volume for calrissian\n", - "These two configuration will be use " - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Workspace Volume\n", - "In this section, the user will providing the configuration of a kubernetes Volume for development environment (i.e workspace). It is important to mention that this volume must keep data persistently. therefore, the user set `persist=True`" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "workspace_volume = Volume( # type: ignore\n", - " name=\"workspace-volume\",\n", - " size=workspace_volume_size,\n", - " claim_name=\"workspace-claim\",\n", - " mount_path=\"/workspace\",\n", - " storage_class=storage_class_rwo,\n", - " access_modes=[\"ReadWriteOnce\"],\n", - " volume_mount=VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"), # type: ignore\n", - " persist=True,\n", - ")\n", - "workspace_volume" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Calrissian Volume\n", - "In this section, the user will configure a **Kubernetes Volume** for Calrissian jobs. Since the job runs within Calrissian and does not require data retention on the Calrissian pod, therefore the user should set `persist=False`. " - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False)" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "calrissian_volume = Volume( # type: ignore\n", - " name=\"calrissian-volume\",\n", - " claim_name=\"calrissian-claim\",\n", - " size=calrissian_volume_size,\n", - " storage_class=storage_class_rwx,\n", - " access_modes=[\"ReadWriteMany\"],\n", - " volume_mount=VolumeMount(name=\"calrissian-volume\", mount_path=\"/calrissian\"), # type: ignore\n", - " persist=False,\n", - ")\n", - "calrissian_volume" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ConfigMaps\n", - "\n", - "In this section, the user will provide the configuration for some kubernetes configmaps that is commonly used in this notebook including:\n", - "- bash login Configmap\n", - "- bash rc Configmap\n", - "- QGIS Configmap\n", - "- Stage-in/out Configmap\n", - "These configmaps will be mounted as files on different pods. " - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### bash login\n", - "\n", - "This configmap file aims to configure the Terminal across different profiles. " - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "bash_login_file_path = os.path.join(parent_dir, \"config-maps/bash-login\") \n", - "with open(bash_login_file_path, \"r\") as f:\n", - " content = f.read()\n", - "\n", - "bash_login_cm = ConfigMap( # type: ignore\n", - " name=\"bash-login\",\n", - " key=\"bash-login\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/workspace/.bash_login\",\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### bash-rc \n", - "This section explains how we configure a **Kubernetes ConfigMap** to manage the `.bashrc` file inside a pod. This config map will:\n", - "- It provides useful aliases and environment settings for users.\n", - "- It ensures a consistent environment across different pods." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "bash_rc_cm_file_path = os.path.join(parent_dir, \"config-maps/bash-rc\") \n", - "with open(bash_rc_cm_file_path, \"r\") as f:\n", - " content = f.read()\n", - "bash_rc_cm = ConfigMap( # type: ignore\n", - " name=\"bash-rc\",\n", - " key=\"bash-rc\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/workspace/.bashrc\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### QGIS Configmap\n", - "The cell below will provide a Configmap configuration for QGIS setup." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "init_qgis_file_path = os.path.join(parent_dir, \"config-maps/init-qgis.sh\") \n", - "with open(init_qgis_file_path, \"r\") as f:\n", - " content = f.read()\n", - "\n", - "init_qgis_cm = ConfigMap( # type: ignore\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Stage-in/out Configmap\n", - "The cell below will provide a Configmap configuration for stage-in/out for [e-learning](#e-learning) profile." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "init_stac_file_path = os.path.join(parent_dir, \"config-maps/init-stac.sh\")\n", - "with open(init_stac_file_path, \"r\") as f:\n", - " content = f.read()\n", - "\n", - "init_stac_cm = ConfigMap( # type: ignore\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Coder init Configmap\n", - "The cell below will provide a Configmap configuration for stage-in/out for [coder](#coder) profile." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "init_cm_file_path = os.path.join(parent_dir, \"config-maps/init.sh\") \n", - "with open(init_cm_file_path, \"r\") as f:\n", - " content = f.read()\n", - "\n", - "init_coder_cm = ConfigMap(\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - " default_mode=\"0660\",\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Profiles\n", - "In the following section, the user will generate the configuration for different profiles on a remote cluster including:\n", - "- [Coder](#coder)\n", - "- [Jupyter Lab](#jupyterlab)\n", - "- [e-learning](#e-learning)\n", - "- [QGIS](#qgis)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "profiles = []" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Coder" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "coders = {\n", - " \"coder1\": {\n", - " \"display_name\": \"Code Server Small\",\n", - " \"slug\": \"ellip_studio_coder_slug_s\",\n", - " \"cpu_limit\": 2,\n", - " \"mem_limit\": \"8G\",\n", - " },\n", - " \"coder2\": {\n", - " \"display_name\": \"Code Server Medium\",\n", - " \"slug\": \"ellip_studio_coder_slug_m\",\n", - " \"cpu_limit\": 4,\n", - " \"mem_limit\": \"12G\",\n", - " },\n", - "}\n", - "\n", - "for key, value in coders.items():\n", - " coder_definition = ProfileDefinition( # type: ignore\n", - " display_name=value[\"display_name\"],\n", - " slug=value[\"slug\"],\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride( # type: ignore\n", - " cpu_limit=value[\"cpu_limit\"],\n", - " mem_limit=value[\"mem_limit\"],\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " ),\n", - " )\n", - "\n", - " coder_profile = Profile( # type: ignore\n", - " id=f\"profile_studio_{key}\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=coder_definition,\n", - " node_selector={},\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[\n", - " bash_rc_cm,\n", - " ],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " },\n", - " )\n", - "\n", - " profiles.append(coder_profile)\n", - "profiles" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### init.sh script" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "\n", - "\n", - "init_context_volume_mount = InitContainerVolumeMount( # type: ignore\n", - " mount_path=\"/opt/init/.init.sh\", name=\"init\", sub_path=\"init\"\n", - ")\n", - "init_container = InitContainer( # type: ignore\n", - " name=\"init-file-on-volume\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " command=[\"sh\", \"-c\", \"sh /opt/init/.init.sh\"],\n", - " volume_mounts=[\n", - " VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"), # type: ignore\n", - " init_context_volume_mount,\n", - " ],\n", - ")\n", - "\n", - "eoepca_demo_init_script_profile = Profile( # type: ignore\n", - " id=f\"profile_demo_init_script\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition( # type: ignore\n", - " display_name=\"Coder demo init script\",\n", - " description=\"This profile is used to demonstrate the use of an init script\",\n", - " slug=\"eoepca_demo_init_script\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride( # type: ignore\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[init_coder_cm],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " \"CONDARC\": \"/workspace/.condarc\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"CODE_SERVER_WS\": \"/workspace/mastering-app-package\",\n", - " },\n", - " init_containers=[init_container],\n", - ")\n", - "profiles.append(eoepca_demo_init_script_profile)\n", - "profiles" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### JupyterLab" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "image = \"jupyter/scipy-notebook\"\n", - "\n", - "\n", - "eoepca_jupyter_lab_profile = Profile( # type: ignore\n", - " id=\"profile_jupyter_lab\",\n", - " groups=[\"group-c\"],\n", - " definition=ProfileDefinition( # type: ignore\n", - " display_name=\"Jupyter Lab\",\n", - " description=\"Jupyter Lab with Python 3.11\",\n", - " slug=\"eoepca_jupyter_lab\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride( # type: ignore\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[workspace_volume],\n", - " config_maps=[],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", - " },\n", - ")\n", - "\n", - "profiles.append(eoepca_jupyter_lab_profile)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Image pull secret\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "image_pull_secret = ImagePullSecret( # type: ignore\n", - " name=\"cr-config\",\n", - " persist=False,\n", - " data=\"ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9\",\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "image = \"cr.terradue.com/eoepca-plus/scipy-notebook@sha256:f339a9fa98d3d0c1fa8d7cc850e7f5a46845781f49bee86aacba059669d02d54\"\n", - "image = \"eoepca/iat-jupyterlab:develop\"\n", - "\n", - "eoepca_jupyter_lab_profile_2 = Profile(\n", - " id=\"profile_jupyter_lab_2\",\n", - " groups=[\"group-c\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Jupyter Lab - profile 2\",\n", - " description=\"Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret\",\n", - " slug=\"eoepca_jupyter_lab_2\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[workspace_volume],\n", - " config_maps=[],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", - " },\n", - " image_pull_secrets=[image_pull_secret],\n", - ")\n", - "\n", - "profiles.append(eoepca_jupyter_lab_profile_2)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [], - "source": [ - "localstack_manifest_path = os.path.join(parent_dir, \"manifests/manifest.yaml\") \n", - "with open(localstack_manifest_path, \"r\") as f:\n", - " content = yaml.safe_load_all(f.read())\n", - "\n", - "\n", - "localstack_manifest = Manifest(\n", - " name=\"manifests\",\n", - " key=\"manifests\",\n", - " readonly=True,\n", - " persist=False,\n", - " content=[e for e in content],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### e-learning" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "image = \"docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40\"\n", - "\n", - "coder_profile_stac = Profile(\n", - " id=f\"profile_studio_coder_stac\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Understanding STAC for input/output data modelling\",\n", - " description=\"Understand the role of STAC in input/output data manifests in EO data processing workflows\",\n", - " slug=\"eoepca_coder_slug_stac\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[workspace_volume],\n", - " config_maps=[init_stac_cm, bash_rc_cm, bash_login_cm],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " \"CONDARC\": \"/workspace/.condarc\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_CONFIG_HOME\": \"/workspace/.local\",\n", - " \"XDG_DATA_HOME\": \"/workspace/.local/share/\",\n", - " \"CWLTOOL_OPTIONS\": \"--podman\",\n", - " \"CODE_SERVER_WS\": \"/workspace/stac-eoap\",\n", - " \"AWS_DEFAULT_REGION\": \"us-east-1\",\n", - " \"AWS_ACCESS_KEY_ID\": \"test\",\n", - " \"AWS_SECRET_ACCESS_KEY\": \"test\",\n", - " },\n", - " role_bindings=[],\n", - " init_containers=[init_container],\n", - " image_pull_secrets=[image_pull_secret],\n", - " manifests=[localstack_manifest],\n", - ")\n", - "\n", - "profiles.append(coder_profile_stac)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### QGIS" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "\n", - "image = \"eoepca/iga-remote-desktop-qgis:1.1.3\"\n", - "\n", - "qgis_profile = Profile(\n", - " id=\"profile_studio_desktop_qgis\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"QGIS on a Remote Desktop\",\n", - " description=\"Spatial visualization and decision-making tools for everyone\",\n", - " slug=\"eoepca_desktop_qgis\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_limit=2,\n", - " mem_limit=\"2G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[workspace_volume],\n", - " config_maps=[bash_rc_cm, bash_login_cm],\n", - " pod_env_vars={\"HOME\": \"/workspace\"},\n", - " default_url=\"desktop\",\n", - " init_containers=[],\n", - ")\n", - "\n", - "profiles.append(qgis_profile)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Write Configuration\n", - "The code below dump a `config.yaml` containing the information of App Hub." - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [], - "source": [ - "config = Config(profiles=profiles)\n", - "eoepca_demo_config_path = str(Path(current_dir).parent.parent / 'eoepca-demo' / 'config.yml')\n", - "with open(eoepca_demo_config_path, \"w\") as file:\n", - " yaml.dump(config.model_dump(), file)" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [], - "source": [ - "# profiles" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "apphub_configurator", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.2" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/config-generator/examples/config-generator.ipynb b/config-generator/examples/config-generator.ipynb index e12c89d..c3f7c4e 100644 --- a/config-generator/examples/config-generator.ipynb +++ b/config-generator/examples/config-generator.ipynb @@ -1,5 +1,48 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Generate App Hub's configuration\n", + "\n", + "This notebook aim to produce a `config.yml` file to generate all the configuration needs to be set for deployment of App Hub on a remote cluster using Devops tools. \n", + "\n", + "This Notebook can be used for the following requirements:\n", + "\n", + "* Initial dependencies\n", + "* Create the configuration for kubernetes' `PV`s, and `PVC`s(e.g. workspace volume, and calrissian volume)\n", + "* Generate the configuration for kubernetes' config maps\n", + "* Creation of different Profiles \n", + "\n", + "Table of Content:\n", + "- [Initial dependencies](#initial-dependencies)\n", + " - [Configuration](#configuration)\n", + "- [Volumes](#volumes)\n", + " - [Workspace volume](#workspace-volume)\n", + " - [Calrissian volume](#calrissian-volume)\n", + "- [Configmaps](#configmaps)\n", + " - [bash login Configmap](#bash-login-configmap)\n", + " - [bash rc Configmap](#bash-rc-configmap) \n", + " - [QGIS Configmap](#qgis-configmap) \n", + " - [Stage-in/out Configmap](#stage-inout-configmap)\n", + "- [Profiles](#profiles)\n", + " - [Coder](#coder)\n", + " - [Coder with init.sh](#coder-with-initsh)\n", + " - [JupyterLab](#jupyterlab)\n", + " - [JupyterLab Plus](#jupyterlab-plus)\n", + " - [E-Learning](#e-learning)\n", + " - [QGIS](#qgis)\n", + "- [Write Configuration](#write-configuration)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initial dependencies" + ] + }, { "cell_type": "code", "execution_count": 1, @@ -19,56 +62,35 @@ "source": [ "import yaml\n", "from apphub_configurator.models import *\n", - "import os\n", "from pathlib import Path\n", - "\n", + "import os\n", + "import json\n", + "from pprint import pprint\n", "current_dir = Path(os.getcwd())\n", "parent_dir = current_dir.parent\n", "current_dir" ] }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "localstack_manifest_path = os.path.join(parent_dir, \"manifests/manifest.yaml\") \n", - "with open(localstack_manifest_path, \"r\") as f:\n", - " content = yaml.safe_load_all(f.read())\n", - "\n", - "\n", - "localstack_manifest = Manifest(\n", - " name=\"manifests\",\n", - " key=\"manifests\",\n", - " readonly=True,\n", - " persist=False,\n", - " content=[e for e in content],\n", - ")" - ] - }, { "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "## Configuration\n", + "### Configuration\n", "\n" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ - "storage_class_rwo = \"standard\"\n", - "storage_class_rwx = \"standard\"\n", + "storage_class_rwo = \"managed-nfs-storage\"\n", + "storage_class_rwx = \"managed-nfs-storage\"\n", "\n", "workspace_volume_size = \"50Gi\"\n", - "calrissian_volume_size = \"50Gi\"\n", - "\n", - "node_selector = {} # \"k8s.scaleway.com/pool-name\": \"application-hub\"}" + "calrissian_volume_size = \"50Gi\"" ] }, { @@ -78,7 +100,10 @@ "source": [ "## Volumes\n", "\n", - "Create the Volumes" + "In this section, the user will provide the data class objects for creation of volume:\n", + "- Volume for workspace\n", + "- Volume for calrissian\n", + "These two configuration will be use " ] }, { @@ -87,26 +112,37 @@ "metadata": {}, "source": [ "### Workspace Volume\n", - "\n", - "The workspace volume is persisted." + "In this section, the user will providing the configuration of a kubernetes Volume for development environment (i.e workspace). It is important to mention that this volume must keep data persistently. therefore, the user set `persist=True`" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "workspace_volume = Volume(\n", + "workspace_volume = Volume( # type: ignore\n", " name=\"workspace-volume\",\n", " size=workspace_volume_size,\n", " claim_name=\"workspace-claim\",\n", " mount_path=\"/workspace\",\n", " storage_class=storage_class_rwo,\n", " access_modes=[\"ReadWriteOnce\"],\n", - " volume_mount=VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"),\n", + " volume_mount=VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"), # type: ignore\n", " persist=True,\n", - ")" + ")\n", + "workspace_volume" ] }, { @@ -115,25 +151,36 @@ "metadata": {}, "source": [ "### Calrissian Volume\n", - "\n", - "This is a RWX volume, not persisted" + "In this section, the user will configure a **Kubernetes Volume** for Calrissian jobs. Since the job runs within Calrissian and does not require data retention on the Calrissian pod, therefore the user should set `persist=False`. " ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "calrissian_volume = Volume(\n", + "calrissian_volume = Volume( # type: ignore\n", " name=\"calrissian-volume\",\n", " claim_name=\"calrissian-claim\",\n", " size=calrissian_volume_size,\n", " storage_class=storage_class_rwx,\n", " access_modes=[\"ReadWriteMany\"],\n", - " volume_mount=VolumeMount(name=\"calrissian-volume\", mount_path=\"/calrissian\"),\n", + " volume_mount=VolumeMount(name=\"calrissian-volume\", mount_path=\"/calrissian\"), # type: ignore\n", " persist=False,\n", - ")" + ")\n", + "calrissian_volume" ] }, { @@ -143,7 +190,12 @@ "source": [ "## ConfigMaps\n", "\n", - "These configmaps are mounted as files on the pod." + "In this section, the user will provide the configuration for some kubernetes configmaps that is commonly used in this notebook including:\n", + "- bash login Configmap\n", + "- bash rc Configmap\n", + "- QGIS Configmap\n", + "- Stage-in/out Configmap\n", + "These configmaps will be mounted as files on different pods. " ] }, { @@ -151,29 +203,41 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### bash login\n", + "### bash login Configmap\n", "\n", - "This file is used for the JupyterLab Terminal configuration" + "This configmap file aims to configure the Terminal across different profiles. " ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "bash_login_file_path = os.path.join(parent_dir, \"config-maps/bash-login\") \n", "with open(bash_login_file_path, \"r\") as f:\n", " content = f.read()\n", "\n", - "bash_login_cm = ConfigMap(\n", + "bash_login_cm = ConfigMap( # type: ignore\n", " name=\"bash-login\",\n", " key=\"bash-login\",\n", " content=content,\n", " readonly=True,\n", - " persist=True,\n", + " persist=False,\n", " mount_path=\"/workspace/.bash_login\",\n", - ")" + ")\n", + "bash_login_cm" ] }, { @@ -181,40 +245,183 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### bash.rc\n" + "### bash-rc Configmap\n", + "This section explains how we configure a **Kubernetes ConfigMap** to manage the `.bashrc` file inside a pod. This config map will:\n", + "- It provides useful aliases and environment settings for users.\n", + "- It ensures a consistent environment across different pods." ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "bash_rc_cm_file_path = os.path.join(parent_dir, \"config-maps/bash-rc\") \n", "with open(bash_rc_cm_file_path, \"r\") as f:\n", " content = f.read()\n", - "bash_rc_cm = ConfigMap(\n", + "bash_rc_cm = ConfigMap( # type: ignore\n", " name=\"bash-rc\",\n", " key=\"bash-rc\",\n", " content=content,\n", " readonly=True,\n", - " persist=True,\n", + " persist=False,\n", " mount_path=\"/workspace/.bashrc\",\n", - ")" + ")\n", + "bash_rc_cm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### QGIS Configmap\n", + "The cell below will provide a Configmap configuration for QGIS setup." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='mkdir -p /workspace/.config/autostart\\n\\n\\ncat < /workspace/.config/autostart/qgis.desktop \\n[Desktop Entry]\\nEncoding=UTF-8\\nVersion=0.9.4\\nType=Application\\nName=qgis\\nComment=qgis\\nExec=qgis\\nOnlyShowIn=XFCE;\\nRunHook=0\\nStartupNotify=false\\nTerminal=false\\nHidden=false\\nEOF', persist=False)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "init_qgis_file_path = os.path.join(parent_dir, \"config-maps/init-qgis.sh\") \n", + "with open(init_qgis_file_path, \"r\") as f:\n", + " content = f.read()\n", + "\n", + "init_qgis_cm = ConfigMap( # type: ignore\n", + " name=\"init\",\n", + " key=\"init\",\n", + " content=content,\n", + " readonly=True,\n", + " persist=False,\n", + " mount_path=\"/opt/init/.init.sh\",\n", + ")\n", + "init_qgis_cm" ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "## Profiles" + "### Stage-in/out Configmap\n", + "The cell below will provide a Configmap configuration for stage-in/out for [e-learning](#e-learning) profile." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='set -x \\n\\ncd /workspace\\n\\ngit clone \\'https://github.com/eoap/stac-eoap.git\\'\\n\\ncode-server --install-extension ms-python.python \\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nmkdir -p /workspace/User/\\n\\necho \\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\' > /workspace/User/settings.json\\n\\npython -m venv /workspace/.venv\\nsource /workspace/.venv/bin/activate\\n/workspace/.venv/bin/python -m pip install --no-cache-dir stactools rasterio requests stac-asset click-logging tabulate tqdm pystac-client ipykernel loguru scikit-image rio_stac boto3==1.35.23\\n\\n/workspace/.venv/bin/python -m pip install --index-url https://test.pypi.org/simple cwl-wrapper\\n\\n/workspace/.venv/bin/python -m ipykernel install --user --name stac_env --display-name \"Python (STAC)\"\\n\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\nexport AWS_ACCESS_KEY_ID=\"test\"\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\naws s3 mb s3://results --endpoint-url=http://localstack:4566\\n\\nexit 0', persist=False)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "init_stac_file_path = os.path.join(parent_dir, \"config-maps/init-stac.sh\")\n", + "with open(init_stac_file_path, \"r\") as f:\n", + " content = f.read()\n", + "\n", + "init_stac_cm = ConfigMap( # type: ignore\n", + " name=\"init\",\n", + " key=\"init\",\n", + " content=content,\n", + " readonly=True,\n", + " persist=False,\n", + " mount_path=\"/opt/init/.init.sh\",\n", + ")\n", + "init_stac_cm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Coder init Configmap\n", + "The cell below will provide a Configmap configuration for stage-in/out for [coder](#coder) profile." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "init_cm_file_path = os.path.join(parent_dir, \"config-maps/init.sh\") \n", + "with open(init_cm_file_path, \"r\") as f:\n", + " content = f.read()\n", + "\n", + "init_coder_cm = ConfigMap(\n", + " name=\"init\",\n", + " key=\"init\",\n", + " content=content,\n", + " readonly=True,\n", + " persist=False,\n", + " mount_path=\"/opt/init/.init.sh\",\n", + " default_mode=\"0660\",\n", + ")\n", + "init_coder_cm" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Profiles\n", + "In the following section, the user will generate the configuration for different profiles on a remote cluster including:\n", + "- [Coder](#coder)\n", + "- [Coder with init.sh](#coder-with-initsh)\n", + "- [Jupyter Lab](#jupyterlab)\n", + "- [JupyterLab Plus](#jupyterlab-plus)\n", + "- [E-learning](#e-learning)\n", + "- [QGIS](#qgis)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, "outputs": [], "source": [ "profiles = []" @@ -225,122 +432,125 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Coder" + "### Coder\n", + "In the cell below, the user configures two code-server profiles to be deployed on a remote cluster. One profile (**medium**) has more resources than the other (**small**). Two Kubernetes Volumes, discussed in the [Volume section](#volumes), are assigned, and a ConfigMap is mounted. \n" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 12, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "coders = {\n", " \"coder1\": {\n", " \"display_name\": \"Code Server Small\",\n", - " \"slug\": \"eoepca_coder_slug_s\",\n", + " \"slug\": \"ellip_studio_coder_slug_s\",\n", " \"cpu_limit\": 2,\n", " \"mem_limit\": \"8G\",\n", " },\n", " \"coder2\": {\n", " \"display_name\": \"Code Server Medium\",\n", - " \"slug\": \"eoepca_coder_slug_m\",\n", + " \"slug\": \"ellip_studio_coder_slug_m\",\n", " \"cpu_limit\": 4,\n", " \"mem_limit\": \"12G\",\n", " },\n", "}\n", "\n", "for key, value in coders.items():\n", - " coder_definition = ProfileDefinition(\n", + " coder_definition = ProfileDefinition( # type: ignore\n", " display_name=value[\"display_name\"],\n", " slug=value[\"slug\"],\n", " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", " cpu_limit=value[\"cpu_limit\"],\n", " mem_limit=value[\"mem_limit\"],\n", " image=\"eoepca/pde-code-server:develop\",\n", " ),\n", " )\n", "\n", - " coder_profile = Profile(\n", + " coder_profile = Profile( # type: ignore\n", " id=f\"profile_studio_{key}\",\n", " groups=[\"group-a\", \"group-b\"],\n", " definition=coder_definition,\n", - " node_selector=node_selector,\n", + " node_selector={},\n", " volumes=[calrissian_volume, workspace_volume],\n", " config_maps=[\n", " bash_rc_cm,\n", " ],\n", " pod_env_vars={\n", " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \" /workspace/.envs\",\n", + " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", " },\n", - " manifests=[localstack_manifest],\n", - " env_from_config_maps=[\"my-config\"],\n", - " env_from_secrets=[\"my-secret\", \"data-by-name\"],\n", - " secret_mounts=[\n", - " SecretMount(\n", - " name=\"aws-credentials-{{ spawner.user.name }}\",\n", - " mount_path=\"/workspace/.aws\",\n", - " ),\n", - " SecretMount(name=\"data-by-name\", mount_path=\"/workspace/.data-by-name\"),\n", - " SecretMount(name=\"eoepca-plus-secret-ro\", mount_path=\"/workspace/.docker\"),\n", - " ],\n", - " image_pull_secrets=[ImagePullSecret(name=\"eoepca-plus-secret-ro\")],\n", " )\n", "\n", - " profiles.append(coder_profile)" + " profiles.append(coder_profile)\n", + "profiles" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## init.sh script ..." + "### Coder with init.sh\n", + "In the cell below, the user configures the code-server with a new profile, which extends the existing [coder](#coder) profile. The init container is responsible for executing a Bash script to initialize the code-server pod. In this case, the script [`init.sh`](../config-maps/init.sh) runs during pod initialization to clone a Git repository and install extensions on the deployed code-server." ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 13, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "init_cm_file_path = os.path.join(parent_dir, \"config-maps/init.sh\") \n", - "with open(init_cm_file_path, \"r\") as f:\n", - " content = f.read()\n", "\n", - "init_cm = ConfigMap(\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - " default_mode=\"0660\",\n", - ")\n", "\n", - "\n", - "init_context_volume_mount = InitContainerVolumeMount(\n", + "init_context_volume_mount = InitContainerVolumeMount( # type: ignore\n", " mount_path=\"/opt/init/.init.sh\", name=\"init\", sub_path=\"init\"\n", ")\n", - "init_container = InitContainer(\n", + "init_container = InitContainer( # type: ignore\n", " name=\"init-file-on-volume\",\n", " image=\"eoepca/pde-code-server:develop\",\n", " command=[\"sh\", \"-c\", \"sh /opt/init/.init.sh\"],\n", " volume_mounts=[\n", - " VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"),\n", + " VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"), # type: ignore\n", " init_context_volume_mount,\n", " ],\n", ")\n", "\n", - "eoepca_demo_init_script_profile = Profile(\n", + "eoepca_demo_init_script_profile = Profile( # type: ignore\n", " id=f\"profile_demo_init_script\",\n", " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", + " definition=ProfileDefinition( # type: ignore\n", " display_name=\"Coder demo init script\",\n", " description=\"This profile is used to demonstrate the use of an init script\",\n", " slug=\"eoepca_demo_init_script\",\n", " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", " cpu_guarantee=1,\n", " cpu_limit=2,\n", " mem_guarantee=\"4G\",\n", @@ -348,9 +558,9 @@ " image=\"eoepca/pde-code-server:develop\",\n", " ),\n", " ),\n", - " node_selector=node_selector,\n", + " node_selector={},\n", " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[init_cm],\n", + " config_maps=[init_coder_cm],\n", " pod_env_vars={\n", " \"HOME\": \"/workspace\",\n", " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", @@ -359,79 +569,119 @@ " \"CODE_SERVER_WS\": \"/workspace/mastering-app-package\",\n", " },\n", " init_containers=[init_container],\n", - ")" + ")\n", + "profiles.append(eoepca_demo_init_script_profile)\n", + "profiles" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "##### Better code" + "### JupyterLab\n", + "\n", + "In the cell below, the user adds a [JupyterLab](https://github.com/jupyter/docker-stacks) profile to the stack of profiles. The [workspace volume](#workspace-volume) is mounted to persist user files, and necessary environment variables are set to ensure a smooth user experience." ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 14, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "profiles.append(eoepca_demo_init_script_profile)\n", + "image = \"jupyter/scipy-notebook\"\n", "\n", - "eoepca_demo_init_script_profile = Profile(\n", - " id=f\"profile_demo_init_script\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Coder demo init script\",\n", - " description=\"This profile is used to demonstrate the use of an init script\",\n", - " slug=\"eoepca_demo_init_script\",\n", + "\n", + "eoepca_jupyter_lab_profile = Profile( # type: ignore\n", + " id=\"profile_jupyter_lab\",\n", + " groups=[\"group-c\"],\n", + " definition=ProfileDefinition( # type: ignore\n", + " display_name=\"Jupyter Lab\",\n", + " description=\"Jupyter Lab with Python 3.11\",\n", + " slug=\"eoepca_jupyter_lab\",\n", " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", " cpu_guarantee=1,\n", " cpu_limit=2,\n", " mem_guarantee=\"4G\",\n", " mem_limit=\"6G\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", + " image=image,\n", " ),\n", " ),\n", - " node_selector=node_selector,\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[init_cm],\n", + " node_selector={},\n", + " volumes=[workspace_volume],\n", + " config_maps=[],\n", " pod_env_vars={\n", " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " \"CONDARC\": \"/workspace/.condarc\",\n", " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"CODE_SERVER_WS\": \"/workspace/mastering-app-package\",\n", + " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", " },\n", - " init_containers=[init_container],\n", - ")" + ")\n", + "\n", + "profiles.append(eoepca_jupyter_lab_profile)\n", + "profiles" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## JupyterLab" + "### JupyterLab Plus \n", + "JupyterLab Plus is an extended version of the JupyterLab profile with additional features and capabilities. The Docker image for this profile is hosted on a private Docker registry, requiring the user to configure a pull secret to authenticate and successfully pull the JupyterLab Plus image on the remote cluster." ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 15, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab_2', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', description='Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret', slug='eoepca_jupyter_lab_2', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "image = \"jupyter/scipy-notebook\"\n", - "\n", + "image_pull_secret = ImagePullSecret( # type: ignore\n", + " name=\"cr-config\",\n", + " persist=False,\n", + " data=\"ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9\",\n", + ")\n", + "image = \"cr.terradue.com/eoepca-plus/scipy-notebook@sha256:f339a9fa98d3d0c1fa8d7cc850e7f5a46845781f49bee86aacba059669d02d54\"\n", + "image = \"eoepca/iat-jupyterlab:develop\"\n", "\n", - "eoepca_jupyter_lab_profile = Profile(\n", - " id=\"profile_jupyter_lab\",\n", + "eoepca_jupyter_lab_profile_2 = Profile( # type: ignore\n", + " id=\"profile_jupyter_lab_2\",\n", " groups=[\"group-c\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Jupyter Lab\",\n", - " description=\"Jupyter Lab with Python 3.11\",\n", - " slug=\"eoepca_jupyter_lab\",\n", + " definition=ProfileDefinition( # type: ignore\n", + " display_name=\"Jupyter Lab - profile 2\",\n", + " description=\"Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret\",\n", + " slug=\"eoepca_jupyter_lab_2\",\n", " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", " cpu_guarantee=1,\n", " cpu_limit=2,\n", " mem_guarantee=\"4G\",\n", @@ -439,7 +689,7 @@ " image=image,\n", " ),\n", " ),\n", - " node_selector=node_selector,\n", + " node_selector={},\n", " volumes=[workspace_volume],\n", " config_maps=[],\n", " pod_env_vars={\n", @@ -447,68 +697,67 @@ " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", " },\n", + " image_pull_secrets=[image_pull_secret],\n", ")\n", "\n", - "profiles.append(eoepca_jupyter_lab_profile)" + "profiles.append(eoepca_jupyter_lab_profile_2)\n", + "profiles" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Image pull secret\n" + "### E-Learning \n", + "This profile is an extended version of code-server, configured to initialize with an init container using [init.sh](../config-maps/init.sh). It also mounts several ConfigMaps, including [bash-rc](#bash-rc), [bash-login](#bash-login), and [stage-in/out](#stage-inout-configmap). Additionally, key Kubernetes objects such as Role, RoleBinding, and Service are defined using a Kubernetes [manifest](../manifests/manifest.yaml). This manifest ultimately exposes **LocalStack** as a Service, allowing users to emulate AWS locally." ] }, { "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "image_pull_secret = ImagePullSecret(\n", - " name=\"cr-config\",\n", - " persist=False,\n", - " data=\"\",\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 14, + "execution_count": 16, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab_2', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', description='Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret', slug='eoepca_jupyter_lab_2', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder_stac', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Understanding STAC for input/output data modelling', description='Understand the role of STAC in input/output data manifests in EO data processing workflows', slug='eoepca_coder_slug_stac', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='set -x \\n\\ncd /workspace\\n\\ngit clone \\'https://github.com/eoap/stac-eoap.git\\'\\n\\ncode-server --install-extension ms-python.python \\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nmkdir -p /workspace/User/\\n\\necho \\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\' > /workspace/User/settings.json\\n\\npython -m venv /workspace/.venv\\nsource /workspace/.venv/bin/activate\\n/workspace/.venv/bin/python -m pip install --no-cache-dir stactools rasterio requests stac-asset click-logging tabulate tqdm pystac-client ipykernel loguru scikit-image rio_stac boto3==1.35.23\\n\\n/workspace/.venv/bin/python -m pip install --index-url https://test.pypi.org/simple cwl-wrapper\\n\\n/workspace/.venv/bin/python -m ipykernel install --user --name stac_env --display-name \"Python (STAC)\"\\n\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\nexport AWS_ACCESS_KEY_ID=\"test\"\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\naws s3 mb s3://results --endpoint-url=http://localstack:4566\\n\\nexit 0', persist=False), ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.local', 'XDG_DATA_HOME': '/workspace/.local/share/', 'CWLTOOL_OPTIONS': '--podman', 'CODE_SERVER_WS': '/workspace/stac-eoap', 'AWS_DEFAULT_REGION': 'us-east-1', 'AWS_ACCESS_KEY_ID': 'test', 'AWS_SECRET_ACCESS_KEY': 'test'}, default_url=None, node_selector={}, role_bindings=[], image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=[Manifest(name='manifests', key='manifests', content=[{'apiVersion': 'v1', 'kind': 'ServiceAccount', 'metadata': {'name': 'localstack'}}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'Role', 'metadata': {'name': 'localstack'}, 'rules': [{'apiGroups': [''], 'resources': ['pods'], 'verbs': ['*']}, {'apiGroups': [''], 'resources': ['pods/log'], 'verbs': ['get']}, {'apiGroups': [''], 'resources': ['pods/exec'], 'verbs': ['get', 'create']}, {'apiGroups': [''], 'resources': ['services'], 'verbs': ['get', 'list']}]}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'RoleBinding', 'metadata': {'name': 'localstack'}, 'subjects': [{'kind': 'ServiceAccount', 'name': 'localstack'}], 'roleRef': {'kind': 'Role', 'name': 'localstack', 'apiGroup': 'rbac.authorization.k8s.io'}}, {'apiVersion': 'v1', 'kind': 'Service', 'metadata': {'name': 'localstack'}, 'spec': {'type': 'ClusterIP', 'ports': [{'name': 'edge', 'port': 4566, 'targetPort': 4566}, {'name': 'external-service-port-4510', 'port': 4510, 'targetPort': 'ext-svc-4510'}, {'name': 'external-service-port-4511', 'port': 4511, 'targetPort': 'ext-svc-4511'}, {'name': 'external-service-port-4512', 'port': 4512, 'targetPort': 'ext-svc-4512'}, {'name': 'external-service-port-4513', 'port': 4513, 'targetPort': 'ext-svc-4513'}, {'name': 'external-service-port-4514', 'port': 4514, 'targetPort': 'ext-svc-4514'}, {'name': 'external-service-port-4515', 'port': 4515, 'targetPort': 'ext-svc-4515'}, {'name': 'external-service-port-4516', 'port': 4516, 'targetPort': 'ext-svc-4516'}, {'name': 'external-service-port-4517', 'port': 4517, 'targetPort': 'ext-svc-4517'}, {'name': 'external-service-port-4518', 'port': 4518, 'targetPort': 'ext-svc-4518'}, {'name': 'external-service-port-4519', 'port': 4519, 'targetPort': 'ext-svc-4519'}, {'name': 'external-service-port-4520', 'port': 4520, 'targetPort': 'ext-svc-4520'}, {'name': 'external-service-port-4521', 'port': 4521, 'targetPort': 'ext-svc-4521'}, {'name': 'external-service-port-4522', 'port': 4522, 'targetPort': 'ext-svc-4522'}, {'name': 'external-service-port-4523', 'port': 4523, 'targetPort': 'ext-svc-4523'}, {'name': 'external-service-port-4524', 'port': 4524, 'targetPort': 'ext-svc-4524'}, {'name': 'external-service-port-4525', 'port': 4525, 'targetPort': 'ext-svc-4525'}, {'name': 'external-service-port-4526', 'port': 4526, 'targetPort': 'ext-svc-4526'}, {'name': 'external-service-port-4527', 'port': 4527, 'targetPort': 'ext-svc-4527'}, {'name': 'external-service-port-4528', 'port': 4528, 'targetPort': 'ext-svc-4528'}, {'name': 'external-service-port-4529', 'port': 4529, 'targetPort': 'ext-svc-4529'}, {'name': 'external-service-port-4530', 'port': 4530, 'targetPort': 'ext-svc-4530'}, {'name': 'external-service-port-4531', 'port': 4531, 'targetPort': 'ext-svc-4531'}, {'name': 'external-service-port-4532', 'port': 4532, 'targetPort': 'ext-svc-4532'}, {'name': 'external-service-port-4533', 'port': 4533, 'targetPort': 'ext-svc-4533'}, {'name': 'external-service-port-4534', 'port': 4534, 'targetPort': 'ext-svc-4534'}, {'name': 'external-service-port-4535', 'port': 4535, 'targetPort': 'ext-svc-4535'}, {'name': 'external-service-port-4536', 'port': 4536, 'targetPort': 'ext-svc-4536'}, {'name': 'external-service-port-4537', 'port': 4537, 'targetPort': 'ext-svc-4537'}, {'name': 'external-service-port-4538', 'port': 4538, 'targetPort': 'ext-svc-4538'}, {'name': 'external-service-port-4539', 'port': 4539, 'targetPort': 'ext-svc-4539'}, {'name': 'external-service-port-4540', 'port': 4540, 'targetPort': 'ext-svc-4540'}, {'name': 'external-service-port-4541', 'port': 4541, 'targetPort': 'ext-svc-4541'}, {'name': 'external-service-port-4542', 'port': 4542, 'targetPort': 'ext-svc-4542'}, {'name': 'external-service-port-4543', 'port': 4543, 'targetPort': 'ext-svc-4543'}, {'name': 'external-service-port-4544', 'port': 4544, 'targetPort': 'ext-svc-4544'}, {'name': 'external-service-port-4545', 'port': 4545, 'targetPort': 'ext-svc-4545'}, {'name': 'external-service-port-4546', 'port': 4546, 'targetPort': 'ext-svc-4546'}, {'name': 'external-service-port-4547', 'port': 4547, 'targetPort': 'ext-svc-4547'}, {'name': 'external-service-port-4548', 'port': 4548, 'targetPort': 'ext-svc-4548'}, {'name': 'external-service-port-4549', 'port': 4549, 'targetPort': 'ext-svc-4549'}, {'name': 'external-service-port-4550', 'port': 4550, 'targetPort': 'ext-svc-4550'}, {'name': 'external-service-port-4551', 'port': 4551, 'targetPort': 'ext-svc-4551'}, {'name': 'external-service-port-4552', 'port': 4552, 'targetPort': 'ext-svc-4552'}, {'name': 'external-service-port-4553', 'port': 4553, 'targetPort': 'ext-svc-4553'}, {'name': 'external-service-port-4554', 'port': 4554, 'targetPort': 'ext-svc-4554'}, {'name': 'external-service-port-4555', 'port': 4555, 'targetPort': 'ext-svc-4555'}, {'name': 'external-service-port-4556', 'port': 4556, 'targetPort': 'ext-svc-4556'}, {'name': 'external-service-port-4557', 'port': 4557, 'targetPort': 'ext-svc-4557'}, {'name': 'external-service-port-4558', 'port': 4558, 'targetPort': 'ext-svc-4558'}, {'name': 'external-service-port-4559', 'port': 4559, 'targetPort': 'ext-svc-4559'}], 'selector': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}}, {'apiVersion': 'apps/v1', 'kind': 'Deployment', 'metadata': {'name': 'localstack'}, 'spec': {'replicas': 1, 'strategy': {'type': 'RollingUpdate'}, 'selector': {'matchLabels': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'template': {'metadata': {'labels': {'app': 'localstack-{{ spawner.user.name }}', 'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'spec': {'serviceAccountName': 'localstack', 'securityContext': {}, 'containers': [{'name': 'localstack', 'securityContext': {}, 'image': 'localstack/localstack:latest', 'imagePullPolicy': 'IfNotPresent', 'ports': [{'name': 'edge', 'containerPort': 4566, 'protocol': 'TCP'}, {'name': 'ext-svc-4510', 'containerPort': 4510, 'protocol': 'TCP'}, {'name': 'ext-svc-4511', 'containerPort': 4511, 'protocol': 'TCP'}, {'name': 'ext-svc-4512', 'containerPort': 4512, 'protocol': 'TCP'}, {'name': 'ext-svc-4513', 'containerPort': 4513, 'protocol': 'TCP'}, {'name': 'ext-svc-4514', 'containerPort': 4514, 'protocol': 'TCP'}, {'name': 'ext-svc-4515', 'containerPort': 4515, 'protocol': 'TCP'}, {'name': 'ext-svc-4516', 'containerPort': 4516, 'protocol': 'TCP'}, {'name': 'ext-svc-4517', 'containerPort': 4517, 'protocol': 'TCP'}, {'name': 'ext-svc-4518', 'containerPort': 4518, 'protocol': 'TCP'}, {'name': 'ext-svc-4519', 'containerPort': 4519, 'protocol': 'TCP'}, {'name': 'ext-svc-4520', 'containerPort': 4520, 'protocol': 'TCP'}, {'name': 'ext-svc-4521', 'containerPort': 4521, 'protocol': 'TCP'}, {'name': 'ext-svc-4522', 'containerPort': 4522, 'protocol': 'TCP'}, {'name': 'ext-svc-4523', 'containerPort': 4523, 'protocol': 'TCP'}, {'name': 'ext-svc-4524', 'containerPort': 4524, 'protocol': 'TCP'}, {'name': 'ext-svc-4525', 'containerPort': 4525, 'protocol': 'TCP'}, {'name': 'ext-svc-4526', 'containerPort': 4526, 'protocol': 'TCP'}, {'name': 'ext-svc-4527', 'containerPort': 4527, 'protocol': 'TCP'}, {'name': 'ext-svc-4528', 'containerPort': 4528, 'protocol': 'TCP'}, {'name': 'ext-svc-4529', 'containerPort': 4529, 'protocol': 'TCP'}, {'name': 'ext-svc-4530', 'containerPort': 4530, 'protocol': 'TCP'}, {'name': 'ext-svc-4531', 'containerPort': 4531, 'protocol': 'TCP'}, {'name': 'ext-svc-4532', 'containerPort': 4532, 'protocol': 'TCP'}, {'name': 'ext-svc-4533', 'containerPort': 4533, 'protocol': 'TCP'}, {'name': 'ext-svc-4534', 'containerPort': 4534, 'protocol': 'TCP'}, {'name': 'ext-svc-4535', 'containerPort': 4535, 'protocol': 'TCP'}, {'name': 'ext-svc-4536', 'containerPort': 4536, 'protocol': 'TCP'}, {'name': 'ext-svc-4537', 'containerPort': 4537, 'protocol': 'TCP'}, {'name': 'ext-svc-4538', 'containerPort': 4538, 'protocol': 'TCP'}, {'name': 'ext-svc-4539', 'containerPort': 4539, 'protocol': 'TCP'}, {'name': 'ext-svc-4540', 'containerPort': 4540, 'protocol': 'TCP'}, {'name': 'ext-svc-4541', 'containerPort': 4541, 'protocol': 'TCP'}, {'name': 'ext-svc-4542', 'containerPort': 4542, 'protocol': 'TCP'}, {'name': 'ext-svc-4543', 'containerPort': 4543, 'protocol': 'TCP'}, {'name': 'ext-svc-4544', 'containerPort': 4544, 'protocol': 'TCP'}, {'name': 'ext-svc-4545', 'containerPort': 4545, 'protocol': 'TCP'}, {'name': 'ext-svc-4546', 'containerPort': 4546, 'protocol': 'TCP'}, {'name': 'ext-svc-4547', 'containerPort': 4547, 'protocol': 'TCP'}, {'name': 'ext-svc-4548', 'containerPort': 4548, 'protocol': 'TCP'}, {'name': 'ext-svc-4549', 'containerPort': 4549, 'protocol': 'TCP'}, {'name': 'ext-svc-4550', 'containerPort': 4550, 'protocol': 'TCP'}, {'name': 'ext-svc-4551', 'containerPort': 4551, 'protocol': 'TCP'}, {'name': 'ext-svc-4552', 'containerPort': 4552, 'protocol': 'TCP'}, {'name': 'ext-svc-4553', 'containerPort': 4553, 'protocol': 'TCP'}, {'name': 'ext-svc-4554', 'containerPort': 4554, 'protocol': 'TCP'}, {'name': 'ext-svc-4555', 'containerPort': 4555, 'protocol': 'TCP'}, {'name': 'ext-svc-4556', 'containerPort': 4556, 'protocol': 'TCP'}, {'name': 'ext-svc-4557', 'containerPort': 4557, 'protocol': 'TCP'}, {'name': 'ext-svc-4558', 'containerPort': 4558, 'protocol': 'TCP'}, {'name': 'ext-svc-4559', 'containerPort': 4559, 'protocol': 'TCP'}], 'livenessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'readinessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'resources': {}, 'env': [{'name': 'DEBUG', 'value': '0'}, {'name': 'EXTERNAL_SERVICE_PORTS_START', 'value': '4510'}, {'name': 'EXTERNAL_SERVICE_PORTS_END', 'value': '4560'}, {'name': 'LOCALSTACK_K8S_SERVICE_NAME', 'value': 'localstack'}, {'name': 'LOCALSTACK_K8S_NAMESPACE', 'valueFrom': {'fieldRef': {'fieldPath': 'metadata.namespace'}}}, {'name': 'LAMBDA_RUNTIME_EXECUTOR', 'value': 'docker'}, {'name': 'LAMBDA_K8S_IMAGE_PREFIX', 'value': 'localstack/lambda-'}, {'name': 'LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT', 'value': '60'}, {'name': 'OVERRIDE_IN_DOCKER', 'value': '1'}]}], 'volumes': []}}}}, {'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': 'my-config'}, 'data': {'ENV_VAR1': 'value1', 'ENV_VAR2': 'value2'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'my-secret'}, 'type': 'Opaque', 'data': {'SECRET_KEY1': 'dmFsdWUx', 'SECRET_KEY2': 'dmFsdWUy'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'aws-credentials-{{ spawner.user.name }}'}, 'type': 'Opaque', 'data': {'credentials': 'W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg='}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'data-by-name', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'data-by-name', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': 'secret-value', 'remoteRef': {'key': 'secret-one', 'property': 'the-key'}}]}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'eoepca-plus-secret-ro', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'eoepca-plus-secret-ro', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': '.dockerconfigjson', 'remoteRef': {'key': 'eoepca-plus-secret-ro', 'property': '.dockerconfigjson'}}]}}], persist=False)], env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ + "localstack_manifest_path = os.path.join(parent_dir, \"manifests/manifest.yaml\") \n", "with open(localstack_manifest_path, \"r\") as f:\n", " content = yaml.safe_load_all(f.read())\n", "\n", "\n", - "localstack_manifest = Manifest(\n", + "localstack_manifest = Manifest( # type: ignore\n", " name=\"manifests\",\n", " key=\"manifests\",\n", " readonly=True,\n", " persist=False,\n", " content=[e for e in content],\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "image = \"jupyter/scipy-notebook\"\n", + ")\n", "\n", + "image = \"docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40\"\n", "\n", - "eoepca_jupyter_lab_profile_2 = Profile(\n", - " id=\"profile_jupyter_lab_2\",\n", - " groups=[\"group-c\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Jupyter Lab - profile 2\",\n", - " description=\"Jupyter Lab with Python 3.11 - demoes the use of an image pull secret\",\n", - " slug=\"eoepca_jupyter_lab_2\",\n", + "coder_profile_stac = Profile( # type: ignore\n", + " id=f\"profile_studio_coder_stac\",\n", + " groups=[\"group-a\", \"group-b\"], \n", + " definition=ProfileDefinition( # type: ignore\n", + " display_name=\"Understanding STAC for input/output data modelling\",\n", + " description=\"Understand the role of STAC in input/output data manifests in EO data processing workflows\",\n", + " slug=\"eoepca_coder_slug_stac\",\n", " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", " cpu_guarantee=1,\n", " cpu_limit=2,\n", " mem_guarantee=\"4G\",\n", @@ -516,85 +765,558 @@ " image=image,\n", " ),\n", " ),\n", - " node_selector=node_selector,\n", + " node_selector={},\n", " volumes=[workspace_volume],\n", - " config_maps=[],\n", + " config_maps=[init_stac_cm, bash_rc_cm, bash_login_cm],\n", " pod_env_vars={\n", " \"HOME\": \"/workspace\",\n", + " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", + " \"CONDARC\": \"/workspace/.condarc\",\n", " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", + " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", + " \"XDG_CONFIG_HOME\": \"/workspace/.local\",\n", + " \"XDG_DATA_HOME\": \"/workspace/.local/share/\",\n", + " \"CWLTOOL_OPTIONS\": \"--podman\",\n", + " \"CODE_SERVER_WS\": \"/workspace/stac-eoap\",\n", + " \"AWS_DEFAULT_REGION\": \"us-east-1\",\n", + " \"AWS_ACCESS_KEY_ID\": \"test\",\n", + " \"AWS_SECRET_ACCESS_KEY\": \"test\",\n", " },\n", + " role_bindings=[],\n", + " init_containers=[init_container],\n", " image_pull_secrets=[image_pull_secret],\n", " manifests=[localstack_manifest],\n", ")\n", "\n", - "profiles.append(eoepca_jupyter_lab_profile_2)" + "profiles.append(coder_profile_stac)\n", + "profiles" ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "## Configuration" + "### QGIS \n", + "This profile is configured to deploy the [QGIS](https://github.com/qgis/QGIS) application on the **App Hub**, enabling users to visualize and analyze Earth observation data directly within the platform. The deployment ensures that QGIS is configured with the necessary resources, storage, and dependencies to support geospatial analysis, map rendering, and data processing. By integrating QGIS into the App Hub, users can seamlessly interact with satellite imagery, vector layers, and raster data for advanced geospatial analysis." ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "metadata": {}, "outputs": [ { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_1697007/2113836574.py:5: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.11/migration/\n", - " yaml.dump(config.dict(), file)\n" - ] + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab_2', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', description='Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret', slug='eoepca_jupyter_lab_2', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder_stac', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Understanding STAC for input/output data modelling', description='Understand the role of STAC in input/output data manifests in EO data processing workflows', slug='eoepca_coder_slug_stac', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='set -x \\n\\ncd /workspace\\n\\ngit clone \\'https://github.com/eoap/stac-eoap.git\\'\\n\\ncode-server --install-extension ms-python.python \\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nmkdir -p /workspace/User/\\n\\necho \\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\' > /workspace/User/settings.json\\n\\npython -m venv /workspace/.venv\\nsource /workspace/.venv/bin/activate\\n/workspace/.venv/bin/python -m pip install --no-cache-dir stactools rasterio requests stac-asset click-logging tabulate tqdm pystac-client ipykernel loguru scikit-image rio_stac boto3==1.35.23\\n\\n/workspace/.venv/bin/python -m pip install --index-url https://test.pypi.org/simple cwl-wrapper\\n\\n/workspace/.venv/bin/python -m ipykernel install --user --name stac_env --display-name \"Python (STAC)\"\\n\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\nexport AWS_ACCESS_KEY_ID=\"test\"\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\naws s3 mb s3://results --endpoint-url=http://localstack:4566\\n\\nexit 0', persist=False), ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.local', 'XDG_DATA_HOME': '/workspace/.local/share/', 'CWLTOOL_OPTIONS': '--podman', 'CODE_SERVER_WS': '/workspace/stac-eoap', 'AWS_DEFAULT_REGION': 'us-east-1', 'AWS_ACCESS_KEY_ID': 'test', 'AWS_SECRET_ACCESS_KEY': 'test'}, default_url=None, node_selector={}, role_bindings=[], image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=[Manifest(name='manifests', key='manifests', content=[{'apiVersion': 'v1', 'kind': 'ServiceAccount', 'metadata': {'name': 'localstack'}}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'Role', 'metadata': {'name': 'localstack'}, 'rules': [{'apiGroups': [''], 'resources': ['pods'], 'verbs': ['*']}, {'apiGroups': [''], 'resources': ['pods/log'], 'verbs': ['get']}, {'apiGroups': [''], 'resources': ['pods/exec'], 'verbs': ['get', 'create']}, {'apiGroups': [''], 'resources': ['services'], 'verbs': ['get', 'list']}]}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'RoleBinding', 'metadata': {'name': 'localstack'}, 'subjects': [{'kind': 'ServiceAccount', 'name': 'localstack'}], 'roleRef': {'kind': 'Role', 'name': 'localstack', 'apiGroup': 'rbac.authorization.k8s.io'}}, {'apiVersion': 'v1', 'kind': 'Service', 'metadata': {'name': 'localstack'}, 'spec': {'type': 'ClusterIP', 'ports': [{'name': 'edge', 'port': 4566, 'targetPort': 4566}, {'name': 'external-service-port-4510', 'port': 4510, 'targetPort': 'ext-svc-4510'}, {'name': 'external-service-port-4511', 'port': 4511, 'targetPort': 'ext-svc-4511'}, {'name': 'external-service-port-4512', 'port': 4512, 'targetPort': 'ext-svc-4512'}, {'name': 'external-service-port-4513', 'port': 4513, 'targetPort': 'ext-svc-4513'}, {'name': 'external-service-port-4514', 'port': 4514, 'targetPort': 'ext-svc-4514'}, {'name': 'external-service-port-4515', 'port': 4515, 'targetPort': 'ext-svc-4515'}, {'name': 'external-service-port-4516', 'port': 4516, 'targetPort': 'ext-svc-4516'}, {'name': 'external-service-port-4517', 'port': 4517, 'targetPort': 'ext-svc-4517'}, {'name': 'external-service-port-4518', 'port': 4518, 'targetPort': 'ext-svc-4518'}, {'name': 'external-service-port-4519', 'port': 4519, 'targetPort': 'ext-svc-4519'}, {'name': 'external-service-port-4520', 'port': 4520, 'targetPort': 'ext-svc-4520'}, {'name': 'external-service-port-4521', 'port': 4521, 'targetPort': 'ext-svc-4521'}, {'name': 'external-service-port-4522', 'port': 4522, 'targetPort': 'ext-svc-4522'}, {'name': 'external-service-port-4523', 'port': 4523, 'targetPort': 'ext-svc-4523'}, {'name': 'external-service-port-4524', 'port': 4524, 'targetPort': 'ext-svc-4524'}, {'name': 'external-service-port-4525', 'port': 4525, 'targetPort': 'ext-svc-4525'}, {'name': 'external-service-port-4526', 'port': 4526, 'targetPort': 'ext-svc-4526'}, {'name': 'external-service-port-4527', 'port': 4527, 'targetPort': 'ext-svc-4527'}, {'name': 'external-service-port-4528', 'port': 4528, 'targetPort': 'ext-svc-4528'}, {'name': 'external-service-port-4529', 'port': 4529, 'targetPort': 'ext-svc-4529'}, {'name': 'external-service-port-4530', 'port': 4530, 'targetPort': 'ext-svc-4530'}, {'name': 'external-service-port-4531', 'port': 4531, 'targetPort': 'ext-svc-4531'}, {'name': 'external-service-port-4532', 'port': 4532, 'targetPort': 'ext-svc-4532'}, {'name': 'external-service-port-4533', 'port': 4533, 'targetPort': 'ext-svc-4533'}, {'name': 'external-service-port-4534', 'port': 4534, 'targetPort': 'ext-svc-4534'}, {'name': 'external-service-port-4535', 'port': 4535, 'targetPort': 'ext-svc-4535'}, {'name': 'external-service-port-4536', 'port': 4536, 'targetPort': 'ext-svc-4536'}, {'name': 'external-service-port-4537', 'port': 4537, 'targetPort': 'ext-svc-4537'}, {'name': 'external-service-port-4538', 'port': 4538, 'targetPort': 'ext-svc-4538'}, {'name': 'external-service-port-4539', 'port': 4539, 'targetPort': 'ext-svc-4539'}, {'name': 'external-service-port-4540', 'port': 4540, 'targetPort': 'ext-svc-4540'}, {'name': 'external-service-port-4541', 'port': 4541, 'targetPort': 'ext-svc-4541'}, {'name': 'external-service-port-4542', 'port': 4542, 'targetPort': 'ext-svc-4542'}, {'name': 'external-service-port-4543', 'port': 4543, 'targetPort': 'ext-svc-4543'}, {'name': 'external-service-port-4544', 'port': 4544, 'targetPort': 'ext-svc-4544'}, {'name': 'external-service-port-4545', 'port': 4545, 'targetPort': 'ext-svc-4545'}, {'name': 'external-service-port-4546', 'port': 4546, 'targetPort': 'ext-svc-4546'}, {'name': 'external-service-port-4547', 'port': 4547, 'targetPort': 'ext-svc-4547'}, {'name': 'external-service-port-4548', 'port': 4548, 'targetPort': 'ext-svc-4548'}, {'name': 'external-service-port-4549', 'port': 4549, 'targetPort': 'ext-svc-4549'}, {'name': 'external-service-port-4550', 'port': 4550, 'targetPort': 'ext-svc-4550'}, {'name': 'external-service-port-4551', 'port': 4551, 'targetPort': 'ext-svc-4551'}, {'name': 'external-service-port-4552', 'port': 4552, 'targetPort': 'ext-svc-4552'}, {'name': 'external-service-port-4553', 'port': 4553, 'targetPort': 'ext-svc-4553'}, {'name': 'external-service-port-4554', 'port': 4554, 'targetPort': 'ext-svc-4554'}, {'name': 'external-service-port-4555', 'port': 4555, 'targetPort': 'ext-svc-4555'}, {'name': 'external-service-port-4556', 'port': 4556, 'targetPort': 'ext-svc-4556'}, {'name': 'external-service-port-4557', 'port': 4557, 'targetPort': 'ext-svc-4557'}, {'name': 'external-service-port-4558', 'port': 4558, 'targetPort': 'ext-svc-4558'}, {'name': 'external-service-port-4559', 'port': 4559, 'targetPort': 'ext-svc-4559'}], 'selector': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}}, {'apiVersion': 'apps/v1', 'kind': 'Deployment', 'metadata': {'name': 'localstack'}, 'spec': {'replicas': 1, 'strategy': {'type': 'RollingUpdate'}, 'selector': {'matchLabels': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'template': {'metadata': {'labels': {'app': 'localstack-{{ spawner.user.name }}', 'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'spec': {'serviceAccountName': 'localstack', 'securityContext': {}, 'containers': [{'name': 'localstack', 'securityContext': {}, 'image': 'localstack/localstack:latest', 'imagePullPolicy': 'IfNotPresent', 'ports': [{'name': 'edge', 'containerPort': 4566, 'protocol': 'TCP'}, {'name': 'ext-svc-4510', 'containerPort': 4510, 'protocol': 'TCP'}, {'name': 'ext-svc-4511', 'containerPort': 4511, 'protocol': 'TCP'}, {'name': 'ext-svc-4512', 'containerPort': 4512, 'protocol': 'TCP'}, {'name': 'ext-svc-4513', 'containerPort': 4513, 'protocol': 'TCP'}, {'name': 'ext-svc-4514', 'containerPort': 4514, 'protocol': 'TCP'}, {'name': 'ext-svc-4515', 'containerPort': 4515, 'protocol': 'TCP'}, {'name': 'ext-svc-4516', 'containerPort': 4516, 'protocol': 'TCP'}, {'name': 'ext-svc-4517', 'containerPort': 4517, 'protocol': 'TCP'}, {'name': 'ext-svc-4518', 'containerPort': 4518, 'protocol': 'TCP'}, {'name': 'ext-svc-4519', 'containerPort': 4519, 'protocol': 'TCP'}, {'name': 'ext-svc-4520', 'containerPort': 4520, 'protocol': 'TCP'}, {'name': 'ext-svc-4521', 'containerPort': 4521, 'protocol': 'TCP'}, {'name': 'ext-svc-4522', 'containerPort': 4522, 'protocol': 'TCP'}, {'name': 'ext-svc-4523', 'containerPort': 4523, 'protocol': 'TCP'}, {'name': 'ext-svc-4524', 'containerPort': 4524, 'protocol': 'TCP'}, {'name': 'ext-svc-4525', 'containerPort': 4525, 'protocol': 'TCP'}, {'name': 'ext-svc-4526', 'containerPort': 4526, 'protocol': 'TCP'}, {'name': 'ext-svc-4527', 'containerPort': 4527, 'protocol': 'TCP'}, {'name': 'ext-svc-4528', 'containerPort': 4528, 'protocol': 'TCP'}, {'name': 'ext-svc-4529', 'containerPort': 4529, 'protocol': 'TCP'}, {'name': 'ext-svc-4530', 'containerPort': 4530, 'protocol': 'TCP'}, {'name': 'ext-svc-4531', 'containerPort': 4531, 'protocol': 'TCP'}, {'name': 'ext-svc-4532', 'containerPort': 4532, 'protocol': 'TCP'}, {'name': 'ext-svc-4533', 'containerPort': 4533, 'protocol': 'TCP'}, {'name': 'ext-svc-4534', 'containerPort': 4534, 'protocol': 'TCP'}, {'name': 'ext-svc-4535', 'containerPort': 4535, 'protocol': 'TCP'}, {'name': 'ext-svc-4536', 'containerPort': 4536, 'protocol': 'TCP'}, {'name': 'ext-svc-4537', 'containerPort': 4537, 'protocol': 'TCP'}, {'name': 'ext-svc-4538', 'containerPort': 4538, 'protocol': 'TCP'}, {'name': 'ext-svc-4539', 'containerPort': 4539, 'protocol': 'TCP'}, {'name': 'ext-svc-4540', 'containerPort': 4540, 'protocol': 'TCP'}, {'name': 'ext-svc-4541', 'containerPort': 4541, 'protocol': 'TCP'}, {'name': 'ext-svc-4542', 'containerPort': 4542, 'protocol': 'TCP'}, {'name': 'ext-svc-4543', 'containerPort': 4543, 'protocol': 'TCP'}, {'name': 'ext-svc-4544', 'containerPort': 4544, 'protocol': 'TCP'}, {'name': 'ext-svc-4545', 'containerPort': 4545, 'protocol': 'TCP'}, {'name': 'ext-svc-4546', 'containerPort': 4546, 'protocol': 'TCP'}, {'name': 'ext-svc-4547', 'containerPort': 4547, 'protocol': 'TCP'}, {'name': 'ext-svc-4548', 'containerPort': 4548, 'protocol': 'TCP'}, {'name': 'ext-svc-4549', 'containerPort': 4549, 'protocol': 'TCP'}, {'name': 'ext-svc-4550', 'containerPort': 4550, 'protocol': 'TCP'}, {'name': 'ext-svc-4551', 'containerPort': 4551, 'protocol': 'TCP'}, {'name': 'ext-svc-4552', 'containerPort': 4552, 'protocol': 'TCP'}, {'name': 'ext-svc-4553', 'containerPort': 4553, 'protocol': 'TCP'}, {'name': 'ext-svc-4554', 'containerPort': 4554, 'protocol': 'TCP'}, {'name': 'ext-svc-4555', 'containerPort': 4555, 'protocol': 'TCP'}, {'name': 'ext-svc-4556', 'containerPort': 4556, 'protocol': 'TCP'}, {'name': 'ext-svc-4557', 'containerPort': 4557, 'protocol': 'TCP'}, {'name': 'ext-svc-4558', 'containerPort': 4558, 'protocol': 'TCP'}, {'name': 'ext-svc-4559', 'containerPort': 4559, 'protocol': 'TCP'}], 'livenessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'readinessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'resources': {}, 'env': [{'name': 'DEBUG', 'value': '0'}, {'name': 'EXTERNAL_SERVICE_PORTS_START', 'value': '4510'}, {'name': 'EXTERNAL_SERVICE_PORTS_END', 'value': '4560'}, {'name': 'LOCALSTACK_K8S_SERVICE_NAME', 'value': 'localstack'}, {'name': 'LOCALSTACK_K8S_NAMESPACE', 'valueFrom': {'fieldRef': {'fieldPath': 'metadata.namespace'}}}, {'name': 'LAMBDA_RUNTIME_EXECUTOR', 'value': 'docker'}, {'name': 'LAMBDA_K8S_IMAGE_PREFIX', 'value': 'localstack/lambda-'}, {'name': 'LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT', 'value': '60'}, {'name': 'OVERRIDE_IN_DOCKER', 'value': '1'}]}], 'volumes': []}}}}, {'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': 'my-config'}, 'data': {'ENV_VAR1': 'value1', 'ENV_VAR2': 'value2'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'my-secret'}, 'type': 'Opaque', 'data': {'SECRET_KEY1': 'dmFsdWUx', 'SECRET_KEY2': 'dmFsdWUy'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'aws-credentials-{{ spawner.user.name }}'}, 'type': 'Opaque', 'data': {'credentials': 'W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg='}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'data-by-name', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'data-by-name', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': 'secret-value', 'remoteRef': {'key': 'secret-one', 'property': 'the-key'}}]}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'eoepca-plus-secret-ro', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'eoepca-plus-secret-ro', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': '.dockerconfigjson', 'remoteRef': {'key': 'eoepca-plus-secret-ro', 'property': '.dockerconfigjson'}}]}}], persist=False)], env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_desktop_qgis', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='QGIS on a Remote Desktop', description='Spatial visualization and decision-making tools for everyone', slug='eoepca_desktop_qgis', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='2G', mem_guarantee=None, image='eoepca/iga-remote-desktop-qgis:1.1.3', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace'}, default_url='desktop', node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "config = Config(profiles=profiles)\n", - "config_file_path = str(Path(current_dir).parent.parent / 'files' / 'hub' / 'config.yml')\n", + "image = \"eoepca/iga-remote-desktop-qgis:1.1.3\"\n", + "\n", + "qgis_profile = Profile( # type: ignore\n", + " id=\"profile_studio_desktop_qgis\",\n", + " groups=[\"group-a\", \"group-b\"],\n", + " definition=ProfileDefinition( # type: ignore\n", + " display_name=\"QGIS on a Remote Desktop\",\n", + " description=\"Spatial visualization and decision-making tools for everyone\",\n", + " slug=\"eoepca_desktop_qgis\",\n", + " default=False,\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", + " cpu_limit=2,\n", + " mem_limit=\"2G\",\n", + " image=image,\n", + " ),\n", + " ),\n", + " node_selector={},\n", + " volumes=[workspace_volume],\n", + " config_maps=[bash_rc_cm, bash_login_cm],\n", + " pod_env_vars={\"HOME\": \"/workspace\"},\n", + " default_url=\"desktop\",\n", + " init_containers=[],\n", + ")\n", "\n", - "with open(config_file_path, \"w\") as file:\n", - " yaml.dump(config.dict(), file)" + "profiles.append(qgis_profile)\n", + "profiles" ] }, { - "cell_type": "code", - "execution_count": 17, + "attachments": {}, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "# profiles" + "## Write Configuration \n", + "In the code below, the user inspects all the configurations made in the previous section and then generates a [config.yaml](../../files/hub/config.yml) file. This file contains the complete configuration of the **App Hub**, including the different profiles defined for deployment. The `config.yaml` serves as the configuration blueprint, allowing the user to easily apply and share the profiles across different environments or clusters." ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab_2', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', description='Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret', slug='eoepca_jupyter_lab_2', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder_stac', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Understanding STAC for input/output data modelling', description='Understand the role of STAC in input/output data manifests in EO data processing workflows', slug='eoepca_coder_slug_stac', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='set -x \\n\\ncd /workspace\\n\\ngit clone \\'https://github.com/eoap/stac-eoap.git\\'\\n\\ncode-server --install-extension ms-python.python \\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nmkdir -p /workspace/User/\\n\\necho \\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\' > /workspace/User/settings.json\\n\\npython -m venv /workspace/.venv\\nsource /workspace/.venv/bin/activate\\n/workspace/.venv/bin/python -m pip install --no-cache-dir stactools rasterio requests stac-asset click-logging tabulate tqdm pystac-client ipykernel loguru scikit-image rio_stac boto3==1.35.23\\n\\n/workspace/.venv/bin/python -m pip install --index-url https://test.pypi.org/simple cwl-wrapper\\n\\n/workspace/.venv/bin/python -m ipykernel install --user --name stac_env --display-name \"Python (STAC)\"\\n\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\nexport AWS_ACCESS_KEY_ID=\"test\"\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\naws s3 mb s3://results --endpoint-url=http://localstack:4566\\n\\nexit 0', persist=False), ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.local', 'XDG_DATA_HOME': '/workspace/.local/share/', 'CWLTOOL_OPTIONS': '--podman', 'CODE_SERVER_WS': '/workspace/stac-eoap', 'AWS_DEFAULT_REGION': 'us-east-1', 'AWS_ACCESS_KEY_ID': 'test', 'AWS_SECRET_ACCESS_KEY': 'test'}, default_url=None, node_selector={}, role_bindings=[], image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=[Manifest(name='manifests', key='manifests', content=[{'apiVersion': 'v1', 'kind': 'ServiceAccount', 'metadata': {'name': 'localstack'}}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'Role', 'metadata': {'name': 'localstack'}, 'rules': [{'apiGroups': [''], 'resources': ['pods'], 'verbs': ['*']}, {'apiGroups': [''], 'resources': ['pods/log'], 'verbs': ['get']}, {'apiGroups': [''], 'resources': ['pods/exec'], 'verbs': ['get', 'create']}, {'apiGroups': [''], 'resources': ['services'], 'verbs': ['get', 'list']}]}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'RoleBinding', 'metadata': {'name': 'localstack'}, 'subjects': [{'kind': 'ServiceAccount', 'name': 'localstack'}], 'roleRef': {'kind': 'Role', 'name': 'localstack', 'apiGroup': 'rbac.authorization.k8s.io'}}, {'apiVersion': 'v1', 'kind': 'Service', 'metadata': {'name': 'localstack'}, 'spec': {'type': 'ClusterIP', 'ports': [{'name': 'edge', 'port': 4566, 'targetPort': 4566}, {'name': 'external-service-port-4510', 'port': 4510, 'targetPort': 'ext-svc-4510'}, {'name': 'external-service-port-4511', 'port': 4511, 'targetPort': 'ext-svc-4511'}, {'name': 'external-service-port-4512', 'port': 4512, 'targetPort': 'ext-svc-4512'}, {'name': 'external-service-port-4513', 'port': 4513, 'targetPort': 'ext-svc-4513'}, {'name': 'external-service-port-4514', 'port': 4514, 'targetPort': 'ext-svc-4514'}, {'name': 'external-service-port-4515', 'port': 4515, 'targetPort': 'ext-svc-4515'}, {'name': 'external-service-port-4516', 'port': 4516, 'targetPort': 'ext-svc-4516'}, {'name': 'external-service-port-4517', 'port': 4517, 'targetPort': 'ext-svc-4517'}, {'name': 'external-service-port-4518', 'port': 4518, 'targetPort': 'ext-svc-4518'}, {'name': 'external-service-port-4519', 'port': 4519, 'targetPort': 'ext-svc-4519'}, {'name': 'external-service-port-4520', 'port': 4520, 'targetPort': 'ext-svc-4520'}, {'name': 'external-service-port-4521', 'port': 4521, 'targetPort': 'ext-svc-4521'}, {'name': 'external-service-port-4522', 'port': 4522, 'targetPort': 'ext-svc-4522'}, {'name': 'external-service-port-4523', 'port': 4523, 'targetPort': 'ext-svc-4523'}, {'name': 'external-service-port-4524', 'port': 4524, 'targetPort': 'ext-svc-4524'}, {'name': 'external-service-port-4525', 'port': 4525, 'targetPort': 'ext-svc-4525'}, {'name': 'external-service-port-4526', 'port': 4526, 'targetPort': 'ext-svc-4526'}, {'name': 'external-service-port-4527', 'port': 4527, 'targetPort': 'ext-svc-4527'}, {'name': 'external-service-port-4528', 'port': 4528, 'targetPort': 'ext-svc-4528'}, {'name': 'external-service-port-4529', 'port': 4529, 'targetPort': 'ext-svc-4529'}, {'name': 'external-service-port-4530', 'port': 4530, 'targetPort': 'ext-svc-4530'}, {'name': 'external-service-port-4531', 'port': 4531, 'targetPort': 'ext-svc-4531'}, {'name': 'external-service-port-4532', 'port': 4532, 'targetPort': 'ext-svc-4532'}, {'name': 'external-service-port-4533', 'port': 4533, 'targetPort': 'ext-svc-4533'}, {'name': 'external-service-port-4534', 'port': 4534, 'targetPort': 'ext-svc-4534'}, {'name': 'external-service-port-4535', 'port': 4535, 'targetPort': 'ext-svc-4535'}, {'name': 'external-service-port-4536', 'port': 4536, 'targetPort': 'ext-svc-4536'}, {'name': 'external-service-port-4537', 'port': 4537, 'targetPort': 'ext-svc-4537'}, {'name': 'external-service-port-4538', 'port': 4538, 'targetPort': 'ext-svc-4538'}, {'name': 'external-service-port-4539', 'port': 4539, 'targetPort': 'ext-svc-4539'}, {'name': 'external-service-port-4540', 'port': 4540, 'targetPort': 'ext-svc-4540'}, {'name': 'external-service-port-4541', 'port': 4541, 'targetPort': 'ext-svc-4541'}, {'name': 'external-service-port-4542', 'port': 4542, 'targetPort': 'ext-svc-4542'}, {'name': 'external-service-port-4543', 'port': 4543, 'targetPort': 'ext-svc-4543'}, {'name': 'external-service-port-4544', 'port': 4544, 'targetPort': 'ext-svc-4544'}, {'name': 'external-service-port-4545', 'port': 4545, 'targetPort': 'ext-svc-4545'}, {'name': 'external-service-port-4546', 'port': 4546, 'targetPort': 'ext-svc-4546'}, {'name': 'external-service-port-4547', 'port': 4547, 'targetPort': 'ext-svc-4547'}, {'name': 'external-service-port-4548', 'port': 4548, 'targetPort': 'ext-svc-4548'}, {'name': 'external-service-port-4549', 'port': 4549, 'targetPort': 'ext-svc-4549'}, {'name': 'external-service-port-4550', 'port': 4550, 'targetPort': 'ext-svc-4550'}, {'name': 'external-service-port-4551', 'port': 4551, 'targetPort': 'ext-svc-4551'}, {'name': 'external-service-port-4552', 'port': 4552, 'targetPort': 'ext-svc-4552'}, {'name': 'external-service-port-4553', 'port': 4553, 'targetPort': 'ext-svc-4553'}, {'name': 'external-service-port-4554', 'port': 4554, 'targetPort': 'ext-svc-4554'}, {'name': 'external-service-port-4555', 'port': 4555, 'targetPort': 'ext-svc-4555'}, {'name': 'external-service-port-4556', 'port': 4556, 'targetPort': 'ext-svc-4556'}, {'name': 'external-service-port-4557', 'port': 4557, 'targetPort': 'ext-svc-4557'}, {'name': 'external-service-port-4558', 'port': 4558, 'targetPort': 'ext-svc-4558'}, {'name': 'external-service-port-4559', 'port': 4559, 'targetPort': 'ext-svc-4559'}], 'selector': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}}, {'apiVersion': 'apps/v1', 'kind': 'Deployment', 'metadata': {'name': 'localstack'}, 'spec': {'replicas': 1, 'strategy': {'type': 'RollingUpdate'}, 'selector': {'matchLabels': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'template': {'metadata': {'labels': {'app': 'localstack-{{ spawner.user.name }}', 'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'spec': {'serviceAccountName': 'localstack', 'securityContext': {}, 'containers': [{'name': 'localstack', 'securityContext': {}, 'image': 'localstack/localstack:latest', 'imagePullPolicy': 'IfNotPresent', 'ports': [{'name': 'edge', 'containerPort': 4566, 'protocol': 'TCP'}, {'name': 'ext-svc-4510', 'containerPort': 4510, 'protocol': 'TCP'}, {'name': 'ext-svc-4511', 'containerPort': 4511, 'protocol': 'TCP'}, {'name': 'ext-svc-4512', 'containerPort': 4512, 'protocol': 'TCP'}, {'name': 'ext-svc-4513', 'containerPort': 4513, 'protocol': 'TCP'}, {'name': 'ext-svc-4514', 'containerPort': 4514, 'protocol': 'TCP'}, {'name': 'ext-svc-4515', 'containerPort': 4515, 'protocol': 'TCP'}, {'name': 'ext-svc-4516', 'containerPort': 4516, 'protocol': 'TCP'}, {'name': 'ext-svc-4517', 'containerPort': 4517, 'protocol': 'TCP'}, {'name': 'ext-svc-4518', 'containerPort': 4518, 'protocol': 'TCP'}, {'name': 'ext-svc-4519', 'containerPort': 4519, 'protocol': 'TCP'}, {'name': 'ext-svc-4520', 'containerPort': 4520, 'protocol': 'TCP'}, {'name': 'ext-svc-4521', 'containerPort': 4521, 'protocol': 'TCP'}, {'name': 'ext-svc-4522', 'containerPort': 4522, 'protocol': 'TCP'}, {'name': 'ext-svc-4523', 'containerPort': 4523, 'protocol': 'TCP'}, {'name': 'ext-svc-4524', 'containerPort': 4524, 'protocol': 'TCP'}, {'name': 'ext-svc-4525', 'containerPort': 4525, 'protocol': 'TCP'}, {'name': 'ext-svc-4526', 'containerPort': 4526, 'protocol': 'TCP'}, {'name': 'ext-svc-4527', 'containerPort': 4527, 'protocol': 'TCP'}, {'name': 'ext-svc-4528', 'containerPort': 4528, 'protocol': 'TCP'}, {'name': 'ext-svc-4529', 'containerPort': 4529, 'protocol': 'TCP'}, {'name': 'ext-svc-4530', 'containerPort': 4530, 'protocol': 'TCP'}, {'name': 'ext-svc-4531', 'containerPort': 4531, 'protocol': 'TCP'}, {'name': 'ext-svc-4532', 'containerPort': 4532, 'protocol': 'TCP'}, {'name': 'ext-svc-4533', 'containerPort': 4533, 'protocol': 'TCP'}, {'name': 'ext-svc-4534', 'containerPort': 4534, 'protocol': 'TCP'}, {'name': 'ext-svc-4535', 'containerPort': 4535, 'protocol': 'TCP'}, {'name': 'ext-svc-4536', 'containerPort': 4536, 'protocol': 'TCP'}, {'name': 'ext-svc-4537', 'containerPort': 4537, 'protocol': 'TCP'}, {'name': 'ext-svc-4538', 'containerPort': 4538, 'protocol': 'TCP'}, {'name': 'ext-svc-4539', 'containerPort': 4539, 'protocol': 'TCP'}, {'name': 'ext-svc-4540', 'containerPort': 4540, 'protocol': 'TCP'}, {'name': 'ext-svc-4541', 'containerPort': 4541, 'protocol': 'TCP'}, {'name': 'ext-svc-4542', 'containerPort': 4542, 'protocol': 'TCP'}, {'name': 'ext-svc-4543', 'containerPort': 4543, 'protocol': 'TCP'}, {'name': 'ext-svc-4544', 'containerPort': 4544, 'protocol': 'TCP'}, {'name': 'ext-svc-4545', 'containerPort': 4545, 'protocol': 'TCP'}, {'name': 'ext-svc-4546', 'containerPort': 4546, 'protocol': 'TCP'}, {'name': 'ext-svc-4547', 'containerPort': 4547, 'protocol': 'TCP'}, {'name': 'ext-svc-4548', 'containerPort': 4548, 'protocol': 'TCP'}, {'name': 'ext-svc-4549', 'containerPort': 4549, 'protocol': 'TCP'}, {'name': 'ext-svc-4550', 'containerPort': 4550, 'protocol': 'TCP'}, {'name': 'ext-svc-4551', 'containerPort': 4551, 'protocol': 'TCP'}, {'name': 'ext-svc-4552', 'containerPort': 4552, 'protocol': 'TCP'}, {'name': 'ext-svc-4553', 'containerPort': 4553, 'protocol': 'TCP'}, {'name': 'ext-svc-4554', 'containerPort': 4554, 'protocol': 'TCP'}, {'name': 'ext-svc-4555', 'containerPort': 4555, 'protocol': 'TCP'}, {'name': 'ext-svc-4556', 'containerPort': 4556, 'protocol': 'TCP'}, {'name': 'ext-svc-4557', 'containerPort': 4557, 'protocol': 'TCP'}, {'name': 'ext-svc-4558', 'containerPort': 4558, 'protocol': 'TCP'}, {'name': 'ext-svc-4559', 'containerPort': 4559, 'protocol': 'TCP'}], 'livenessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'readinessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'resources': {}, 'env': [{'name': 'DEBUG', 'value': '0'}, {'name': 'EXTERNAL_SERVICE_PORTS_START', 'value': '4510'}, {'name': 'EXTERNAL_SERVICE_PORTS_END', 'value': '4560'}, {'name': 'LOCALSTACK_K8S_SERVICE_NAME', 'value': 'localstack'}, {'name': 'LOCALSTACK_K8S_NAMESPACE', 'valueFrom': {'fieldRef': {'fieldPath': 'metadata.namespace'}}}, {'name': 'LAMBDA_RUNTIME_EXECUTOR', 'value': 'docker'}, {'name': 'LAMBDA_K8S_IMAGE_PREFIX', 'value': 'localstack/lambda-'}, {'name': 'LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT', 'value': '60'}, {'name': 'OVERRIDE_IN_DOCKER', 'value': '1'}]}], 'volumes': []}}}}, {'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': 'my-config'}, 'data': {'ENV_VAR1': 'value1', 'ENV_VAR2': 'value2'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'my-secret'}, 'type': 'Opaque', 'data': {'SECRET_KEY1': 'dmFsdWUx', 'SECRET_KEY2': 'dmFsdWUy'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'aws-credentials-{{ spawner.user.name }}'}, 'type': 'Opaque', 'data': {'credentials': 'W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg='}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'data-by-name', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'data-by-name', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': 'secret-value', 'remoteRef': {'key': 'secret-one', 'property': 'the-key'}}]}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'eoepca-plus-secret-ro', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'eoepca-plus-secret-ro', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': '.dockerconfigjson', 'remoteRef': {'key': 'eoepca-plus-secret-ro', 'property': '.dockerconfigjson'}}]}}], persist=False)], env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_desktop_qgis', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='QGIS on a Remote Desktop', description='Spatial visualization and decision-making tools for everyone', slug='eoepca_desktop_qgis', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='2G', mem_guarantee=None, image='eoepca/iga-remote-desktop-qgis:1.1.3', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace'}, default_url='desktop', node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]\n", + "(\"config: profiles=[Profile(id='profile_studio_coder1', groups=['group-a', \"\n", + " \"'group-b'], definition=ProfileDefinition(display_name='Code Server Small', \"\n", + " \"description=None, slug='ellip_studio_coder_slug_s', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, '\n", + " \"mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', \"\n", + " 'extra_resource_limits={}, extra_resource_guarantees={})), '\n", + " \"config_maps=[ConfigMap(name='bash-rc', key='bash-rc', \"\n", + " \"mount_path='/workspace/.bashrc', default_mode=None, readonly=True, \"\n", + " 'content=\\'alias ll=\"ls -l\"\\\\nalias calrissian=\"/opt/conda/bin/calrissian '\n", + " '--pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout '\n", + " '/calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix '\n", + " '/calrissian/tmp/ --outdir /calrissian/\"\\\\nalias '\n", + " 'cwltool=\"/opt/conda/bin/cwltool --podman\"\\\\n. '\n", + " '/home/jovyan/.bashrc\\\\n\\\\n#alias aws=\"aws '\n", + " '--endpoint-url=http://localstack:4566\"\\\\n\\\\n# >>> conda initialize >>>\\\\n# '\n", + " \"!! Contents within this block are managed by \\\\'conda init\\\\' \"\n", + " '!!\\\\n__conda_setup=\"$(\\\\\\'/opt/conda/bin/conda\\\\\\' \\\\\\'shell.bash\\\\\\' '\n", + " '\\\\\\'hook\\\\\\' 2> /dev/null)\"\\\\nif [ $? -eq 0 ]; then\\\\n eval '\n", + " '\"$__conda_setup\"\\\\nelse\\\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; '\n", + " 'then\\\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\\\n else\\\\n '\n", + " 'export PATH=\"/srv/conda/bin:$PATH\"\\\\n fi\\\\nfi\\\\nunset '\n", + " '__conda_setup\\\\n\\\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\\\n '\n", + " '. \"/opt/conda/etc/profile.d/mamba.sh\"\\\\nfi\\\\n# <<< conda initialize '\n", + " '<<<\\\\n\\\\na={{spawner.user.name}}\\\\n\\\\nalias aws=\"aws '\n", + " '--endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"\\', '\n", + " \"persist=False)], volumes=[Volume(name='calrissian-volume', \"\n", + " \"claim_name='calrissian-claim', size='50Gi', \"\n", + " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], \"\n", + " \"volume_mount=VolumeMount(name='calrissian-volume', \"\n", + " \"mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', \"\n", + " \"claim_name='workspace-claim', size='50Gi', \"\n", + " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': \"\n", + " \"'/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, \"\n", + " 'image_pull_secrets=[], init_containers=[], manifests=None, '\n", + " 'env_from_config_maps=None, env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], \"\n", + " \"definition=ProfileDefinition(display_name='Code Server Medium', \"\n", + " \"description=None, slug='ellip_studio_coder_slug_m', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, '\n", + " \"mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', \"\n", + " 'extra_resource_limits={}, extra_resource_guarantees={})), '\n", + " \"config_maps=[ConfigMap(name='bash-rc', key='bash-rc', \"\n", + " \"mount_path='/workspace/.bashrc', default_mode=None, readonly=True, \"\n", + " 'content=\\'alias ll=\"ls -l\"\\\\nalias calrissian=\"/opt/conda/bin/calrissian '\n", + " '--pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout '\n", + " '/calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix '\n", + " '/calrissian/tmp/ --outdir /calrissian/\"\\\\nalias '\n", + " 'cwltool=\"/opt/conda/bin/cwltool --podman\"\\\\n. '\n", + " '/home/jovyan/.bashrc\\\\n\\\\n#alias aws=\"aws '\n", + " '--endpoint-url=http://localstack:4566\"\\\\n\\\\n# >>> conda initialize >>>\\\\n# '\n", + " \"!! Contents within this block are managed by \\\\'conda init\\\\' \"\n", + " '!!\\\\n__conda_setup=\"$(\\\\\\'/opt/conda/bin/conda\\\\\\' \\\\\\'shell.bash\\\\\\' '\n", + " '\\\\\\'hook\\\\\\' 2> /dev/null)\"\\\\nif [ $? -eq 0 ]; then\\\\n eval '\n", + " '\"$__conda_setup\"\\\\nelse\\\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; '\n", + " 'then\\\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\\\n else\\\\n '\n", + " 'export PATH=\"/srv/conda/bin:$PATH\"\\\\n fi\\\\nfi\\\\nunset '\n", + " '__conda_setup\\\\n\\\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\\\n '\n", + " '. \"/opt/conda/etc/profile.d/mamba.sh\"\\\\nfi\\\\n# <<< conda initialize '\n", + " '<<<\\\\n\\\\na={{spawner.user.name}}\\\\n\\\\nalias aws=\"aws '\n", + " '--endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"\\', '\n", + " \"persist=False)], volumes=[Volume(name='calrissian-volume', \"\n", + " \"claim_name='calrissian-claim', size='50Gi', \"\n", + " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], \"\n", + " \"volume_mount=VolumeMount(name='calrissian-volume', \"\n", + " \"mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', \"\n", + " \"claim_name='workspace-claim', size='50Gi', \"\n", + " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': \"\n", + " \"'/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, \"\n", + " 'image_pull_secrets=[], init_containers=[], manifests=None, '\n", + " 'env_from_config_maps=None, env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], \"\n", + " \"definition=ProfileDefinition(display_name='Coder demo init script', \"\n", + " \"description='This profile is used to demonstrate the use of an init script', \"\n", + " \"slug='eoepca_demo_init_script', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, '\n", + " \"mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', \"\n", + " 'extra_resource_limits={}, extra_resource_guarantees={})), '\n", + " \"config_maps=[ConfigMap(name='init', key='init', \"\n", + " \"mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, \"\n", + " 'content=\"set -x\\\\n\\\\ncd /workspace\\\\n\\\\ngit clone '\n", + " \"'https://github.com/eoap/mastering-app-package.git'\\\\n\\\\ncode-server \"\n", + " '--install-extension ms-python.python\\\\ncode-server --install-extension '\n", + " 'redhat.vscode-yaml\\\\ncode-server --install-extension '\n", + " 'sbg-rabix.benten-cwl\\\\ncode-server --install-extension '\n", + " 'ms-toolsai.jupyter\\\\n\\\\nln -s /workspace/.local/share/code-server/extensions '\n", + " '/workspace/extensions\\\\n\\\\nexit 0\\\\n\", persist=False)], '\n", + " \"volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', \"\n", + " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", + " \"access_modes=['ReadWriteMany'], \"\n", + " \"volume_mount=VolumeMount(name='calrissian-volume', \"\n", + " \"mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', \"\n", + " \"claim_name='workspace-claim', size='50Gi', \"\n", + " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': \"\n", + " \"'/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': \"\n", + " \"'/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, \"\n", + " 'default_url=None, node_selector={}, role_bindings=None, '\n", + " 'image_pull_secrets=[], '\n", + " \"init_containers=[InitContainer(name='init-file-on-volume', \"\n", + " \"image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh \"\n", + " \"/opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', \"\n", + " \"mount_path='/workspace'), InitContainerVolumeMount(name='init', \"\n", + " \"mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, \"\n", + " 'env_from_config_maps=None, env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_jupyter_lab', groups=['group-c'], \"\n", + " \"definition=ProfileDefinition(display_name='Jupyter Lab', \"\n", + " \"description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', \"\n", + " 'default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, '\n", + " \"cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', \"\n", + " \"image='jupyter/scipy-notebook', extra_resource_limits={}, \"\n", + " 'extra_resource_guarantees={})), config_maps=[], '\n", + " \"volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', \"\n", + " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", + " \"access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': \"\n", + " \"'/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, \"\n", + " 'default_url=None, node_selector={}, role_bindings=None, '\n", + " 'image_pull_secrets=[], init_containers=[], manifests=None, '\n", + " 'env_from_config_maps=None, env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_jupyter_lab_2', groups=['group-c'], \"\n", + " \"definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', \"\n", + " \"description='Jupyter Lab with Python 3.11 private image - demoes the use of \"\n", + " \"an image pull secret', slug='eoepca_jupyter_lab_2', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, '\n", + " \"mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', \"\n", + " 'extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], '\n", + " \"volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', \"\n", + " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", + " \"access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': \"\n", + " \"'/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, \"\n", + " 'default_url=None, node_selector={}, role_bindings=None, '\n", + " \"image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, \"\n", + " \"data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], \"\n", + " 'init_containers=[], manifests=None, env_from_config_maps=None, '\n", + " 'env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_studio_coder_stac', groups=['group-a', 'group-b'], \"\n", + " \"definition=ProfileDefinition(display_name='Understanding STAC for \"\n", + " \"input/output data modelling', description='Understand the role of STAC in \"\n", + " \"input/output data manifests in EO data processing workflows', \"\n", + " \"slug='eoepca_coder_slug_stac', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, '\n", + " \"mem_limit='6G', mem_guarantee='4G', \"\n", + " \"image='docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40', \"\n", + " 'extra_resource_limits={}, extra_resource_guarantees={})), '\n", + " \"config_maps=[ConfigMap(name='init', key='init', \"\n", + " \"mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, \"\n", + " \"content='set -x \\\\n\\\\ncd /workspace\\\\n\\\\ngit clone \"\n", + " \"\\\\'https://github.com/eoap/stac-eoap.git\\\\'\\\\n\\\\ncode-server \"\n", + " '--install-extension ms-python.python \\\\ncode-server --install-extension '\n", + " 'redhat.vscode-yaml\\\\ncode-server --install-extension '\n", + " 'sbg-rabix.benten-cwl\\\\ncode-server --install-extension '\n", + " 'ms-toolsai.jupyter\\\\n\\\\nln -s /workspace/.local/share/code-server/extensions '\n", + " '/workspace/extensions\\\\n\\\\nmkdir -p /workspace/User/\\\\n\\\\necho '\n", + " '\\\\\\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\\\\\' > '\n", + " '/workspace/User/settings.json\\\\n\\\\npython -m venv /workspace/.venv\\\\nsource '\n", + " '/workspace/.venv/bin/activate\\\\n/workspace/.venv/bin/python -m pip install '\n", + " '--no-cache-dir stactools rasterio requests stac-asset click-logging tabulate '\n", + " 'tqdm pystac-client ipykernel loguru scikit-image rio_stac '\n", + " 'boto3==1.35.23\\\\n\\\\n/workspace/.venv/bin/python -m pip install --index-url '\n", + " 'https://test.pypi.org/simple cwl-wrapper\\\\n\\\\n/workspace/.venv/bin/python -m '\n", + " 'ipykernel install --user --name stac_env --display-name \"Python '\n", + " '(STAC)\"\\\\n\\\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\\\nexport '\n", + " 'AWS_ACCESS_KEY_ID=\"test\"\\\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\\\naws s3 mb '\n", + " \"s3://results --endpoint-url=http://localstack:4566\\\\n\\\\nexit 0', \"\n", + " \"persist=False), ConfigMap(name='bash-rc', key='bash-rc', \"\n", + " \"mount_path='/workspace/.bashrc', default_mode=None, readonly=True, \"\n", + " 'content=\\'alias ll=\"ls -l\"\\\\nalias calrissian=\"/opt/conda/bin/calrissian '\n", + " '--pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout '\n", + " '/calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix '\n", + " '/calrissian/tmp/ --outdir /calrissian/\"\\\\nalias '\n", + " 'cwltool=\"/opt/conda/bin/cwltool --podman\"\\\\n. '\n", + " '/home/jovyan/.bashrc\\\\n\\\\n#alias aws=\"aws '\n", + " '--endpoint-url=http://localstack:4566\"\\\\n\\\\n# >>> conda initialize >>>\\\\n# '\n", + " \"!! Contents within this block are managed by \\\\'conda init\\\\' \"\n", + " '!!\\\\n__conda_setup=\"$(\\\\\\'/opt/conda/bin/conda\\\\\\' \\\\\\'shell.bash\\\\\\' '\n", + " '\\\\\\'hook\\\\\\' 2> /dev/null)\"\\\\nif [ $? -eq 0 ]; then\\\\n eval '\n", + " '\"$__conda_setup\"\\\\nelse\\\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; '\n", + " 'then\\\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\\\n else\\\\n '\n", + " 'export PATH=\"/srv/conda/bin:$PATH\"\\\\n fi\\\\nfi\\\\nunset '\n", + " '__conda_setup\\\\n\\\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\\\n '\n", + " '. \"/opt/conda/etc/profile.d/mamba.sh\"\\\\nfi\\\\n# <<< conda initialize '\n", + " '<<<\\\\n\\\\na={{spawner.user.name}}\\\\n\\\\nalias aws=\"aws '\n", + " '--endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"\\', '\n", + " \"persist=False), ConfigMap(name='bash-login', key='bash-login', \"\n", + " \"mount_path='/workspace/.bash_login', default_mode=None, readonly=True, \"\n", + " \"content='source /workspace/.bashrc\\\\n', persist=False)], \"\n", + " \"volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', \"\n", + " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", + " \"access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': \"\n", + " \"'/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': \"\n", + " \"'/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.local', \"\n", + " \"'XDG_DATA_HOME': '/workspace/.local/share/', 'CWLTOOL_OPTIONS': '--podman', \"\n", + " \"'CODE_SERVER_WS': '/workspace/stac-eoap', 'AWS_DEFAULT_REGION': 'us-east-1', \"\n", + " \"'AWS_ACCESS_KEY_ID': 'test', 'AWS_SECRET_ACCESS_KEY': 'test'}, \"\n", + " 'default_url=None, node_selector={}, role_bindings=[], '\n", + " \"image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, \"\n", + " \"data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], \"\n", + " \"init_containers=[InitContainer(name='init-file-on-volume', \"\n", + " \"image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh \"\n", + " \"/opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', \"\n", + " \"mount_path='/workspace'), InitContainerVolumeMount(name='init', \"\n", + " \"mount_path='/opt/init/.init.sh', sub_path='init')])], \"\n", + " \"manifests=[Manifest(name='manifests', key='manifests', \"\n", + " \"content=[{'apiVersion': 'v1', 'kind': 'ServiceAccount', 'metadata': {'name': \"\n", + " \"'localstack'}}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': \"\n", + " \"'Role', 'metadata': {'name': 'localstack'}, 'rules': [{'apiGroups': [''], \"\n", + " \"'resources': ['pods'], 'verbs': ['*']}, {'apiGroups': [''], 'resources': \"\n", + " \"['pods/log'], 'verbs': ['get']}, {'apiGroups': [''], 'resources': \"\n", + " \"['pods/exec'], 'verbs': ['get', 'create']}, {'apiGroups': [''], 'resources': \"\n", + " \"['services'], 'verbs': ['get', 'list']}]}, {'apiVersion': \"\n", + " \"'rbac.authorization.k8s.io/v1', 'kind': 'RoleBinding', 'metadata': {'name': \"\n", + " \"'localstack'}, 'subjects': [{'kind': 'ServiceAccount', 'name': \"\n", + " \"'localstack'}], 'roleRef': {'kind': 'Role', 'name': 'localstack', \"\n", + " \"'apiGroup': 'rbac.authorization.k8s.io'}}, {'apiVersion': 'v1', 'kind': \"\n", + " \"'Service', 'metadata': {'name': 'localstack'}, 'spec': {'type': 'ClusterIP', \"\n", + " \"'ports': [{'name': 'edge', 'port': 4566, 'targetPort': 4566}, {'name': \"\n", + " \"'external-service-port-4510', 'port': 4510, 'targetPort': 'ext-svc-4510'}, \"\n", + " \"{'name': 'external-service-port-4511', 'port': 4511, 'targetPort': \"\n", + " \"'ext-svc-4511'}, {'name': 'external-service-port-4512', 'port': 4512, \"\n", + " \"'targetPort': 'ext-svc-4512'}, {'name': 'external-service-port-4513', \"\n", + " \"'port': 4513, 'targetPort': 'ext-svc-4513'}, {'name': \"\n", + " \"'external-service-port-4514', 'port': 4514, 'targetPort': 'ext-svc-4514'}, \"\n", + " \"{'name': 'external-service-port-4515', 'port': 4515, 'targetPort': \"\n", + " \"'ext-svc-4515'}, {'name': 'external-service-port-4516', 'port': 4516, \"\n", + " \"'targetPort': 'ext-svc-4516'}, {'name': 'external-service-port-4517', \"\n", + " \"'port': 4517, 'targetPort': 'ext-svc-4517'}, {'name': \"\n", + " \"'external-service-port-4518', 'port': 4518, 'targetPort': 'ext-svc-4518'}, \"\n", + " \"{'name': 'external-service-port-4519', 'port': 4519, 'targetPort': \"\n", + " \"'ext-svc-4519'}, {'name': 'external-service-port-4520', 'port': 4520, \"\n", + " \"'targetPort': 'ext-svc-4520'}, {'name': 'external-service-port-4521', \"\n", + " \"'port': 4521, 'targetPort': 'ext-svc-4521'}, {'name': \"\n", + " \"'external-service-port-4522', 'port': 4522, 'targetPort': 'ext-svc-4522'}, \"\n", + " \"{'name': 'external-service-port-4523', 'port': 4523, 'targetPort': \"\n", + " \"'ext-svc-4523'}, {'name': 'external-service-port-4524', 'port': 4524, \"\n", + " \"'targetPort': 'ext-svc-4524'}, {'name': 'external-service-port-4525', \"\n", + " \"'port': 4525, 'targetPort': 'ext-svc-4525'}, {'name': \"\n", + " \"'external-service-port-4526', 'port': 4526, 'targetPort': 'ext-svc-4526'}, \"\n", + " \"{'name': 'external-service-port-4527', 'port': 4527, 'targetPort': \"\n", + " \"'ext-svc-4527'}, {'name': 'external-service-port-4528', 'port': 4528, \"\n", + " \"'targetPort': 'ext-svc-4528'}, {'name': 'external-service-port-4529', \"\n", + " \"'port': 4529, 'targetPort': 'ext-svc-4529'}, {'name': \"\n", + " \"'external-service-port-4530', 'port': 4530, 'targetPort': 'ext-svc-4530'}, \"\n", + " \"{'name': 'external-service-port-4531', 'port': 4531, 'targetPort': \"\n", + " \"'ext-svc-4531'}, {'name': 'external-service-port-4532', 'port': 4532, \"\n", + " \"'targetPort': 'ext-svc-4532'}, {'name': 'external-service-port-4533', \"\n", + " \"'port': 4533, 'targetPort': 'ext-svc-4533'}, {'name': \"\n", + " \"'external-service-port-4534', 'port': 4534, 'targetPort': 'ext-svc-4534'}, \"\n", + " \"{'name': 'external-service-port-4535', 'port': 4535, 'targetPort': \"\n", + " \"'ext-svc-4535'}, {'name': 'external-service-port-4536', 'port': 4536, \"\n", + " \"'targetPort': 'ext-svc-4536'}, {'name': 'external-service-port-4537', \"\n", + " \"'port': 4537, 'targetPort': 'ext-svc-4537'}, {'name': \"\n", + " \"'external-service-port-4538', 'port': 4538, 'targetPort': 'ext-svc-4538'}, \"\n", + " \"{'name': 'external-service-port-4539', 'port': 4539, 'targetPort': \"\n", + " \"'ext-svc-4539'}, {'name': 'external-service-port-4540', 'port': 4540, \"\n", + " \"'targetPort': 'ext-svc-4540'}, {'name': 'external-service-port-4541', \"\n", + " \"'port': 4541, 'targetPort': 'ext-svc-4541'}, {'name': \"\n", + " \"'external-service-port-4542', 'port': 4542, 'targetPort': 'ext-svc-4542'}, \"\n", + " \"{'name': 'external-service-port-4543', 'port': 4543, 'targetPort': \"\n", + " \"'ext-svc-4543'}, {'name': 'external-service-port-4544', 'port': 4544, \"\n", + " \"'targetPort': 'ext-svc-4544'}, {'name': 'external-service-port-4545', \"\n", + " \"'port': 4545, 'targetPort': 'ext-svc-4545'}, {'name': \"\n", + " \"'external-service-port-4546', 'port': 4546, 'targetPort': 'ext-svc-4546'}, \"\n", + " \"{'name': 'external-service-port-4547', 'port': 4547, 'targetPort': \"\n", + " \"'ext-svc-4547'}, {'name': 'external-service-port-4548', 'port': 4548, \"\n", + " \"'targetPort': 'ext-svc-4548'}, {'name': 'external-service-port-4549', \"\n", + " \"'port': 4549, 'targetPort': 'ext-svc-4549'}, {'name': \"\n", + " \"'external-service-port-4550', 'port': 4550, 'targetPort': 'ext-svc-4550'}, \"\n", + " \"{'name': 'external-service-port-4551', 'port': 4551, 'targetPort': \"\n", + " \"'ext-svc-4551'}, {'name': 'external-service-port-4552', 'port': 4552, \"\n", + " \"'targetPort': 'ext-svc-4552'}, {'name': 'external-service-port-4553', \"\n", + " \"'port': 4553, 'targetPort': 'ext-svc-4553'}, {'name': \"\n", + " \"'external-service-port-4554', 'port': 4554, 'targetPort': 'ext-svc-4554'}, \"\n", + " \"{'name': 'external-service-port-4555', 'port': 4555, 'targetPort': \"\n", + " \"'ext-svc-4555'}, {'name': 'external-service-port-4556', 'port': 4556, \"\n", + " \"'targetPort': 'ext-svc-4556'}, {'name': 'external-service-port-4557', \"\n", + " \"'port': 4557, 'targetPort': 'ext-svc-4557'}, {'name': \"\n", + " \"'external-service-port-4558', 'port': 4558, 'targetPort': 'ext-svc-4558'}, \"\n", + " \"{'name': 'external-service-port-4559', 'port': 4559, 'targetPort': \"\n", + " \"'ext-svc-4559'}], 'selector': {'app.kubernetes.io/name': 'localstack', \"\n", + " \"'app.kubernetes.io/instance': 'localstack'}}}, {'apiVersion': 'apps/v1', \"\n", + " \"'kind': 'Deployment', 'metadata': {'name': 'localstack'}, 'spec': \"\n", + " \"{'replicas': 1, 'strategy': {'type': 'RollingUpdate'}, 'selector': \"\n", + " \"{'matchLabels': {'app.kubernetes.io/name': 'localstack', \"\n", + " \"'app.kubernetes.io/instance': 'localstack'}}, 'template': {'metadata': \"\n", + " \"{'labels': {'app': 'localstack-{{ spawner.user.name }}', \"\n", + " \"'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': \"\n", + " \"'localstack'}}, 'spec': {'serviceAccountName': 'localstack', \"\n", + " \"'securityContext': {}, 'containers': [{'name': 'localstack', \"\n", + " \"'securityContext': {}, 'image': 'localstack/localstack:latest', \"\n", + " \"'imagePullPolicy': 'IfNotPresent', 'ports': [{'name': 'edge', \"\n", + " \"'containerPort': 4566, 'protocol': 'TCP'}, {'name': 'ext-svc-4510', \"\n", + " \"'containerPort': 4510, 'protocol': 'TCP'}, {'name': 'ext-svc-4511', \"\n", + " \"'containerPort': 4511, 'protocol': 'TCP'}, {'name': 'ext-svc-4512', \"\n", + " \"'containerPort': 4512, 'protocol': 'TCP'}, {'name': 'ext-svc-4513', \"\n", + " \"'containerPort': 4513, 'protocol': 'TCP'}, {'name': 'ext-svc-4514', \"\n", + " \"'containerPort': 4514, 'protocol': 'TCP'}, {'name': 'ext-svc-4515', \"\n", + " \"'containerPort': 4515, 'protocol': 'TCP'}, {'name': 'ext-svc-4516', \"\n", + " \"'containerPort': 4516, 'protocol': 'TCP'}, {'name': 'ext-svc-4517', \"\n", + " \"'containerPort': 4517, 'protocol': 'TCP'}, {'name': 'ext-svc-4518', \"\n", + " \"'containerPort': 4518, 'protocol': 'TCP'}, {'name': 'ext-svc-4519', \"\n", + " \"'containerPort': 4519, 'protocol': 'TCP'}, {'name': 'ext-svc-4520', \"\n", + " \"'containerPort': 4520, 'protocol': 'TCP'}, {'name': 'ext-svc-4521', \"\n", + " \"'containerPort': 4521, 'protocol': 'TCP'}, {'name': 'ext-svc-4522', \"\n", + " \"'containerPort': 4522, 'protocol': 'TCP'}, {'name': 'ext-svc-4523', \"\n", + " \"'containerPort': 4523, 'protocol': 'TCP'}, {'name': 'ext-svc-4524', \"\n", + " \"'containerPort': 4524, 'protocol': 'TCP'}, {'name': 'ext-svc-4525', \"\n", + " \"'containerPort': 4525, 'protocol': 'TCP'}, {'name': 'ext-svc-4526', \"\n", + " \"'containerPort': 4526, 'protocol': 'TCP'}, {'name': 'ext-svc-4527', \"\n", + " \"'containerPort': 4527, 'protocol': 'TCP'}, {'name': 'ext-svc-4528', \"\n", + " \"'containerPort': 4528, 'protocol': 'TCP'}, {'name': 'ext-svc-4529', \"\n", + " \"'containerPort': 4529, 'protocol': 'TCP'}, {'name': 'ext-svc-4530', \"\n", + " \"'containerPort': 4530, 'protocol': 'TCP'}, {'name': 'ext-svc-4531', \"\n", + " \"'containerPort': 4531, 'protocol': 'TCP'}, {'name': 'ext-svc-4532', \"\n", + " \"'containerPort': 4532, 'protocol': 'TCP'}, {'name': 'ext-svc-4533', \"\n", + " \"'containerPort': 4533, 'protocol': 'TCP'}, {'name': 'ext-svc-4534', \"\n", + " \"'containerPort': 4534, 'protocol': 'TCP'}, {'name': 'ext-svc-4535', \"\n", + " \"'containerPort': 4535, 'protocol': 'TCP'}, {'name': 'ext-svc-4536', \"\n", + " \"'containerPort': 4536, 'protocol': 'TCP'}, {'name': 'ext-svc-4537', \"\n", + " \"'containerPort': 4537, 'protocol': 'TCP'}, {'name': 'ext-svc-4538', \"\n", + " \"'containerPort': 4538, 'protocol': 'TCP'}, {'name': 'ext-svc-4539', \"\n", + " \"'containerPort': 4539, 'protocol': 'TCP'}, {'name': 'ext-svc-4540', \"\n", + " \"'containerPort': 4540, 'protocol': 'TCP'}, {'name': 'ext-svc-4541', \"\n", + " \"'containerPort': 4541, 'protocol': 'TCP'}, {'name': 'ext-svc-4542', \"\n", + " \"'containerPort': 4542, 'protocol': 'TCP'}, {'name': 'ext-svc-4543', \"\n", + " \"'containerPort': 4543, 'protocol': 'TCP'}, {'name': 'ext-svc-4544', \"\n", + " \"'containerPort': 4544, 'protocol': 'TCP'}, {'name': 'ext-svc-4545', \"\n", + " \"'containerPort': 4545, 'protocol': 'TCP'}, {'name': 'ext-svc-4546', \"\n", + " \"'containerPort': 4546, 'protocol': 'TCP'}, {'name': 'ext-svc-4547', \"\n", + " \"'containerPort': 4547, 'protocol': 'TCP'}, {'name': 'ext-svc-4548', \"\n", + " \"'containerPort': 4548, 'protocol': 'TCP'}, {'name': 'ext-svc-4549', \"\n", + " \"'containerPort': 4549, 'protocol': 'TCP'}, {'name': 'ext-svc-4550', \"\n", + " \"'containerPort': 4550, 'protocol': 'TCP'}, {'name': 'ext-svc-4551', \"\n", + " \"'containerPort': 4551, 'protocol': 'TCP'}, {'name': 'ext-svc-4552', \"\n", + " \"'containerPort': 4552, 'protocol': 'TCP'}, {'name': 'ext-svc-4553', \"\n", + " \"'containerPort': 4553, 'protocol': 'TCP'}, {'name': 'ext-svc-4554', \"\n", + " \"'containerPort': 4554, 'protocol': 'TCP'}, {'name': 'ext-svc-4555', \"\n", + " \"'containerPort': 4555, 'protocol': 'TCP'}, {'name': 'ext-svc-4556', \"\n", + " \"'containerPort': 4556, 'protocol': 'TCP'}, {'name': 'ext-svc-4557', \"\n", + " \"'containerPort': 4557, 'protocol': 'TCP'}, {'name': 'ext-svc-4558', \"\n", + " \"'containerPort': 4558, 'protocol': 'TCP'}, {'name': 'ext-svc-4559', \"\n", + " \"'containerPort': 4559, 'protocol': 'TCP'}], 'livenessProbe': \"\n", + " \"{'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, \"\n", + " \"'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': \"\n", + " \"'/_localstack/health', 'port': 'edge'}}, 'readinessProbe': \"\n", + " \"{'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, \"\n", + " \"'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': \"\n", + " \"'/_localstack/health', 'port': 'edge'}}, 'resources': {}, 'env': [{'name': \"\n", + " \"'DEBUG', 'value': '0'}, {'name': 'EXTERNAL_SERVICE_PORTS_START', 'value': \"\n", + " \"'4510'}, {'name': 'EXTERNAL_SERVICE_PORTS_END', 'value': '4560'}, {'name': \"\n", + " \"'LOCALSTACK_K8S_SERVICE_NAME', 'value': 'localstack'}, {'name': \"\n", + " \"'LOCALSTACK_K8S_NAMESPACE', 'valueFrom': {'fieldRef': {'fieldPath': \"\n", + " \"'metadata.namespace'}}}, {'name': 'LAMBDA_RUNTIME_EXECUTOR', 'value': \"\n", + " \"'docker'}, {'name': 'LAMBDA_K8S_IMAGE_PREFIX', 'value': \"\n", + " \"'localstack/lambda-'}, {'name': 'LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT', \"\n", + " \"'value': '60'}, {'name': 'OVERRIDE_IN_DOCKER', 'value': '1'}]}], 'volumes': \"\n", + " \"[]}}}}, {'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': \"\n", + " \"'my-config'}, 'data': {'ENV_VAR1': 'value1', 'ENV_VAR2': 'value2'}}, \"\n", + " \"{'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'my-secret'}, \"\n", + " \"'type': 'Opaque', 'data': {'SECRET_KEY1': 'dmFsdWUx', 'SECRET_KEY2': \"\n", + " \"'dmFsdWUy'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': \"\n", + " \"'aws-credentials-{{ spawner.user.name }}'}, 'type': 'Opaque', 'data': \"\n", + " \"{'credentials': \"\n", + " \"'W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg='}}, \"\n", + " \"{'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', \"\n", + " \"'metadata': {'name': 'data-by-name', 'namespace': 'jupyter-{{ \"\n", + " \"spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': \"\n", + " \"{'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': \"\n", + " \"{'name': 'data-by-name', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': \"\n", + " \"'secret-value', 'remoteRef': {'key': 'secret-one', 'property': \"\n", + " \"'the-key'}}]}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': \"\n", + " \"'ExternalSecret', 'metadata': {'name': 'eoepca-plus-secret-ro', 'namespace': \"\n", + " \"'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', \"\n", + " \"'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': \"\n", + " \"'k8s-secret-store'}, 'target': {'name': 'eoepca-plus-secret-ro', \"\n", + " \"'creationPolicy': 'Owner'}, 'data': [{'secretKey': '.dockerconfigjson', \"\n", + " \"'remoteRef': {'key': 'eoepca-plus-secret-ro', 'property': \"\n", + " \"'.dockerconfigjson'}}]}}], persist=False)], env_from_config_maps=None, \"\n", + " 'env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_studio_desktop_qgis', groups=['group-a', 'group-b'], \"\n", + " \"definition=ProfileDefinition(display_name='QGIS on a Remote Desktop', \"\n", + " \"description='Spatial visualization and decision-making tools for everyone', \"\n", + " \"slug='eoepca_desktop_qgis', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, '\n", + " \"mem_limit='2G', mem_guarantee=None, \"\n", + " \"image='eoepca/iga-remote-desktop-qgis:1.1.3', extra_resource_limits={}, \"\n", + " \"extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', \"\n", + " \"key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, \"\n", + " 'readonly=True, content=\\'alias ll=\"ls -l\"\\\\nalias '\n", + " 'calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors '\n", + " '/etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json '\n", + " '--max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir '\n", + " '/calrissian/\"\\\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\\\n. '\n", + " '/home/jovyan/.bashrc\\\\n\\\\n#alias aws=\"aws '\n", + " '--endpoint-url=http://localstack:4566\"\\\\n\\\\n# >>> conda initialize >>>\\\\n# '\n", + " \"!! Contents within this block are managed by \\\\'conda init\\\\' \"\n", + " '!!\\\\n__conda_setup=\"$(\\\\\\'/opt/conda/bin/conda\\\\\\' \\\\\\'shell.bash\\\\\\' '\n", + " '\\\\\\'hook\\\\\\' 2> /dev/null)\"\\\\nif [ $? -eq 0 ]; then\\\\n eval '\n", + " '\"$__conda_setup\"\\\\nelse\\\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; '\n", + " 'then\\\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\\\n else\\\\n '\n", + " 'export PATH=\"/srv/conda/bin:$PATH\"\\\\n fi\\\\nfi\\\\nunset '\n", + " '__conda_setup\\\\n\\\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\\\n '\n", + " '. \"/opt/conda/etc/profile.d/mamba.sh\"\\\\nfi\\\\n# <<< conda initialize '\n", + " '<<<\\\\n\\\\na={{spawner.user.name}}\\\\n\\\\nalias aws=\"aws '\n", + " '--endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"\\', '\n", + " \"persist=False), ConfigMap(name='bash-login', key='bash-login', \"\n", + " \"mount_path='/workspace/.bash_login', default_mode=None, readonly=True, \"\n", + " \"content='source /workspace/.bashrc\\\\n', persist=False)], \"\n", + " \"volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', \"\n", + " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", + " \"access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace'}, default_url='desktop', \"\n", + " 'node_selector={}, role_bindings=None, image_pull_secrets=[], '\n", + " 'init_containers=[], manifests=None, env_from_config_maps=None, '\n", + " 'env_from_secrets=None, secret_mounts=None)]')\n" + ] + } + ], + "source": [ + "pprint(profiles)\n", + "config = Config(profiles=profiles) # type: ignore\n", + "pprint(f'config: {config}')\n", + "eoepca_demo_config_path = str(Path(current_dir).parent.parent / 'eoepca-demo' / 'config.yml')\n", + "with open(eoepca_demo_config_path, \"w\") as file:\n", + " yaml.dump(config.model_dump(), file)" + ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "apphub_configurator", "language": "python", "name": "python3" }, @@ -608,7 +1330,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.10.12" }, "orig_nbformat": 4 }, From aa9d2396bfd3dac9b66ba137a5d13a933ad8c924 Mon Sep 17 00:00:00 2001 From: pmembari Date: Thu, 3 Apr 2025 12:18:52 +0200 Subject: [PATCH 39/86] docs: docs for finding kernel --- config-generator/README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/config-generator/README.md b/config-generator/README.md index be33f38..6ee2109 100644 --- a/config-generator/README.md +++ b/config-generator/README.md @@ -1,10 +1,5 @@ ## ApplicationHub configuration generator - - - - - - +This application aim to produce a `config.yml` file to generate all the configuration needs to be set for deployment of App Hub on a remote cluster using Devops tools. # apphub-configurator [![PyPI - Version](https://img.shields.io/pypi/v/apphub-configurator.svg)](https://pypi.org/project/apphub-configurator) @@ -26,11 +21,13 @@ Create a hatch environment with the dependencies listed in the file `pyproject.t hatch shell prod hatch -e prod run python -m ipykernel install --user --name=apphub_configurator --display-name "apphub_configurator" ``` +> Note: you may need to reopen visual studio code to set the jupyter kernel successfully. ## Overview This package contains a notebook and the python modules to support the generation of ApplicationHub configurations for a minikube cluster. For more information about ApplicationHub please check this [link](https://github.com/EOEPCA/application-hub-context) ## Examples: The [example](./examples/) folder contains a notebook and the python modules to support the generation of ApplicationHub configurations. + ## License `apphub-configurator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. From 4ef83b54ae93500290be5cc25c4cefc6db59799d Mon Sep 17 00:00:00 2001 From: pmembari Date: Thu, 3 Apr 2025 12:36:51 +0200 Subject: [PATCH 40/86] feat: add logging --- config-generator/examples/generate_config.py | 6 ++++++ config-generator/pyproject.toml | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/config-generator/examples/generate_config.py b/config-generator/examples/generate_config.py index 7fecb8b..a09a990 100644 --- a/config-generator/examples/generate_config.py +++ b/config-generator/examples/generate_config.py @@ -11,6 +11,8 @@ ) import click +logger.info("Generating config file...") + storage_class_rwo = "standard" storage_class_rwx = "standard" profiles = [] @@ -160,3 +162,7 @@ with open(config_file_path, "w") as file: yaml.dump(config.model_dump(), file, width=200) + +logger.success( + f"Config file generated successfully at {config_file_path}" +) \ No newline at end of file diff --git a/config-generator/pyproject.toml b/config-generator/pyproject.toml index fa221fd..e3a3cb1 100644 --- a/config-generator/pyproject.toml +++ b/config-generator/pyproject.toml @@ -49,7 +49,10 @@ dependencies = ["pytest", "pydantic", "pyyaml", "ipykernel", - "jupyter"] + "jupyter", + "loguru", + "click", + ] [[tool.hatch.envs.test.matrix]] python = ["3.10", "3.11", "3.12"] From 3a165fdd0c7d6f5486d0fa571d0f8ad0d666c42e Mon Sep 17 00:00:00 2001 From: pmembari Date: Thu, 3 Apr 2025 17:01:03 +0200 Subject: [PATCH 41/86] feat: algining for pypi push --- .dockerignore | 2 +- .github/workflows/package.yaml | 2 +- .gitignore | 2 +- .../LICENSE.txt | 0 .../README.md | 0 .../config-maps/bash-login | 0 .../config-maps/bash-rc | 0 .../config-maps/conda-rc.yml | 0 .../config-maps/init-qgis.sh | 0 .../config-maps/init-stac.sh | 0 .../config-maps/init.sh | 0 .../examples/config-generator.ipynb | 2 +- .../examples/generate-config.sh | 4 + .../examples/generate_config.py | 0 .../manifests/dask-gateway.yaml | 0 .../manifests/kaniko.yaml | 0 .../manifests/manifest.yaml | 0 .../pyproject.toml | 6 +- .../src/apphub_configurator/__about__.py | 0 .../src/apphub_configurator/__init__.py | 0 .../src/apphub_configurator/helpers.py | 0 .../src/apphub_configurator/models.py | 0 .../tests/__init__.py | 0 config-generator/examples/generate-config.sh | 4 - eoepca-demo/config.yml | 150 ++++++++++++------ skaffold.yaml | 2 +- 26 files changed, 115 insertions(+), 59 deletions(-) rename {config-generator => apphub-configurator}/LICENSE.txt (100%) rename {config-generator => apphub-configurator}/README.md (100%) rename {config-generator => apphub-configurator}/config-maps/bash-login (100%) rename {config-generator => apphub-configurator}/config-maps/bash-rc (100%) rename {config-generator => apphub-configurator}/config-maps/conda-rc.yml (100%) rename {config-generator => apphub-configurator}/config-maps/init-qgis.sh (100%) rename {config-generator => apphub-configurator}/config-maps/init-stac.sh (100%) rename {config-generator => apphub-configurator}/config-maps/init.sh (100%) rename {config-generator => apphub-configurator}/examples/config-generator.ipynb (99%) create mode 100755 apphub-configurator/examples/generate-config.sh rename {config-generator => apphub-configurator}/examples/generate_config.py (100%) rename {config-generator => apphub-configurator}/manifests/dask-gateway.yaml (100%) rename {config-generator => apphub-configurator}/manifests/kaniko.yaml (100%) rename {config-generator => apphub-configurator}/manifests/manifest.yaml (100%) rename {config-generator => apphub-configurator}/pyproject.toml (88%) rename {config-generator => apphub-configurator}/src/apphub_configurator/__about__.py (100%) rename {config-generator => apphub-configurator}/src/apphub_configurator/__init__.py (100%) rename {config-generator => apphub-configurator}/src/apphub_configurator/helpers.py (100%) rename {config-generator => apphub-configurator}/src/apphub_configurator/models.py (100%) rename {config-generator => apphub-configurator}/tests/__init__.py (100%) delete mode 100755 config-generator/examples/generate-config.sh diff --git a/.dockerignore b/.dockerignore index a5d8001..d19c40d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,6 @@ jupyterhub values.yaml config.yml -config-generator +apphub-configurator skaffold.yaml files/* \ No newline at end of file diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index cab9bbe..afe5619 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -30,7 +30,7 @@ jobs: # python -m pip install --upgrade pip pip install hatch - name: Build package - run: hatch build config-generator/apphub-configurator/ + run: hatch build apphub-configurator/apphub-configurator/ - name: Publish package distributions to PyPI (main) if: github.ref == 'refs/heads/main' diff --git a/.gitignore b/.gitignore index 6994ab4..f2a524e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,5 @@ values.yaml build _README.md dist -.env-config-generator +.env-apphub-configurator hatch \ No newline at end of file diff --git a/config-generator/LICENSE.txt b/apphub-configurator/LICENSE.txt similarity index 100% rename from config-generator/LICENSE.txt rename to apphub-configurator/LICENSE.txt diff --git a/config-generator/README.md b/apphub-configurator/README.md similarity index 100% rename from config-generator/README.md rename to apphub-configurator/README.md diff --git a/config-generator/config-maps/bash-login b/apphub-configurator/config-maps/bash-login similarity index 100% rename from config-generator/config-maps/bash-login rename to apphub-configurator/config-maps/bash-login diff --git a/config-generator/config-maps/bash-rc b/apphub-configurator/config-maps/bash-rc similarity index 100% rename from config-generator/config-maps/bash-rc rename to apphub-configurator/config-maps/bash-rc diff --git a/config-generator/config-maps/conda-rc.yml b/apphub-configurator/config-maps/conda-rc.yml similarity index 100% rename from config-generator/config-maps/conda-rc.yml rename to apphub-configurator/config-maps/conda-rc.yml diff --git a/config-generator/config-maps/init-qgis.sh b/apphub-configurator/config-maps/init-qgis.sh similarity index 100% rename from config-generator/config-maps/init-qgis.sh rename to apphub-configurator/config-maps/init-qgis.sh diff --git a/config-generator/config-maps/init-stac.sh b/apphub-configurator/config-maps/init-stac.sh similarity index 100% rename from config-generator/config-maps/init-stac.sh rename to apphub-configurator/config-maps/init-stac.sh diff --git a/config-generator/config-maps/init.sh b/apphub-configurator/config-maps/init.sh similarity index 100% rename from config-generator/config-maps/init.sh rename to apphub-configurator/config-maps/init.sh diff --git a/config-generator/examples/config-generator.ipynb b/apphub-configurator/examples/config-generator.ipynb similarity index 99% rename from config-generator/examples/config-generator.ipynb rename to apphub-configurator/examples/config-generator.ipynb index c3f7c4e..8c63109 100644 --- a/config-generator/examples/config-generator.ipynb +++ b/apphub-configurator/examples/config-generator.ipynb @@ -51,7 +51,7 @@ { "data": { "text/plain": [ - "PosixPath('/home/t2/Desktop/p/EOEPCA/application-hub-context/config-generator/examples')" + "PosixPath('/home/t2/Desktop/p/EOEPCA/application-hub-context/apphub-configurator/examples')" ] }, "execution_count": 1, diff --git a/apphub-configurator/examples/generate-config.sh b/apphub-configurator/examples/generate-config.sh new file mode 100755 index 0000000..0238b9d --- /dev/null +++ b/apphub-configurator/examples/generate-config.sh @@ -0,0 +1,4 @@ +#exit 0 +export PYTHONPATH=$PWD/`dirname $0`/. + +.env-apphub-configurator/bin/python3 -m generate_config \ No newline at end of file diff --git a/config-generator/examples/generate_config.py b/apphub-configurator/examples/generate_config.py similarity index 100% rename from config-generator/examples/generate_config.py rename to apphub-configurator/examples/generate_config.py diff --git a/config-generator/manifests/dask-gateway.yaml b/apphub-configurator/manifests/dask-gateway.yaml similarity index 100% rename from config-generator/manifests/dask-gateway.yaml rename to apphub-configurator/manifests/dask-gateway.yaml diff --git a/config-generator/manifests/kaniko.yaml b/apphub-configurator/manifests/kaniko.yaml similarity index 100% rename from config-generator/manifests/kaniko.yaml rename to apphub-configurator/manifests/kaniko.yaml diff --git a/config-generator/manifests/manifest.yaml b/apphub-configurator/manifests/manifest.yaml similarity index 100% rename from config-generator/manifests/manifest.yaml rename to apphub-configurator/manifests/manifest.yaml diff --git a/config-generator/pyproject.toml b/apphub-configurator/pyproject.toml similarity index 88% rename from config-generator/pyproject.toml rename to apphub-configurator/pyproject.toml index e3a3cb1..4964470 100644 --- a/config-generator/pyproject.toml +++ b/apphub-configurator/pyproject.toml @@ -24,10 +24,10 @@ classifiers = [ dependencies = [] [project.urls] -Documentation = "https://github.com/EOEPCA/application-hub-context/config-generator/apphub-configurator#readme" +Documentation = "https://github.com/EOEPCA/application-hub-context/apphub-configurator/apphub-configurator#readme" Issues = "https://github.com/EOEPCA/application-hub-context/issues" -Source = "https://github.com/EOEPCA/application-hub-context/config-generator/apphub-configurator" -Examples = "https://github.com/EOEPCA/application-hub-context/config-generator/examples" +Source = "https://github.com/EOEPCA/application-hub-context/apphub-configurator/apphub-configurator" +Examples = "https://github.com/EOEPCA/application-hub-context/apphub-configurator/examples" [tool.hatch.version] path = "src/apphub_configurator/__about__.py" diff --git a/config-generator/src/apphub_configurator/__about__.py b/apphub-configurator/src/apphub_configurator/__about__.py similarity index 100% rename from config-generator/src/apphub_configurator/__about__.py rename to apphub-configurator/src/apphub_configurator/__about__.py diff --git a/config-generator/src/apphub_configurator/__init__.py b/apphub-configurator/src/apphub_configurator/__init__.py similarity index 100% rename from config-generator/src/apphub_configurator/__init__.py rename to apphub-configurator/src/apphub_configurator/__init__.py diff --git a/config-generator/src/apphub_configurator/helpers.py b/apphub-configurator/src/apphub_configurator/helpers.py similarity index 100% rename from config-generator/src/apphub_configurator/helpers.py rename to apphub-configurator/src/apphub_configurator/helpers.py diff --git a/config-generator/src/apphub_configurator/models.py b/apphub-configurator/src/apphub_configurator/models.py similarity index 100% rename from config-generator/src/apphub_configurator/models.py rename to apphub-configurator/src/apphub_configurator/models.py diff --git a/config-generator/tests/__init__.py b/apphub-configurator/tests/__init__.py similarity index 100% rename from config-generator/tests/__init__.py rename to apphub-configurator/tests/__init__.py diff --git a/config-generator/examples/generate-config.sh b/config-generator/examples/generate-config.sh deleted file mode 100755 index 279e056..0000000 --- a/config-generator/examples/generate-config.sh +++ /dev/null @@ -1,4 +0,0 @@ -#exit 0 -export PYTHONPATH=$PWD/`dirname $0`/. - -.env-config-generator/bin/python3 -m generate_config \ No newline at end of file diff --git a/eoepca-demo/config.yml b/eoepca-demo/config.yml index 61b05c9..f944149 100644 --- a/eoepca-demo/config.yml +++ b/eoepca-demo/config.yml @@ -4,14 +4,15 @@ profiles: \ /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram\ \ 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\ \nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\n. /home/jovyan/.bashrc\n\ - \nalias aws=\"aws --endpoint-url=http://localstack:4566\"\n\n# >>> conda initialize\ + \n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\n\n# >>> conda initialize\ \ >>>\n# !! Contents within this block are managed by 'conda init' !!\n__conda_setup=\"\ $('/opt/conda/bin/conda' 'shell.bash' 'hook' 2> /dev/null)\"\nif [ $? -eq 0\ \ ]; then\n eval \"$__conda_setup\"\nelse\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\"\ \ ]; then\n . \"/opt/conda/etc/profile.d/conda.sh\"\n else\n \ \ export PATH=\"/srv/conda/bin:$PATH\"\n fi\nfi\nunset __conda_setup\n\n\ if [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\n . \"/opt/conda/etc/profile.d/mamba.sh\"\ - \nfi\n# <<< conda initialize <<<\n" + \nfi\n# <<< conda initialize <<<\n\na={{spawner.user.name}}\n\nalias aws=\"\ + aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"" default_mode: null key: bash-rc mount_path: /workspace/.bashrc @@ -32,6 +33,8 @@ profiles: mem_guarantee: null mem_limit: 8G slug: ellip_studio_coder_slug_s + env_from_config_maps: null + env_from_secrets: null groups: - group-a - group-b @@ -44,6 +47,7 @@ profiles: CONDA_ENVS_PATH: /workspace/.envs HOME: /workspace role_bindings: null + secret_mounts: null volumes: - access_modes: - ReadWriteMany @@ -59,7 +63,7 @@ profiles: - ReadWriteOnce claim_name: workspace-claim name: workspace-volume - persist: false + persist: true size: 50Gi storage_class: managed-nfs-storage volume_mount: @@ -70,14 +74,15 @@ profiles: \ /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram\ \ 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\ \nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\n. /home/jovyan/.bashrc\n\ - \nalias aws=\"aws --endpoint-url=http://localstack:4566\"\n\n# >>> conda initialize\ + \n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\n\n# >>> conda initialize\ \ >>>\n# !! Contents within this block are managed by 'conda init' !!\n__conda_setup=\"\ $('/opt/conda/bin/conda' 'shell.bash' 'hook' 2> /dev/null)\"\nif [ $? -eq 0\ \ ]; then\n eval \"$__conda_setup\"\nelse\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\"\ \ ]; then\n . \"/opt/conda/etc/profile.d/conda.sh\"\n else\n \ \ export PATH=\"/srv/conda/bin:$PATH\"\n fi\nfi\nunset __conda_setup\n\n\ if [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\n . \"/opt/conda/etc/profile.d/mamba.sh\"\ - \nfi\n# <<< conda initialize <<<\n" + \nfi\n# <<< conda initialize <<<\n\na={{spawner.user.name}}\n\nalias aws=\"\ + aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"" default_mode: null key: bash-rc mount_path: /workspace/.bashrc @@ -98,6 +103,8 @@ profiles: mem_guarantee: null mem_limit: 12G slug: ellip_studio_coder_slug_m + env_from_config_maps: null + env_from_secrets: null groups: - group-a - group-b @@ -110,6 +117,7 @@ profiles: CONDA_ENVS_PATH: /workspace/.envs HOME: /workspace role_bindings: null + secret_mounts: null volumes: - access_modes: - ReadWriteMany @@ -125,7 +133,7 @@ profiles: - ReadWriteOnce claim_name: workspace-claim name: workspace-volume - persist: false + persist: true size: 50Gi storage_class: managed-nfs-storage volume_mount: @@ -176,6 +184,8 @@ profiles: mem_guarantee: 4G mem_limit: 6G slug: eoepca_demo_init_script + env_from_config_maps: null + env_from_secrets: null groups: - group-a - group-b @@ -203,6 +213,7 @@ profiles: HOME: /workspace XDG_RUNTIME_DIR: /workspace/.local role_bindings: null + secret_mounts: null volumes: - access_modes: - ReadWriteMany @@ -218,7 +229,7 @@ profiles: - ReadWriteOnce claim_name: workspace-claim name: workspace-volume - persist: false + persist: true size: 50Gi storage_class: managed-nfs-storage volume_mount: @@ -239,6 +250,8 @@ profiles: mem_guarantee: 4G mem_limit: 6G slug: eoepca_jupyter_lab + env_from_config_maps: null + env_from_secrets: null groups: - group-c id: profile_jupyter_lab @@ -251,12 +264,13 @@ profiles: XDG_CONFIG_HOME: /workspace/.config XDG_RUNTIME_DIR: /workspace/.local role_bindings: null + secret_mounts: null volumes: - access_modes: - ReadWriteOnce claim_name: workspace-claim name: workspace-volume - persist: false + persist: true size: 50Gi storage_class: managed-nfs-storage volume_mount: @@ -278,12 +292,13 @@ profiles: mem_guarantee: 4G mem_limit: 6G slug: eoepca_jupyter_lab_2 + env_from_config_maps: null + env_from_secrets: null groups: - group-c id: profile_jupyter_lab_2 image_pull_secrets: - - data: null - - data: '' + - data: ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9 name: cr-config persist: false init_containers: [] @@ -294,12 +309,13 @@ profiles: XDG_CONFIG_HOME: /workspace/.config XDG_RUNTIME_DIR: /workspace/.local role_bindings: null + secret_mounts: null volumes: - access_modes: - ReadWriteOnce claim_name: workspace-claim name: workspace-volume - persist: false + persist: true size: 50Gi storage_class: managed-nfs-storage volume_mount: @@ -331,14 +347,15 @@ profiles: \ /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram\ \ 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\ \nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\n. /home/jovyan/.bashrc\n\ - \nalias aws=\"aws --endpoint-url=http://localstack:4566\"\n\n# >>> conda initialize\ + \n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\n\n# >>> conda initialize\ \ >>>\n# !! Contents within this block are managed by 'conda init' !!\n__conda_setup=\"\ $('/opt/conda/bin/conda' 'shell.bash' 'hook' 2> /dev/null)\"\nif [ $? -eq 0\ \ ]; then\n eval \"$__conda_setup\"\nelse\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\"\ \ ]; then\n . \"/opt/conda/etc/profile.d/conda.sh\"\n else\n \ \ export PATH=\"/srv/conda/bin:$PATH\"\n fi\nfi\nunset __conda_setup\n\n\ if [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\n . \"/opt/conda/etc/profile.d/mamba.sh\"\ - \nfi\n# <<< conda initialize <<<\n" + \nfi\n# <<< conda initialize <<<\n\na={{spawner.user.name}}\n\nalias aws=\"\ + aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"" default_mode: null key: bash-rc mount_path: /workspace/.bashrc @@ -369,12 +386,14 @@ profiles: mem_guarantee: 4G mem_limit: 6G slug: eoepca_coder_slug_stac + env_from_config_maps: null + env_from_secrets: null groups: - group-a - group-b id: profile_studio_coder_stac image_pull_secrets: - - data: null + - data: ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9 name: cr-config persist: false init_containers: @@ -616,6 +635,7 @@ profiles: template: metadata: labels: + app: localstack-{{ spawner.user.name }} app.kubernetes.io/instance: localstack app.kubernetes.io/name: localstack spec: @@ -821,6 +841,64 @@ profiles: securityContext: {} serviceAccountName: localstack volumes: [] + - apiVersion: v1 + data: + ENV_VAR1: value1 + ENV_VAR2: value2 + kind: ConfigMap + metadata: + name: my-config + - apiVersion: v1 + data: + SECRET_KEY1: dmFsdWUx + SECRET_KEY2: dmFsdWUy + kind: Secret + metadata: + name: my-secret + type: Opaque + - apiVersion: v1 + data: + credentials: W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg= + kind: Secret + metadata: + name: aws-credentials-{{ spawner.user.name }} + type: Opaque + - apiVersion: external-secrets.io/v1beta1 + kind: ExternalSecret + metadata: + name: data-by-name + namespace: jupyter-{{ spawner.user.name }} + spec: + data: + - remoteRef: + key: secret-one + property: the-key + secretKey: secret-value + refreshInterval: 15s + secretStoreRef: + kind: ClusterSecretStore + name: k8s-secret-store + target: + creationPolicy: Owner + name: data-by-name + - apiVersion: external-secrets.io/v1beta1 + kind: ExternalSecret + metadata: + name: eoepca-plus-secret-ro + namespace: jupyter-{{ spawner.user.name }} + spec: + data: + - remoteRef: + key: eoepca-plus-secret-ro + property: .dockerconfigjson + secretKey: .dockerconfigjson + refreshInterval: 15s + secretStoreRef: + kind: ClusterSecretStore + name: k8s-secret-store + target: + creationPolicy: Owner + name: eoepca-plus-secret-ro key: manifests name: manifests persist: false @@ -838,40 +916,32 @@ profiles: XDG_DATA_HOME: /workspace/.local/share/ XDG_RUNTIME_DIR: /workspace/.local role_bindings: [] + secret_mounts: null volumes: - access_modes: - ReadWriteOnce claim_name: workspace-claim name: workspace-volume - persist: false + persist: true size: 50Gi storage_class: managed-nfs-storage volume_mount: mount_path: /workspace name: workspace-volume - config_maps: - - content: "mkdir -p /workspace/.config/autostart\n\n\ncat < /workspace/.config/autostart/qgis.desktop\ - \ \n[Desktop Entry]\nEncoding=UTF-8\nVersion=0.9.4\nType=Application\nName=qgis\n\ - Comment=qgis\nExec=qgis\nOnlyShowIn=XFCE;\nRunHook=0\nStartupNotify=false\n\ - Terminal=false\nHidden=false\nEOF" - default_mode: null - key: init - mount_path: /opt/init/.init.sh - name: init - persist: false - readonly: true - content: "alias ll=\"ls -l\"\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors\ \ /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram\ \ 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\ \nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\n. /home/jovyan/.bashrc\n\ - \nalias aws=\"aws --endpoint-url=http://localstack:4566\"\n\n# >>> conda initialize\ + \n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\n\n# >>> conda initialize\ \ >>>\n# !! Contents within this block are managed by 'conda init' !!\n__conda_setup=\"\ $('/opt/conda/bin/conda' 'shell.bash' 'hook' 2> /dev/null)\"\nif [ $? -eq 0\ \ ]; then\n eval \"$__conda_setup\"\nelse\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\"\ \ ]; then\n . \"/opt/conda/etc/profile.d/conda.sh\"\n else\n \ \ export PATH=\"/srv/conda/bin:$PATH\"\n fi\nfi\nunset __conda_setup\n\n\ if [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\n . \"/opt/conda/etc/profile.d/mamba.sh\"\ - \nfi\n# <<< conda initialize <<<\n" + \nfi\n# <<< conda initialize <<<\n\na={{spawner.user.name}}\n\nalias aws=\"\ + aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"" default_mode: null key: bash-rc mount_path: /workspace/.bashrc @@ -897,44 +967,30 @@ profiles: cpu_limit: 2 extra_resource_guarantees: {} extra_resource_limits: {} - image: eoepca/iga-remote-desktop-qgis:1.1.2 + image: eoepca/iga-remote-desktop-qgis:1.1.3 mem_guarantee: null mem_limit: 2G slug: eoepca_desktop_qgis + env_from_config_maps: null + env_from_secrets: null groups: - group-a - group-b id: profile_studio_desktop_qgis image_pull_secrets: [] - init_containers: - - command: - - sh - - -c - - sh /opt/init/.init.sh - image: eoepca/pde-code-server:develop - name: init-file-on-volume - volume_mounts: - - mount_path: /workspace - name: workspace-volume - - mount_path: /opt/init/.init.sh - name: init - sub_path: init + init_containers: [] manifests: null node_selector: {} pod_env_vars: - AWS_ACCESS_KEY_ID: SCWMRGD5GA0Y68XRKFW8 - AWS_REGION: fr-par - AWS_S3_ENDPOINT: s3.fr-par.scw.cloud - AWS_SECRET_ACCESS_KEY: a9a7dc0c-8b0a-47b1-a311-b134b258dbb3 - AWS_VIRTUAL_HOSTING: 'true' HOME: /workspace role_bindings: null + secret_mounts: null volumes: - access_modes: - ReadWriteOnce claim_name: workspace-claim name: workspace-volume - persist: false + persist: true size: 50Gi storage_class: managed-nfs-storage volume_mount: diff --git a/skaffold.yaml b/skaffold.yaml index 559145f..13cf1ba 100644 --- a/skaffold.yaml +++ b/skaffold.yaml @@ -31,7 +31,7 @@ profiles: hooks: before: - host: - command: ["sh", "-c", "config-generator/examples/generate-config.sh"] + command: ["sh", "-c", "apphub-configurator/examples/generate-config.sh"] os: [darwin, linux] manifests: rawYaml: From af6f5bad5977632cf95c0eb77ea1f6025bd6f822 Mon Sep 17 00:00:00 2001 From: pmembari Date: Thu, 3 Apr 2025 17:45:41 +0200 Subject: [PATCH 42/86] ci: edit package build --- .github/workflows/package.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index afe5619..d1637ce 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -30,7 +30,15 @@ jobs: # python -m pip install --upgrade pip pip install hatch - name: Build package - run: hatch build apphub-configurator/apphub-configurator/ + run: | + rm -rf application_hub_context + rm -rf setup* + cd apphub-configurator/ + hatch build + ls -la + mv -f dist/ ../ + cd - + ls -la - name: Publish package distributions to PyPI (main) if: github.ref == 'refs/heads/main' From 978e5cf30e3b1545c4c13bd9047d31480388a51a Mon Sep 17 00:00:00 2001 From: pmembari Date: Fri, 4 Apr 2025 10:40:35 +0200 Subject: [PATCH 43/86] chore: edit README refrence --- apphub-configurator/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apphub-configurator/pyproject.toml b/apphub-configurator/pyproject.toml index 4964470..d47dd4a 100644 --- a/apphub-configurator/pyproject.toml +++ b/apphub-configurator/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "apphub-configurator" dynamic = ["version"] description = "apphub-configurator" -readme = "README.md" +readme = "../docs/app-hub-configurator.md" requires-python = ">=3.10" license = "Apache-2.0" keywords = ["kubernetes", "configuration", "EOEPCA"] From 5828d7559d410143216bfed6615ee764e9b4bd3d Mon Sep 17 00:00:00 2001 From: pmembari Date: Fri, 4 Apr 2025 10:41:14 +0200 Subject: [PATCH 44/86] docs: add AppHub Configurator Application Package doc --- docs/app-hub-configurator.md | 33 +++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 34 insertions(+) create mode 100644 docs/app-hub-configurator.md diff --git a/docs/app-hub-configurator.md b/docs/app-hub-configurator.md new file mode 100644 index 0000000..6ee2109 --- /dev/null +++ b/docs/app-hub-configurator.md @@ -0,0 +1,33 @@ +## ApplicationHub configuration generator +This application aim to produce a `config.yml` file to generate all the configuration needs to be set for deployment of App Hub on a remote cluster using Devops tools. +# apphub-configurator + +[![PyPI - Version](https://img.shields.io/pypi/v/apphub-configurator.svg)](https://pypi.org/project/apphub-configurator) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/apphub-configurator.svg)](https://pypi.org/project/apphub-configurator) + +----- + +## Table of Contents + +- [Installation](#installation) +- [Overview](#overview) +- [Examples](#Examples) +- [License](#license) + +## Installation +Create a hatch environment with the dependencies listed in the file `pyproject.toml`. + +```console +hatch shell prod +hatch -e prod run python -m ipykernel install --user --name=apphub_configurator --display-name "apphub_configurator" +``` +> Note: you may need to reopen visual studio code to set the jupyter kernel successfully. +## Overview +This package contains a notebook and the python modules to support the generation of ApplicationHub configurations for a minikube cluster. For more information about ApplicationHub please check this [link](https://github.com/EOEPCA/application-hub-context) + +## Examples: +The [example](./examples/) folder contains a notebook and the python modules to support the generation of ApplicationHub configurations. + +## License + +`apphub-configurator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. diff --git a/mkdocs.yml b/mkdocs.yml index ee86f92..8db05bc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -25,4 +25,5 @@ nav: - Introduction: 'index.md' - Kubernetes: 'k8s.md' - Configuration: 'configuration.md' + - AppHub Configurator Application Package: 'app-hub-configurator.md' - JupyterHub API: 'jupyterhub-api.md' From 309e4d1b5bf99f4688b56f126e03c95247380fa9 Mon Sep 17 00:00:00 2001 From: pmembari Date: Fri, 4 Apr 2025 10:43:05 +0200 Subject: [PATCH 45/86] updating docs --- .../examples/config-generator.ipynb | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/apphub-configurator/examples/config-generator.ipynb b/apphub-configurator/examples/config-generator.ipynb index 8c63109..6488944 100644 --- a/apphub-configurator/examples/config-generator.ipynb +++ b/apphub-configurator/examples/config-generator.ipynb @@ -372,7 +372,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -381,7 +381,7 @@ "ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)" ] }, - "execution_count": 10, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -420,7 +420,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -438,7 +438,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -448,7 +448,7 @@ " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" ] }, - "execution_count": 12, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -510,7 +510,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -521,7 +521,7 @@ " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" ] }, - "execution_count": 13, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -585,7 +585,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -597,7 +597,7 @@ " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" ] }, - "execution_count": 14, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -646,7 +646,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -659,7 +659,7 @@ " Profile(id='profile_jupyter_lab_2', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', description='Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret', slug='eoepca_jupyter_lab_2', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" ] }, - "execution_count": 15, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -714,7 +714,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -728,7 +728,7 @@ " Profile(id='profile_studio_coder_stac', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Understanding STAC for input/output data modelling', description='Understand the role of STAC in input/output data manifests in EO data processing workflows', slug='eoepca_coder_slug_stac', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='set -x \\n\\ncd /workspace\\n\\ngit clone \\'https://github.com/eoap/stac-eoap.git\\'\\n\\ncode-server --install-extension ms-python.python \\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nmkdir -p /workspace/User/\\n\\necho \\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\' > /workspace/User/settings.json\\n\\npython -m venv /workspace/.venv\\nsource /workspace/.venv/bin/activate\\n/workspace/.venv/bin/python -m pip install --no-cache-dir stactools rasterio requests stac-asset click-logging tabulate tqdm pystac-client ipykernel loguru scikit-image rio_stac boto3==1.35.23\\n\\n/workspace/.venv/bin/python -m pip install --index-url https://test.pypi.org/simple cwl-wrapper\\n\\n/workspace/.venv/bin/python -m ipykernel install --user --name stac_env --display-name \"Python (STAC)\"\\n\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\nexport AWS_ACCESS_KEY_ID=\"test\"\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\naws s3 mb s3://results --endpoint-url=http://localstack:4566\\n\\nexit 0', persist=False), ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.local', 'XDG_DATA_HOME': '/workspace/.local/share/', 'CWLTOOL_OPTIONS': '--podman', 'CODE_SERVER_WS': '/workspace/stac-eoap', 'AWS_DEFAULT_REGION': 'us-east-1', 'AWS_ACCESS_KEY_ID': 'test', 'AWS_SECRET_ACCESS_KEY': 'test'}, default_url=None, node_selector={}, role_bindings=[], image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=[Manifest(name='manifests', key='manifests', content=[{'apiVersion': 'v1', 'kind': 'ServiceAccount', 'metadata': {'name': 'localstack'}}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'Role', 'metadata': {'name': 'localstack'}, 'rules': [{'apiGroups': [''], 'resources': ['pods'], 'verbs': ['*']}, {'apiGroups': [''], 'resources': ['pods/log'], 'verbs': ['get']}, {'apiGroups': [''], 'resources': ['pods/exec'], 'verbs': ['get', 'create']}, {'apiGroups': [''], 'resources': ['services'], 'verbs': ['get', 'list']}]}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'RoleBinding', 'metadata': {'name': 'localstack'}, 'subjects': [{'kind': 'ServiceAccount', 'name': 'localstack'}], 'roleRef': {'kind': 'Role', 'name': 'localstack', 'apiGroup': 'rbac.authorization.k8s.io'}}, {'apiVersion': 'v1', 'kind': 'Service', 'metadata': {'name': 'localstack'}, 'spec': {'type': 'ClusterIP', 'ports': [{'name': 'edge', 'port': 4566, 'targetPort': 4566}, {'name': 'external-service-port-4510', 'port': 4510, 'targetPort': 'ext-svc-4510'}, {'name': 'external-service-port-4511', 'port': 4511, 'targetPort': 'ext-svc-4511'}, {'name': 'external-service-port-4512', 'port': 4512, 'targetPort': 'ext-svc-4512'}, {'name': 'external-service-port-4513', 'port': 4513, 'targetPort': 'ext-svc-4513'}, {'name': 'external-service-port-4514', 'port': 4514, 'targetPort': 'ext-svc-4514'}, {'name': 'external-service-port-4515', 'port': 4515, 'targetPort': 'ext-svc-4515'}, {'name': 'external-service-port-4516', 'port': 4516, 'targetPort': 'ext-svc-4516'}, {'name': 'external-service-port-4517', 'port': 4517, 'targetPort': 'ext-svc-4517'}, {'name': 'external-service-port-4518', 'port': 4518, 'targetPort': 'ext-svc-4518'}, {'name': 'external-service-port-4519', 'port': 4519, 'targetPort': 'ext-svc-4519'}, {'name': 'external-service-port-4520', 'port': 4520, 'targetPort': 'ext-svc-4520'}, {'name': 'external-service-port-4521', 'port': 4521, 'targetPort': 'ext-svc-4521'}, {'name': 'external-service-port-4522', 'port': 4522, 'targetPort': 'ext-svc-4522'}, {'name': 'external-service-port-4523', 'port': 4523, 'targetPort': 'ext-svc-4523'}, {'name': 'external-service-port-4524', 'port': 4524, 'targetPort': 'ext-svc-4524'}, {'name': 'external-service-port-4525', 'port': 4525, 'targetPort': 'ext-svc-4525'}, {'name': 'external-service-port-4526', 'port': 4526, 'targetPort': 'ext-svc-4526'}, {'name': 'external-service-port-4527', 'port': 4527, 'targetPort': 'ext-svc-4527'}, {'name': 'external-service-port-4528', 'port': 4528, 'targetPort': 'ext-svc-4528'}, {'name': 'external-service-port-4529', 'port': 4529, 'targetPort': 'ext-svc-4529'}, {'name': 'external-service-port-4530', 'port': 4530, 'targetPort': 'ext-svc-4530'}, {'name': 'external-service-port-4531', 'port': 4531, 'targetPort': 'ext-svc-4531'}, {'name': 'external-service-port-4532', 'port': 4532, 'targetPort': 'ext-svc-4532'}, {'name': 'external-service-port-4533', 'port': 4533, 'targetPort': 'ext-svc-4533'}, {'name': 'external-service-port-4534', 'port': 4534, 'targetPort': 'ext-svc-4534'}, {'name': 'external-service-port-4535', 'port': 4535, 'targetPort': 'ext-svc-4535'}, {'name': 'external-service-port-4536', 'port': 4536, 'targetPort': 'ext-svc-4536'}, {'name': 'external-service-port-4537', 'port': 4537, 'targetPort': 'ext-svc-4537'}, {'name': 'external-service-port-4538', 'port': 4538, 'targetPort': 'ext-svc-4538'}, {'name': 'external-service-port-4539', 'port': 4539, 'targetPort': 'ext-svc-4539'}, {'name': 'external-service-port-4540', 'port': 4540, 'targetPort': 'ext-svc-4540'}, {'name': 'external-service-port-4541', 'port': 4541, 'targetPort': 'ext-svc-4541'}, {'name': 'external-service-port-4542', 'port': 4542, 'targetPort': 'ext-svc-4542'}, {'name': 'external-service-port-4543', 'port': 4543, 'targetPort': 'ext-svc-4543'}, {'name': 'external-service-port-4544', 'port': 4544, 'targetPort': 'ext-svc-4544'}, {'name': 'external-service-port-4545', 'port': 4545, 'targetPort': 'ext-svc-4545'}, {'name': 'external-service-port-4546', 'port': 4546, 'targetPort': 'ext-svc-4546'}, {'name': 'external-service-port-4547', 'port': 4547, 'targetPort': 'ext-svc-4547'}, {'name': 'external-service-port-4548', 'port': 4548, 'targetPort': 'ext-svc-4548'}, {'name': 'external-service-port-4549', 'port': 4549, 'targetPort': 'ext-svc-4549'}, {'name': 'external-service-port-4550', 'port': 4550, 'targetPort': 'ext-svc-4550'}, {'name': 'external-service-port-4551', 'port': 4551, 'targetPort': 'ext-svc-4551'}, {'name': 'external-service-port-4552', 'port': 4552, 'targetPort': 'ext-svc-4552'}, {'name': 'external-service-port-4553', 'port': 4553, 'targetPort': 'ext-svc-4553'}, {'name': 'external-service-port-4554', 'port': 4554, 'targetPort': 'ext-svc-4554'}, {'name': 'external-service-port-4555', 'port': 4555, 'targetPort': 'ext-svc-4555'}, {'name': 'external-service-port-4556', 'port': 4556, 'targetPort': 'ext-svc-4556'}, {'name': 'external-service-port-4557', 'port': 4557, 'targetPort': 'ext-svc-4557'}, {'name': 'external-service-port-4558', 'port': 4558, 'targetPort': 'ext-svc-4558'}, {'name': 'external-service-port-4559', 'port': 4559, 'targetPort': 'ext-svc-4559'}], 'selector': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}}, {'apiVersion': 'apps/v1', 'kind': 'Deployment', 'metadata': {'name': 'localstack'}, 'spec': {'replicas': 1, 'strategy': {'type': 'RollingUpdate'}, 'selector': {'matchLabels': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'template': {'metadata': {'labels': {'app': 'localstack-{{ spawner.user.name }}', 'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'spec': {'serviceAccountName': 'localstack', 'securityContext': {}, 'containers': [{'name': 'localstack', 'securityContext': {}, 'image': 'localstack/localstack:latest', 'imagePullPolicy': 'IfNotPresent', 'ports': [{'name': 'edge', 'containerPort': 4566, 'protocol': 'TCP'}, {'name': 'ext-svc-4510', 'containerPort': 4510, 'protocol': 'TCP'}, {'name': 'ext-svc-4511', 'containerPort': 4511, 'protocol': 'TCP'}, {'name': 'ext-svc-4512', 'containerPort': 4512, 'protocol': 'TCP'}, {'name': 'ext-svc-4513', 'containerPort': 4513, 'protocol': 'TCP'}, {'name': 'ext-svc-4514', 'containerPort': 4514, 'protocol': 'TCP'}, {'name': 'ext-svc-4515', 'containerPort': 4515, 'protocol': 'TCP'}, {'name': 'ext-svc-4516', 'containerPort': 4516, 'protocol': 'TCP'}, {'name': 'ext-svc-4517', 'containerPort': 4517, 'protocol': 'TCP'}, {'name': 'ext-svc-4518', 'containerPort': 4518, 'protocol': 'TCP'}, {'name': 'ext-svc-4519', 'containerPort': 4519, 'protocol': 'TCP'}, {'name': 'ext-svc-4520', 'containerPort': 4520, 'protocol': 'TCP'}, {'name': 'ext-svc-4521', 'containerPort': 4521, 'protocol': 'TCP'}, {'name': 'ext-svc-4522', 'containerPort': 4522, 'protocol': 'TCP'}, {'name': 'ext-svc-4523', 'containerPort': 4523, 'protocol': 'TCP'}, {'name': 'ext-svc-4524', 'containerPort': 4524, 'protocol': 'TCP'}, {'name': 'ext-svc-4525', 'containerPort': 4525, 'protocol': 'TCP'}, {'name': 'ext-svc-4526', 'containerPort': 4526, 'protocol': 'TCP'}, {'name': 'ext-svc-4527', 'containerPort': 4527, 'protocol': 'TCP'}, {'name': 'ext-svc-4528', 'containerPort': 4528, 'protocol': 'TCP'}, {'name': 'ext-svc-4529', 'containerPort': 4529, 'protocol': 'TCP'}, {'name': 'ext-svc-4530', 'containerPort': 4530, 'protocol': 'TCP'}, {'name': 'ext-svc-4531', 'containerPort': 4531, 'protocol': 'TCP'}, {'name': 'ext-svc-4532', 'containerPort': 4532, 'protocol': 'TCP'}, {'name': 'ext-svc-4533', 'containerPort': 4533, 'protocol': 'TCP'}, {'name': 'ext-svc-4534', 'containerPort': 4534, 'protocol': 'TCP'}, {'name': 'ext-svc-4535', 'containerPort': 4535, 'protocol': 'TCP'}, {'name': 'ext-svc-4536', 'containerPort': 4536, 'protocol': 'TCP'}, {'name': 'ext-svc-4537', 'containerPort': 4537, 'protocol': 'TCP'}, {'name': 'ext-svc-4538', 'containerPort': 4538, 'protocol': 'TCP'}, {'name': 'ext-svc-4539', 'containerPort': 4539, 'protocol': 'TCP'}, {'name': 'ext-svc-4540', 'containerPort': 4540, 'protocol': 'TCP'}, {'name': 'ext-svc-4541', 'containerPort': 4541, 'protocol': 'TCP'}, {'name': 'ext-svc-4542', 'containerPort': 4542, 'protocol': 'TCP'}, {'name': 'ext-svc-4543', 'containerPort': 4543, 'protocol': 'TCP'}, {'name': 'ext-svc-4544', 'containerPort': 4544, 'protocol': 'TCP'}, {'name': 'ext-svc-4545', 'containerPort': 4545, 'protocol': 'TCP'}, {'name': 'ext-svc-4546', 'containerPort': 4546, 'protocol': 'TCP'}, {'name': 'ext-svc-4547', 'containerPort': 4547, 'protocol': 'TCP'}, {'name': 'ext-svc-4548', 'containerPort': 4548, 'protocol': 'TCP'}, {'name': 'ext-svc-4549', 'containerPort': 4549, 'protocol': 'TCP'}, {'name': 'ext-svc-4550', 'containerPort': 4550, 'protocol': 'TCP'}, {'name': 'ext-svc-4551', 'containerPort': 4551, 'protocol': 'TCP'}, {'name': 'ext-svc-4552', 'containerPort': 4552, 'protocol': 'TCP'}, {'name': 'ext-svc-4553', 'containerPort': 4553, 'protocol': 'TCP'}, {'name': 'ext-svc-4554', 'containerPort': 4554, 'protocol': 'TCP'}, {'name': 'ext-svc-4555', 'containerPort': 4555, 'protocol': 'TCP'}, {'name': 'ext-svc-4556', 'containerPort': 4556, 'protocol': 'TCP'}, {'name': 'ext-svc-4557', 'containerPort': 4557, 'protocol': 'TCP'}, {'name': 'ext-svc-4558', 'containerPort': 4558, 'protocol': 'TCP'}, {'name': 'ext-svc-4559', 'containerPort': 4559, 'protocol': 'TCP'}], 'livenessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'readinessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'resources': {}, 'env': [{'name': 'DEBUG', 'value': '0'}, {'name': 'EXTERNAL_SERVICE_PORTS_START', 'value': '4510'}, {'name': 'EXTERNAL_SERVICE_PORTS_END', 'value': '4560'}, {'name': 'LOCALSTACK_K8S_SERVICE_NAME', 'value': 'localstack'}, {'name': 'LOCALSTACK_K8S_NAMESPACE', 'valueFrom': {'fieldRef': {'fieldPath': 'metadata.namespace'}}}, {'name': 'LAMBDA_RUNTIME_EXECUTOR', 'value': 'docker'}, {'name': 'LAMBDA_K8S_IMAGE_PREFIX', 'value': 'localstack/lambda-'}, {'name': 'LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT', 'value': '60'}, {'name': 'OVERRIDE_IN_DOCKER', 'value': '1'}]}], 'volumes': []}}}}, {'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': 'my-config'}, 'data': {'ENV_VAR1': 'value1', 'ENV_VAR2': 'value2'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'my-secret'}, 'type': 'Opaque', 'data': {'SECRET_KEY1': 'dmFsdWUx', 'SECRET_KEY2': 'dmFsdWUy'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'aws-credentials-{{ spawner.user.name }}'}, 'type': 'Opaque', 'data': {'credentials': 'W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg='}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'data-by-name', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'data-by-name', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': 'secret-value', 'remoteRef': {'key': 'secret-one', 'property': 'the-key'}}]}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'eoepca-plus-secret-ro', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'eoepca-plus-secret-ro', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': '.dockerconfigjson', 'remoteRef': {'key': 'eoepca-plus-secret-ro', 'property': '.dockerconfigjson'}}]}}], persist=False)], env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" ] }, - "execution_count": 16, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -802,7 +802,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -817,7 +817,7 @@ " Profile(id='profile_studio_desktop_qgis', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='QGIS on a Remote Desktop', description='Spatial visualization and decision-making tools for everyone', slug='eoepca_desktop_qgis', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='2G', mem_guarantee=None, image='eoepca/iga-remote-desktop-qgis:1.1.3', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace'}, default_url='desktop', node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" ] }, - "execution_count": 17, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -862,7 +862,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": {}, "outputs": [ { From 063dc6a568a4d454ff69162d9f2acd33d275bb68 Mon Sep 17 00:00:00 2001 From: pmembari Date: Fri, 4 Apr 2025 10:46:53 +0200 Subject: [PATCH 46/86] chore: bump version 0.0.5 --- apphub-configurator/src/apphub_configurator/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apphub-configurator/src/apphub_configurator/__about__.py b/apphub-configurator/src/apphub_configurator/__about__.py index 5f6f7b4..a132830 100644 --- a/apphub-configurator/src/apphub_configurator/__about__.py +++ b/apphub-configurator/src/apphub_configurator/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2025-present pmembari # # SPDX-License-Identifier: MIT -__version__ = "0.0.4" +__version__ = "0.0.5" From de6c2b81f341984612a7ae81b47dc8ec6dd77205 Mon Sep 17 00:00:00 2001 From: pmembari Date: Fri, 4 Apr 2025 10:53:39 +0200 Subject: [PATCH 47/86] docs: edit example folder path --- docs/app-hub-configurator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/app-hub-configurator.md b/docs/app-hub-configurator.md index 6ee2109..e12c8cd 100644 --- a/docs/app-hub-configurator.md +++ b/docs/app-hub-configurator.md @@ -26,7 +26,7 @@ hatch -e prod run python -m ipykernel install --user --name=apphub_configurator This package contains a notebook and the python modules to support the generation of ApplicationHub configurations for a minikube cluster. For more information about ApplicationHub please check this [link](https://github.com/EOEPCA/application-hub-context) ## Examples: -The [example](./examples/) folder contains a notebook and the python modules to support the generation of ApplicationHub configurations. +The [example](../apphub-configurator/examples/) folder contains a notebook and the python modules to support the generation of ApplicationHub configurations. ## License From 232c4ee0dec8c6c29b7abab79cbb4ee34014da45 Mon Sep 17 00:00:00 2001 From: pmembari Date: Fri, 4 Apr 2025 10:56:03 +0200 Subject: [PATCH 48/86] ci: updating --- .github/workflows/package.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index d1637ce..bd58785 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -2,7 +2,7 @@ name: Publish to PyPI on: push: - branches: [ "ESAEOEPCA-236", "patch-1" ] + branches: ["patch-1" ] release: types: [published] pull_request: From 03e177bf1a2ad8f643d88311fb6073da76b0ee52 Mon Sep 17 00:00:00 2001 From: pmembari Date: Fri, 4 Apr 2025 10:57:04 +0200 Subject: [PATCH 49/86] update version to avoid ci error --- apphub-configurator/src/apphub_configurator/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apphub-configurator/src/apphub_configurator/__about__.py b/apphub-configurator/src/apphub_configurator/__about__.py index a132830..06f57eb 100644 --- a/apphub-configurator/src/apphub_configurator/__about__.py +++ b/apphub-configurator/src/apphub_configurator/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2025-present pmembari # # SPDX-License-Identifier: MIT -__version__ = "0.0.5" +__version__ = "0.0.6" From 03dbfaef30bb08927e76f3f0ba244cf669c269fd Mon Sep 17 00:00:00 2001 From: pmembari Date: Fri, 4 Apr 2025 11:03:52 +0200 Subject: [PATCH 50/86] update ci and bump new version 0.0.6 --- .github/workflows/package.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index bd58785..73c68e1 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -2,11 +2,17 @@ name: Publish to PyPI on: push: - branches: ["patch-1" ] + branches: [ "ESAEOEPCA-236", "patch-1" ] + paths: + - 'apphub-configurator/**' + release: types: [published] pull_request: branches: [develop, main] + paths: + - 'apphub-configurator/**' + permissions: contents: read From 6888fad1232dd5aca0058f9fffa71011294f6823 Mon Sep 17 00:00:00 2001 From: Parham Membari <148858617+parham-membari-terradue@users.noreply.github.com> Date: Fri, 4 Apr 2025 11:48:14 +0200 Subject: [PATCH 51/86] CI + config-generator doc (ESAEOEPCA-236) (#5) * ci: create pipy package * fix: edit pyproject with correct Terradue email * ci: create pipy package * ci: remove test * ci: add branch * ci: add branch * ci: edit package ci * feat: edit hatch env path * docs: initial docs * feat: edit generate-config sh refrence * feat: update documentation * Update setup.cfg * Update setup.cfg * docs: documenation for config generator * docs: docs for finding kernel * feat: add logging * feat: algining for pypi push * ci: edit package build * chore: edit README refrence * docs: add AppHub Configurator Application Package doc * updating docs * chore: bump version 0.0.5 * docs: edit example folder path * ci: updating * update version to avoid ci error * update ci and bump new version 0.0.6 --------- Co-authored-by: liviodonnini <47381409+liviodonnini@users.noreply.github.com> --- .dockerignore | 2 +- .github/workflows/package.yaml | 58 + .gitignore | 3 +- .../LICENSE.txt | 0 .../README.md | 17 +- .../config-maps/bash-login | 0 .../config-maps/bash-rc | 0 .../config-maps/conda-rc.yml | 0 .../config-maps/init-qgis.sh | 0 .../config-maps/init-stac.sh | 0 .../config-maps/init.sh | 0 .../examples/config-generator.ipynb | 1339 +++++++++++++++++ .../examples/generate-config.sh | 4 + .../examples/generate_config.py | 6 + .../manifests/dask-gateway.yaml | 0 .../manifests/kaniko.yaml | 0 .../manifests/manifest.yaml | 0 .../pyproject.toml | 19 +- .../src/apphub_configurator/__about__.py | 2 +- .../src/apphub_configurator/__init__.py | 0 .../src/apphub_configurator/helpers.py | 0 .../src/apphub_configurator/models.py | 0 .../tests/__init__.py | 0 .../config-generator-eoepca-demo.ipynb | 729 --------- .../examples/config-generator.ipynb | 610 -------- config-generator/examples/generate-config.sh | 4 - docs/app-hub-configurator.md | 33 + eoepca-demo/config.yml | 150 +- mkdocs.yml | 1 + setup.cfg | 3 +- skaffold.yaml | 2 +- 31 files changed, 1565 insertions(+), 1417 deletions(-) create mode 100644 .github/workflows/package.yaml rename {config-generator => apphub-configurator}/LICENSE.txt (100%) rename {config-generator => apphub-configurator}/README.md (64%) rename {config-generator => apphub-configurator}/config-maps/bash-login (100%) rename {config-generator => apphub-configurator}/config-maps/bash-rc (100%) rename {config-generator => apphub-configurator}/config-maps/conda-rc.yml (100%) rename {config-generator => apphub-configurator}/config-maps/init-qgis.sh (100%) rename {config-generator => apphub-configurator}/config-maps/init-stac.sh (100%) rename {config-generator => apphub-configurator}/config-maps/init.sh (100%) create mode 100644 apphub-configurator/examples/config-generator.ipynb create mode 100755 apphub-configurator/examples/generate-config.sh rename {config-generator => apphub-configurator}/examples/generate_config.py (97%) rename {config-generator => apphub-configurator}/manifests/dask-gateway.yaml (100%) rename {config-generator => apphub-configurator}/manifests/kaniko.yaml (100%) rename {config-generator => apphub-configurator}/manifests/manifest.yaml (100%) rename {config-generator => apphub-configurator}/pyproject.toml (75%) rename {config-generator => apphub-configurator}/src/apphub_configurator/__about__.py (83%) rename {config-generator => apphub-configurator}/src/apphub_configurator/__init__.py (100%) rename {config-generator => apphub-configurator}/src/apphub_configurator/helpers.py (100%) rename {config-generator => apphub-configurator}/src/apphub_configurator/models.py (100%) rename {config-generator => apphub-configurator}/tests/__init__.py (100%) delete mode 100644 config-generator/examples/config-generator-eoepca-demo.ipynb delete mode 100644 config-generator/examples/config-generator.ipynb delete mode 100755 config-generator/examples/generate-config.sh create mode 100644 docs/app-hub-configurator.md diff --git a/.dockerignore b/.dockerignore index a5d8001..d19c40d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,6 @@ jupyterhub values.yaml config.yml -config-generator +apphub-configurator skaffold.yaml files/* \ No newline at end of file diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml new file mode 100644 index 0000000..73c68e1 --- /dev/null +++ b/.github/workflows/package.yaml @@ -0,0 +1,58 @@ +name: Publish to PyPI + +on: + push: + branches: [ "ESAEOEPCA-236", "patch-1" ] + paths: + - 'apphub-configurator/**' + + release: + types: [published] + pull_request: + branches: [develop, main] + paths: + - 'apphub-configurator/**' + + +permissions: + contents: read + +jobs: + deploy: + runs-on: ubuntu-latest + environment: release + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + cache: 'pip' + - name: Install dependencies + run: | + # python -m pip install --upgrade pip + pip install hatch + - name: Build package + run: | + rm -rf application_hub_context + rm -rf setup* + cd apphub-configurator/ + hatch build + ls -la + mv -f dist/ ../ + cd - + ls -la + + - name: Publish package distributions to PyPI (main) + if: github.ref == 'refs/heads/main' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://upload.pypi.org/legacy/ + - name: Publish package distributions to PyPI (develop) + if: github.ref != 'refs/heads/main' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index ec0db41..f2a524e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ values.yaml build _README.md dist -.env-config-generator \ No newline at end of file +.env-apphub-configurator +hatch \ No newline at end of file diff --git a/config-generator/LICENSE.txt b/apphub-configurator/LICENSE.txt similarity index 100% rename from config-generator/LICENSE.txt rename to apphub-configurator/LICENSE.txt diff --git a/config-generator/README.md b/apphub-configurator/README.md similarity index 64% rename from config-generator/README.md rename to apphub-configurator/README.md index 49ce362..f3e861f 100644 --- a/config-generator/README.md +++ b/apphub-configurator/README.md @@ -1,18 +1,5 @@ ## ApplicationHub configuration generator - -The AppHub configuration generator is the helper that eases the generation of the desired configuration selecting tools among the predefined ones. -They include: - -- **aws-cli**: CLI used for interfacing with an S3 service -- **Stars**: CLI used for EO data stage-in from S3 or external repos and Data stage-out to S3 bucket -- **cwltool**: CWL runner using local resources -- **calrissian**: CWL runner on Kubernetes -- **cwl-wrapper**: Utility to add stage-in/out nodes to an Application Package -- **podman**: Container engine -- **git**: CLI used for interfacing with git-based software repositories -- **dvc**: Data versioning -- **oras**: Client for OCI compliant repositories - +This application aim to produce a `config.yml` file to generate all the configuration needs to be set for deployment of App Hub on a remote cluster using Devops tools. # apphub-configurator [![PyPI - Version](https://img.shields.io/pypi/v/apphub-configurator.svg)](https://pypi.org/project/apphub-configurator) @@ -33,11 +20,13 @@ Create a hatch environment with the dependencies listed in the file `pyproject.t hatch shell prod hatch -e prod run python -m ipykernel install --user --name=apphub_configurator --display-name "apphub_configurator" ``` +> Note: you may need to reopen visual studio code to set the jupyter kernel successfully. ## Overview This package contains a notebook and the python modules to support the generation of ApplicationHub configurations for a minikube cluster. For more information about ApplicationHub please check this [link](https://github.com/EOEPCA/application-hub-context) ## Examples: The [example](./examples/) folder contains a notebook and the python modules to support the generation of ApplicationHub configurations. + ## License `apphub-configurator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. diff --git a/config-generator/config-maps/bash-login b/apphub-configurator/config-maps/bash-login similarity index 100% rename from config-generator/config-maps/bash-login rename to apphub-configurator/config-maps/bash-login diff --git a/config-generator/config-maps/bash-rc b/apphub-configurator/config-maps/bash-rc similarity index 100% rename from config-generator/config-maps/bash-rc rename to apphub-configurator/config-maps/bash-rc diff --git a/config-generator/config-maps/conda-rc.yml b/apphub-configurator/config-maps/conda-rc.yml similarity index 100% rename from config-generator/config-maps/conda-rc.yml rename to apphub-configurator/config-maps/conda-rc.yml diff --git a/config-generator/config-maps/init-qgis.sh b/apphub-configurator/config-maps/init-qgis.sh similarity index 100% rename from config-generator/config-maps/init-qgis.sh rename to apphub-configurator/config-maps/init-qgis.sh diff --git a/config-generator/config-maps/init-stac.sh b/apphub-configurator/config-maps/init-stac.sh similarity index 100% rename from config-generator/config-maps/init-stac.sh rename to apphub-configurator/config-maps/init-stac.sh diff --git a/config-generator/config-maps/init.sh b/apphub-configurator/config-maps/init.sh similarity index 100% rename from config-generator/config-maps/init.sh rename to apphub-configurator/config-maps/init.sh diff --git a/apphub-configurator/examples/config-generator.ipynb b/apphub-configurator/examples/config-generator.ipynb new file mode 100644 index 0000000..6488944 --- /dev/null +++ b/apphub-configurator/examples/config-generator.ipynb @@ -0,0 +1,1339 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Generate App Hub's configuration\n", + "\n", + "This notebook aim to produce a `config.yml` file to generate all the configuration needs to be set for deployment of App Hub on a remote cluster using Devops tools. \n", + "\n", + "This Notebook can be used for the following requirements:\n", + "\n", + "* Initial dependencies\n", + "* Create the configuration for kubernetes' `PV`s, and `PVC`s(e.g. workspace volume, and calrissian volume)\n", + "* Generate the configuration for kubernetes' config maps\n", + "* Creation of different Profiles \n", + "\n", + "Table of Content:\n", + "- [Initial dependencies](#initial-dependencies)\n", + " - [Configuration](#configuration)\n", + "- [Volumes](#volumes)\n", + " - [Workspace volume](#workspace-volume)\n", + " - [Calrissian volume](#calrissian-volume)\n", + "- [Configmaps](#configmaps)\n", + " - [bash login Configmap](#bash-login-configmap)\n", + " - [bash rc Configmap](#bash-rc-configmap) \n", + " - [QGIS Configmap](#qgis-configmap) \n", + " - [Stage-in/out Configmap](#stage-inout-configmap)\n", + "- [Profiles](#profiles)\n", + " - [Coder](#coder)\n", + " - [Coder with init.sh](#coder-with-initsh)\n", + " - [JupyterLab](#jupyterlab)\n", + " - [JupyterLab Plus](#jupyterlab-plus)\n", + " - [E-Learning](#e-learning)\n", + " - [QGIS](#qgis)\n", + "- [Write Configuration](#write-configuration)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initial dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "PosixPath('/home/t2/Desktop/p/EOEPCA/application-hub-context/apphub-configurator/examples')" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import yaml\n", + "from apphub_configurator.models import *\n", + "from pathlib import Path\n", + "import os\n", + "import json\n", + "from pprint import pprint\n", + "current_dir = Path(os.getcwd())\n", + "parent_dir = current_dir.parent\n", + "current_dir" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Configuration\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "storage_class_rwo = \"managed-nfs-storage\"\n", + "storage_class_rwx = \"managed-nfs-storage\"\n", + "\n", + "workspace_volume_size = \"50Gi\"\n", + "calrissian_volume_size = \"50Gi\"" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Volumes\n", + "\n", + "In this section, the user will provide the data class objects for creation of volume:\n", + "- Volume for workspace\n", + "- Volume for calrissian\n", + "These two configuration will be use " + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Workspace Volume\n", + "In this section, the user will providing the configuration of a kubernetes Volume for development environment (i.e workspace). It is important to mention that this volume must keep data persistently. therefore, the user set `persist=True`" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "workspace_volume = Volume( # type: ignore\n", + " name=\"workspace-volume\",\n", + " size=workspace_volume_size,\n", + " claim_name=\"workspace-claim\",\n", + " mount_path=\"/workspace\",\n", + " storage_class=storage_class_rwo,\n", + " access_modes=[\"ReadWriteOnce\"],\n", + " volume_mount=VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"), # type: ignore\n", + " persist=True,\n", + ")\n", + "workspace_volume" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Calrissian Volume\n", + "In this section, the user will configure a **Kubernetes Volume** for Calrissian jobs. Since the job runs within Calrissian and does not require data retention on the Calrissian pod, therefore the user should set `persist=False`. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "calrissian_volume = Volume( # type: ignore\n", + " name=\"calrissian-volume\",\n", + " claim_name=\"calrissian-claim\",\n", + " size=calrissian_volume_size,\n", + " storage_class=storage_class_rwx,\n", + " access_modes=[\"ReadWriteMany\"],\n", + " volume_mount=VolumeMount(name=\"calrissian-volume\", mount_path=\"/calrissian\"), # type: ignore\n", + " persist=False,\n", + ")\n", + "calrissian_volume" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ConfigMaps\n", + "\n", + "In this section, the user will provide the configuration for some kubernetes configmaps that is commonly used in this notebook including:\n", + "- bash login Configmap\n", + "- bash rc Configmap\n", + "- QGIS Configmap\n", + "- Stage-in/out Configmap\n", + "These configmaps will be mounted as files on different pods. " + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### bash login Configmap\n", + "\n", + "This configmap file aims to configure the Terminal across different profiles. " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bash_login_file_path = os.path.join(parent_dir, \"config-maps/bash-login\") \n", + "with open(bash_login_file_path, \"r\") as f:\n", + " content = f.read()\n", + "\n", + "bash_login_cm = ConfigMap( # type: ignore\n", + " name=\"bash-login\",\n", + " key=\"bash-login\",\n", + " content=content,\n", + " readonly=True,\n", + " persist=False,\n", + " mount_path=\"/workspace/.bash_login\",\n", + ")\n", + "bash_login_cm" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### bash-rc Configmap\n", + "This section explains how we configure a **Kubernetes ConfigMap** to manage the `.bashrc` file inside a pod. This config map will:\n", + "- It provides useful aliases and environment settings for users.\n", + "- It ensures a consistent environment across different pods." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bash_rc_cm_file_path = os.path.join(parent_dir, \"config-maps/bash-rc\") \n", + "with open(bash_rc_cm_file_path, \"r\") as f:\n", + " content = f.read()\n", + "bash_rc_cm = ConfigMap( # type: ignore\n", + " name=\"bash-rc\",\n", + " key=\"bash-rc\",\n", + " content=content,\n", + " readonly=True,\n", + " persist=False,\n", + " mount_path=\"/workspace/.bashrc\",\n", + ")\n", + "bash_rc_cm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### QGIS Configmap\n", + "The cell below will provide a Configmap configuration for QGIS setup." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='mkdir -p /workspace/.config/autostart\\n\\n\\ncat < /workspace/.config/autostart/qgis.desktop \\n[Desktop Entry]\\nEncoding=UTF-8\\nVersion=0.9.4\\nType=Application\\nName=qgis\\nComment=qgis\\nExec=qgis\\nOnlyShowIn=XFCE;\\nRunHook=0\\nStartupNotify=false\\nTerminal=false\\nHidden=false\\nEOF', persist=False)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "init_qgis_file_path = os.path.join(parent_dir, \"config-maps/init-qgis.sh\") \n", + "with open(init_qgis_file_path, \"r\") as f:\n", + " content = f.read()\n", + "\n", + "init_qgis_cm = ConfigMap( # type: ignore\n", + " name=\"init\",\n", + " key=\"init\",\n", + " content=content,\n", + " readonly=True,\n", + " persist=False,\n", + " mount_path=\"/opt/init/.init.sh\",\n", + ")\n", + "init_qgis_cm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Stage-in/out Configmap\n", + "The cell below will provide a Configmap configuration for stage-in/out for [e-learning](#e-learning) profile." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='set -x \\n\\ncd /workspace\\n\\ngit clone \\'https://github.com/eoap/stac-eoap.git\\'\\n\\ncode-server --install-extension ms-python.python \\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nmkdir -p /workspace/User/\\n\\necho \\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\' > /workspace/User/settings.json\\n\\npython -m venv /workspace/.venv\\nsource /workspace/.venv/bin/activate\\n/workspace/.venv/bin/python -m pip install --no-cache-dir stactools rasterio requests stac-asset click-logging tabulate tqdm pystac-client ipykernel loguru scikit-image rio_stac boto3==1.35.23\\n\\n/workspace/.venv/bin/python -m pip install --index-url https://test.pypi.org/simple cwl-wrapper\\n\\n/workspace/.venv/bin/python -m ipykernel install --user --name stac_env --display-name \"Python (STAC)\"\\n\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\nexport AWS_ACCESS_KEY_ID=\"test\"\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\naws s3 mb s3://results --endpoint-url=http://localstack:4566\\n\\nexit 0', persist=False)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "init_stac_file_path = os.path.join(parent_dir, \"config-maps/init-stac.sh\")\n", + "with open(init_stac_file_path, \"r\") as f:\n", + " content = f.read()\n", + "\n", + "init_stac_cm = ConfigMap( # type: ignore\n", + " name=\"init\",\n", + " key=\"init\",\n", + " content=content,\n", + " readonly=True,\n", + " persist=False,\n", + " mount_path=\"/opt/init/.init.sh\",\n", + ")\n", + "init_stac_cm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Coder init Configmap\n", + "The cell below will provide a Configmap configuration for stage-in/out for [coder](#coder) profile." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "init_cm_file_path = os.path.join(parent_dir, \"config-maps/init.sh\") \n", + "with open(init_cm_file_path, \"r\") as f:\n", + " content = f.read()\n", + "\n", + "init_coder_cm = ConfigMap(\n", + " name=\"init\",\n", + " key=\"init\",\n", + " content=content,\n", + " readonly=True,\n", + " persist=False,\n", + " mount_path=\"/opt/init/.init.sh\",\n", + " default_mode=\"0660\",\n", + ")\n", + "init_coder_cm" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Profiles\n", + "In the following section, the user will generate the configuration for different profiles on a remote cluster including:\n", + "- [Coder](#coder)\n", + "- [Coder with init.sh](#coder-with-initsh)\n", + "- [Jupyter Lab](#jupyterlab)\n", + "- [JupyterLab Plus](#jupyterlab-plus)\n", + "- [E-learning](#e-learning)\n", + "- [QGIS](#qgis)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "profiles = []" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Coder\n", + "In the cell below, the user configures two code-server profiles to be deployed on a remote cluster. One profile (**medium**) has more resources than the other (**small**). Two Kubernetes Volumes, discussed in the [Volume section](#volumes), are assigned, and a ConfigMap is mounted. \n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "coders = {\n", + " \"coder1\": {\n", + " \"display_name\": \"Code Server Small\",\n", + " \"slug\": \"ellip_studio_coder_slug_s\",\n", + " \"cpu_limit\": 2,\n", + " \"mem_limit\": \"8G\",\n", + " },\n", + " \"coder2\": {\n", + " \"display_name\": \"Code Server Medium\",\n", + " \"slug\": \"ellip_studio_coder_slug_m\",\n", + " \"cpu_limit\": 4,\n", + " \"mem_limit\": \"12G\",\n", + " },\n", + "}\n", + "\n", + "for key, value in coders.items():\n", + " coder_definition = ProfileDefinition( # type: ignore\n", + " display_name=value[\"display_name\"],\n", + " slug=value[\"slug\"],\n", + " default=False,\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", + " cpu_limit=value[\"cpu_limit\"],\n", + " mem_limit=value[\"mem_limit\"],\n", + " image=\"eoepca/pde-code-server:develop\",\n", + " ),\n", + " )\n", + "\n", + " coder_profile = Profile( # type: ignore\n", + " id=f\"profile_studio_{key}\",\n", + " groups=[\"group-a\", \"group-b\"],\n", + " definition=coder_definition,\n", + " node_selector={},\n", + " volumes=[calrissian_volume, workspace_volume],\n", + " config_maps=[\n", + " bash_rc_cm,\n", + " ],\n", + " pod_env_vars={\n", + " \"HOME\": \"/workspace\",\n", + " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", + " },\n", + " )\n", + "\n", + " profiles.append(coder_profile)\n", + "profiles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Coder with init.sh\n", + "In the cell below, the user configures the code-server with a new profile, which extends the existing [coder](#coder) profile. The init container is responsible for executing a Bash script to initialize the code-server pod. In this case, the script [`init.sh`](../config-maps/init.sh) runs during pod initialization to clone a Git repository and install extensions on the deployed code-server." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "\n", + "init_context_volume_mount = InitContainerVolumeMount( # type: ignore\n", + " mount_path=\"/opt/init/.init.sh\", name=\"init\", sub_path=\"init\"\n", + ")\n", + "init_container = InitContainer( # type: ignore\n", + " name=\"init-file-on-volume\",\n", + " image=\"eoepca/pde-code-server:develop\",\n", + " command=[\"sh\", \"-c\", \"sh /opt/init/.init.sh\"],\n", + " volume_mounts=[\n", + " VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"), # type: ignore\n", + " init_context_volume_mount,\n", + " ],\n", + ")\n", + "\n", + "eoepca_demo_init_script_profile = Profile( # type: ignore\n", + " id=f\"profile_demo_init_script\",\n", + " groups=[\"group-a\", \"group-b\"],\n", + " definition=ProfileDefinition( # type: ignore\n", + " display_name=\"Coder demo init script\",\n", + " description=\"This profile is used to demonstrate the use of an init script\",\n", + " slug=\"eoepca_demo_init_script\",\n", + " default=False,\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", + " cpu_guarantee=1,\n", + " cpu_limit=2,\n", + " mem_guarantee=\"4G\",\n", + " mem_limit=\"6G\",\n", + " image=\"eoepca/pde-code-server:develop\",\n", + " ),\n", + " ),\n", + " node_selector={},\n", + " volumes=[calrissian_volume, workspace_volume],\n", + " config_maps=[init_coder_cm],\n", + " pod_env_vars={\n", + " \"HOME\": \"/workspace\",\n", + " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", + " \"CONDARC\": \"/workspace/.condarc\",\n", + " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", + " \"CODE_SERVER_WS\": \"/workspace/mastering-app-package\",\n", + " },\n", + " init_containers=[init_container],\n", + ")\n", + "profiles.append(eoepca_demo_init_script_profile)\n", + "profiles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### JupyterLab\n", + "\n", + "In the cell below, the user adds a [JupyterLab](https://github.com/jupyter/docker-stacks) profile to the stack of profiles. The [workspace volume](#workspace-volume) is mounted to persist user files, and necessary environment variables are set to ensure a smooth user experience." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "image = \"jupyter/scipy-notebook\"\n", + "\n", + "\n", + "eoepca_jupyter_lab_profile = Profile( # type: ignore\n", + " id=\"profile_jupyter_lab\",\n", + " groups=[\"group-c\"],\n", + " definition=ProfileDefinition( # type: ignore\n", + " display_name=\"Jupyter Lab\",\n", + " description=\"Jupyter Lab with Python 3.11\",\n", + " slug=\"eoepca_jupyter_lab\",\n", + " default=False,\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", + " cpu_guarantee=1,\n", + " cpu_limit=2,\n", + " mem_guarantee=\"4G\",\n", + " mem_limit=\"6G\",\n", + " image=image,\n", + " ),\n", + " ),\n", + " node_selector={},\n", + " volumes=[workspace_volume],\n", + " config_maps=[],\n", + " pod_env_vars={\n", + " \"HOME\": \"/workspace\",\n", + " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", + " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", + " },\n", + ")\n", + "\n", + "profiles.append(eoepca_jupyter_lab_profile)\n", + "profiles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### JupyterLab Plus \n", + "JupyterLab Plus is an extended version of the JupyterLab profile with additional features and capabilities. The Docker image for this profile is hosted on a private Docker registry, requiring the user to configure a pull secret to authenticate and successfully pull the JupyterLab Plus image on the remote cluster." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab_2', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', description='Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret', slug='eoepca_jupyter_lab_2', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "image_pull_secret = ImagePullSecret( # type: ignore\n", + " name=\"cr-config\",\n", + " persist=False,\n", + " data=\"ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9\",\n", + ")\n", + "image = \"cr.terradue.com/eoepca-plus/scipy-notebook@sha256:f339a9fa98d3d0c1fa8d7cc850e7f5a46845781f49bee86aacba059669d02d54\"\n", + "image = \"eoepca/iat-jupyterlab:develop\"\n", + "\n", + "eoepca_jupyter_lab_profile_2 = Profile( # type: ignore\n", + " id=\"profile_jupyter_lab_2\",\n", + " groups=[\"group-c\"],\n", + " definition=ProfileDefinition( # type: ignore\n", + " display_name=\"Jupyter Lab - profile 2\",\n", + " description=\"Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret\",\n", + " slug=\"eoepca_jupyter_lab_2\",\n", + " default=False,\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", + " cpu_guarantee=1,\n", + " cpu_limit=2,\n", + " mem_guarantee=\"4G\",\n", + " mem_limit=\"6G\",\n", + " image=image,\n", + " ),\n", + " ),\n", + " node_selector={},\n", + " volumes=[workspace_volume],\n", + " config_maps=[],\n", + " pod_env_vars={\n", + " \"HOME\": \"/workspace\",\n", + " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", + " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", + " },\n", + " image_pull_secrets=[image_pull_secret],\n", + ")\n", + "\n", + "profiles.append(eoepca_jupyter_lab_profile_2)\n", + "profiles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### E-Learning \n", + "This profile is an extended version of code-server, configured to initialize with an init container using [init.sh](../config-maps/init.sh). It also mounts several ConfigMaps, including [bash-rc](#bash-rc), [bash-login](#bash-login), and [stage-in/out](#stage-inout-configmap). Additionally, key Kubernetes objects such as Role, RoleBinding, and Service are defined using a Kubernetes [manifest](../manifests/manifest.yaml). This manifest ultimately exposes **LocalStack** as a Service, allowing users to emulate AWS locally." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab_2', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', description='Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret', slug='eoepca_jupyter_lab_2', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder_stac', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Understanding STAC for input/output data modelling', description='Understand the role of STAC in input/output data manifests in EO data processing workflows', slug='eoepca_coder_slug_stac', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='set -x \\n\\ncd /workspace\\n\\ngit clone \\'https://github.com/eoap/stac-eoap.git\\'\\n\\ncode-server --install-extension ms-python.python \\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nmkdir -p /workspace/User/\\n\\necho \\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\' > /workspace/User/settings.json\\n\\npython -m venv /workspace/.venv\\nsource /workspace/.venv/bin/activate\\n/workspace/.venv/bin/python -m pip install --no-cache-dir stactools rasterio requests stac-asset click-logging tabulate tqdm pystac-client ipykernel loguru scikit-image rio_stac boto3==1.35.23\\n\\n/workspace/.venv/bin/python -m pip install --index-url https://test.pypi.org/simple cwl-wrapper\\n\\n/workspace/.venv/bin/python -m ipykernel install --user --name stac_env --display-name \"Python (STAC)\"\\n\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\nexport AWS_ACCESS_KEY_ID=\"test\"\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\naws s3 mb s3://results --endpoint-url=http://localstack:4566\\n\\nexit 0', persist=False), ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.local', 'XDG_DATA_HOME': '/workspace/.local/share/', 'CWLTOOL_OPTIONS': '--podman', 'CODE_SERVER_WS': '/workspace/stac-eoap', 'AWS_DEFAULT_REGION': 'us-east-1', 'AWS_ACCESS_KEY_ID': 'test', 'AWS_SECRET_ACCESS_KEY': 'test'}, default_url=None, node_selector={}, role_bindings=[], image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=[Manifest(name='manifests', key='manifests', content=[{'apiVersion': 'v1', 'kind': 'ServiceAccount', 'metadata': {'name': 'localstack'}}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'Role', 'metadata': {'name': 'localstack'}, 'rules': [{'apiGroups': [''], 'resources': ['pods'], 'verbs': ['*']}, {'apiGroups': [''], 'resources': ['pods/log'], 'verbs': ['get']}, {'apiGroups': [''], 'resources': ['pods/exec'], 'verbs': ['get', 'create']}, {'apiGroups': [''], 'resources': ['services'], 'verbs': ['get', 'list']}]}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'RoleBinding', 'metadata': {'name': 'localstack'}, 'subjects': [{'kind': 'ServiceAccount', 'name': 'localstack'}], 'roleRef': {'kind': 'Role', 'name': 'localstack', 'apiGroup': 'rbac.authorization.k8s.io'}}, {'apiVersion': 'v1', 'kind': 'Service', 'metadata': {'name': 'localstack'}, 'spec': {'type': 'ClusterIP', 'ports': [{'name': 'edge', 'port': 4566, 'targetPort': 4566}, {'name': 'external-service-port-4510', 'port': 4510, 'targetPort': 'ext-svc-4510'}, {'name': 'external-service-port-4511', 'port': 4511, 'targetPort': 'ext-svc-4511'}, {'name': 'external-service-port-4512', 'port': 4512, 'targetPort': 'ext-svc-4512'}, {'name': 'external-service-port-4513', 'port': 4513, 'targetPort': 'ext-svc-4513'}, {'name': 'external-service-port-4514', 'port': 4514, 'targetPort': 'ext-svc-4514'}, {'name': 'external-service-port-4515', 'port': 4515, 'targetPort': 'ext-svc-4515'}, {'name': 'external-service-port-4516', 'port': 4516, 'targetPort': 'ext-svc-4516'}, {'name': 'external-service-port-4517', 'port': 4517, 'targetPort': 'ext-svc-4517'}, {'name': 'external-service-port-4518', 'port': 4518, 'targetPort': 'ext-svc-4518'}, {'name': 'external-service-port-4519', 'port': 4519, 'targetPort': 'ext-svc-4519'}, {'name': 'external-service-port-4520', 'port': 4520, 'targetPort': 'ext-svc-4520'}, {'name': 'external-service-port-4521', 'port': 4521, 'targetPort': 'ext-svc-4521'}, {'name': 'external-service-port-4522', 'port': 4522, 'targetPort': 'ext-svc-4522'}, {'name': 'external-service-port-4523', 'port': 4523, 'targetPort': 'ext-svc-4523'}, {'name': 'external-service-port-4524', 'port': 4524, 'targetPort': 'ext-svc-4524'}, {'name': 'external-service-port-4525', 'port': 4525, 'targetPort': 'ext-svc-4525'}, {'name': 'external-service-port-4526', 'port': 4526, 'targetPort': 'ext-svc-4526'}, {'name': 'external-service-port-4527', 'port': 4527, 'targetPort': 'ext-svc-4527'}, {'name': 'external-service-port-4528', 'port': 4528, 'targetPort': 'ext-svc-4528'}, {'name': 'external-service-port-4529', 'port': 4529, 'targetPort': 'ext-svc-4529'}, {'name': 'external-service-port-4530', 'port': 4530, 'targetPort': 'ext-svc-4530'}, {'name': 'external-service-port-4531', 'port': 4531, 'targetPort': 'ext-svc-4531'}, {'name': 'external-service-port-4532', 'port': 4532, 'targetPort': 'ext-svc-4532'}, {'name': 'external-service-port-4533', 'port': 4533, 'targetPort': 'ext-svc-4533'}, {'name': 'external-service-port-4534', 'port': 4534, 'targetPort': 'ext-svc-4534'}, {'name': 'external-service-port-4535', 'port': 4535, 'targetPort': 'ext-svc-4535'}, {'name': 'external-service-port-4536', 'port': 4536, 'targetPort': 'ext-svc-4536'}, {'name': 'external-service-port-4537', 'port': 4537, 'targetPort': 'ext-svc-4537'}, {'name': 'external-service-port-4538', 'port': 4538, 'targetPort': 'ext-svc-4538'}, {'name': 'external-service-port-4539', 'port': 4539, 'targetPort': 'ext-svc-4539'}, {'name': 'external-service-port-4540', 'port': 4540, 'targetPort': 'ext-svc-4540'}, {'name': 'external-service-port-4541', 'port': 4541, 'targetPort': 'ext-svc-4541'}, {'name': 'external-service-port-4542', 'port': 4542, 'targetPort': 'ext-svc-4542'}, {'name': 'external-service-port-4543', 'port': 4543, 'targetPort': 'ext-svc-4543'}, {'name': 'external-service-port-4544', 'port': 4544, 'targetPort': 'ext-svc-4544'}, {'name': 'external-service-port-4545', 'port': 4545, 'targetPort': 'ext-svc-4545'}, {'name': 'external-service-port-4546', 'port': 4546, 'targetPort': 'ext-svc-4546'}, {'name': 'external-service-port-4547', 'port': 4547, 'targetPort': 'ext-svc-4547'}, {'name': 'external-service-port-4548', 'port': 4548, 'targetPort': 'ext-svc-4548'}, {'name': 'external-service-port-4549', 'port': 4549, 'targetPort': 'ext-svc-4549'}, {'name': 'external-service-port-4550', 'port': 4550, 'targetPort': 'ext-svc-4550'}, {'name': 'external-service-port-4551', 'port': 4551, 'targetPort': 'ext-svc-4551'}, {'name': 'external-service-port-4552', 'port': 4552, 'targetPort': 'ext-svc-4552'}, {'name': 'external-service-port-4553', 'port': 4553, 'targetPort': 'ext-svc-4553'}, {'name': 'external-service-port-4554', 'port': 4554, 'targetPort': 'ext-svc-4554'}, {'name': 'external-service-port-4555', 'port': 4555, 'targetPort': 'ext-svc-4555'}, {'name': 'external-service-port-4556', 'port': 4556, 'targetPort': 'ext-svc-4556'}, {'name': 'external-service-port-4557', 'port': 4557, 'targetPort': 'ext-svc-4557'}, {'name': 'external-service-port-4558', 'port': 4558, 'targetPort': 'ext-svc-4558'}, {'name': 'external-service-port-4559', 'port': 4559, 'targetPort': 'ext-svc-4559'}], 'selector': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}}, {'apiVersion': 'apps/v1', 'kind': 'Deployment', 'metadata': {'name': 'localstack'}, 'spec': {'replicas': 1, 'strategy': {'type': 'RollingUpdate'}, 'selector': {'matchLabels': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'template': {'metadata': {'labels': {'app': 'localstack-{{ spawner.user.name }}', 'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'spec': {'serviceAccountName': 'localstack', 'securityContext': {}, 'containers': [{'name': 'localstack', 'securityContext': {}, 'image': 'localstack/localstack:latest', 'imagePullPolicy': 'IfNotPresent', 'ports': [{'name': 'edge', 'containerPort': 4566, 'protocol': 'TCP'}, {'name': 'ext-svc-4510', 'containerPort': 4510, 'protocol': 'TCP'}, {'name': 'ext-svc-4511', 'containerPort': 4511, 'protocol': 'TCP'}, {'name': 'ext-svc-4512', 'containerPort': 4512, 'protocol': 'TCP'}, {'name': 'ext-svc-4513', 'containerPort': 4513, 'protocol': 'TCP'}, {'name': 'ext-svc-4514', 'containerPort': 4514, 'protocol': 'TCP'}, {'name': 'ext-svc-4515', 'containerPort': 4515, 'protocol': 'TCP'}, {'name': 'ext-svc-4516', 'containerPort': 4516, 'protocol': 'TCP'}, {'name': 'ext-svc-4517', 'containerPort': 4517, 'protocol': 'TCP'}, {'name': 'ext-svc-4518', 'containerPort': 4518, 'protocol': 'TCP'}, {'name': 'ext-svc-4519', 'containerPort': 4519, 'protocol': 'TCP'}, {'name': 'ext-svc-4520', 'containerPort': 4520, 'protocol': 'TCP'}, {'name': 'ext-svc-4521', 'containerPort': 4521, 'protocol': 'TCP'}, {'name': 'ext-svc-4522', 'containerPort': 4522, 'protocol': 'TCP'}, {'name': 'ext-svc-4523', 'containerPort': 4523, 'protocol': 'TCP'}, {'name': 'ext-svc-4524', 'containerPort': 4524, 'protocol': 'TCP'}, {'name': 'ext-svc-4525', 'containerPort': 4525, 'protocol': 'TCP'}, {'name': 'ext-svc-4526', 'containerPort': 4526, 'protocol': 'TCP'}, {'name': 'ext-svc-4527', 'containerPort': 4527, 'protocol': 'TCP'}, {'name': 'ext-svc-4528', 'containerPort': 4528, 'protocol': 'TCP'}, {'name': 'ext-svc-4529', 'containerPort': 4529, 'protocol': 'TCP'}, {'name': 'ext-svc-4530', 'containerPort': 4530, 'protocol': 'TCP'}, {'name': 'ext-svc-4531', 'containerPort': 4531, 'protocol': 'TCP'}, {'name': 'ext-svc-4532', 'containerPort': 4532, 'protocol': 'TCP'}, {'name': 'ext-svc-4533', 'containerPort': 4533, 'protocol': 'TCP'}, {'name': 'ext-svc-4534', 'containerPort': 4534, 'protocol': 'TCP'}, {'name': 'ext-svc-4535', 'containerPort': 4535, 'protocol': 'TCP'}, {'name': 'ext-svc-4536', 'containerPort': 4536, 'protocol': 'TCP'}, {'name': 'ext-svc-4537', 'containerPort': 4537, 'protocol': 'TCP'}, {'name': 'ext-svc-4538', 'containerPort': 4538, 'protocol': 'TCP'}, {'name': 'ext-svc-4539', 'containerPort': 4539, 'protocol': 'TCP'}, {'name': 'ext-svc-4540', 'containerPort': 4540, 'protocol': 'TCP'}, {'name': 'ext-svc-4541', 'containerPort': 4541, 'protocol': 'TCP'}, {'name': 'ext-svc-4542', 'containerPort': 4542, 'protocol': 'TCP'}, {'name': 'ext-svc-4543', 'containerPort': 4543, 'protocol': 'TCP'}, {'name': 'ext-svc-4544', 'containerPort': 4544, 'protocol': 'TCP'}, {'name': 'ext-svc-4545', 'containerPort': 4545, 'protocol': 'TCP'}, {'name': 'ext-svc-4546', 'containerPort': 4546, 'protocol': 'TCP'}, {'name': 'ext-svc-4547', 'containerPort': 4547, 'protocol': 'TCP'}, {'name': 'ext-svc-4548', 'containerPort': 4548, 'protocol': 'TCP'}, {'name': 'ext-svc-4549', 'containerPort': 4549, 'protocol': 'TCP'}, {'name': 'ext-svc-4550', 'containerPort': 4550, 'protocol': 'TCP'}, {'name': 'ext-svc-4551', 'containerPort': 4551, 'protocol': 'TCP'}, {'name': 'ext-svc-4552', 'containerPort': 4552, 'protocol': 'TCP'}, {'name': 'ext-svc-4553', 'containerPort': 4553, 'protocol': 'TCP'}, {'name': 'ext-svc-4554', 'containerPort': 4554, 'protocol': 'TCP'}, {'name': 'ext-svc-4555', 'containerPort': 4555, 'protocol': 'TCP'}, {'name': 'ext-svc-4556', 'containerPort': 4556, 'protocol': 'TCP'}, {'name': 'ext-svc-4557', 'containerPort': 4557, 'protocol': 'TCP'}, {'name': 'ext-svc-4558', 'containerPort': 4558, 'protocol': 'TCP'}, {'name': 'ext-svc-4559', 'containerPort': 4559, 'protocol': 'TCP'}], 'livenessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'readinessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'resources': {}, 'env': [{'name': 'DEBUG', 'value': '0'}, {'name': 'EXTERNAL_SERVICE_PORTS_START', 'value': '4510'}, {'name': 'EXTERNAL_SERVICE_PORTS_END', 'value': '4560'}, {'name': 'LOCALSTACK_K8S_SERVICE_NAME', 'value': 'localstack'}, {'name': 'LOCALSTACK_K8S_NAMESPACE', 'valueFrom': {'fieldRef': {'fieldPath': 'metadata.namespace'}}}, {'name': 'LAMBDA_RUNTIME_EXECUTOR', 'value': 'docker'}, {'name': 'LAMBDA_K8S_IMAGE_PREFIX', 'value': 'localstack/lambda-'}, {'name': 'LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT', 'value': '60'}, {'name': 'OVERRIDE_IN_DOCKER', 'value': '1'}]}], 'volumes': []}}}}, {'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': 'my-config'}, 'data': {'ENV_VAR1': 'value1', 'ENV_VAR2': 'value2'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'my-secret'}, 'type': 'Opaque', 'data': {'SECRET_KEY1': 'dmFsdWUx', 'SECRET_KEY2': 'dmFsdWUy'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'aws-credentials-{{ spawner.user.name }}'}, 'type': 'Opaque', 'data': {'credentials': 'W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg='}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'data-by-name', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'data-by-name', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': 'secret-value', 'remoteRef': {'key': 'secret-one', 'property': 'the-key'}}]}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'eoepca-plus-secret-ro', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'eoepca-plus-secret-ro', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': '.dockerconfigjson', 'remoteRef': {'key': 'eoepca-plus-secret-ro', 'property': '.dockerconfigjson'}}]}}], persist=False)], env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "localstack_manifest_path = os.path.join(parent_dir, \"manifests/manifest.yaml\") \n", + "with open(localstack_manifest_path, \"r\") as f:\n", + " content = yaml.safe_load_all(f.read())\n", + "\n", + "\n", + "localstack_manifest = Manifest( # type: ignore\n", + " name=\"manifests\",\n", + " key=\"manifests\",\n", + " readonly=True,\n", + " persist=False,\n", + " content=[e for e in content],\n", + ")\n", + "\n", + "image = \"docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40\"\n", + "\n", + "coder_profile_stac = Profile( # type: ignore\n", + " id=f\"profile_studio_coder_stac\",\n", + " groups=[\"group-a\", \"group-b\"], \n", + " definition=ProfileDefinition( # type: ignore\n", + " display_name=\"Understanding STAC for input/output data modelling\",\n", + " description=\"Understand the role of STAC in input/output data manifests in EO data processing workflows\",\n", + " slug=\"eoepca_coder_slug_stac\",\n", + " default=False,\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", + " cpu_guarantee=1,\n", + " cpu_limit=2,\n", + " mem_guarantee=\"4G\",\n", + " mem_limit=\"6G\",\n", + " image=image,\n", + " ),\n", + " ),\n", + " node_selector={},\n", + " volumes=[workspace_volume],\n", + " config_maps=[init_stac_cm, bash_rc_cm, bash_login_cm],\n", + " pod_env_vars={\n", + " \"HOME\": \"/workspace\",\n", + " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", + " \"CONDARC\": \"/workspace/.condarc\",\n", + " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", + " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", + " \"XDG_CONFIG_HOME\": \"/workspace/.local\",\n", + " \"XDG_DATA_HOME\": \"/workspace/.local/share/\",\n", + " \"CWLTOOL_OPTIONS\": \"--podman\",\n", + " \"CODE_SERVER_WS\": \"/workspace/stac-eoap\",\n", + " \"AWS_DEFAULT_REGION\": \"us-east-1\",\n", + " \"AWS_ACCESS_KEY_ID\": \"test\",\n", + " \"AWS_SECRET_ACCESS_KEY\": \"test\",\n", + " },\n", + " role_bindings=[],\n", + " init_containers=[init_container],\n", + " image_pull_secrets=[image_pull_secret],\n", + " manifests=[localstack_manifest],\n", + ")\n", + "\n", + "profiles.append(coder_profile_stac)\n", + "profiles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### QGIS \n", + "This profile is configured to deploy the [QGIS](https://github.com/qgis/QGIS) application on the **App Hub**, enabling users to visualize and analyze Earth observation data directly within the platform. The deployment ensures that QGIS is configured with the necessary resources, storage, and dependencies to support geospatial analysis, map rendering, and data processing. By integrating QGIS into the App Hub, users can seamlessly interact with satellite imagery, vector layers, and raster data for advanced geospatial analysis." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab_2', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', description='Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret', slug='eoepca_jupyter_lab_2', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder_stac', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Understanding STAC for input/output data modelling', description='Understand the role of STAC in input/output data manifests in EO data processing workflows', slug='eoepca_coder_slug_stac', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='set -x \\n\\ncd /workspace\\n\\ngit clone \\'https://github.com/eoap/stac-eoap.git\\'\\n\\ncode-server --install-extension ms-python.python \\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nmkdir -p /workspace/User/\\n\\necho \\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\' > /workspace/User/settings.json\\n\\npython -m venv /workspace/.venv\\nsource /workspace/.venv/bin/activate\\n/workspace/.venv/bin/python -m pip install --no-cache-dir stactools rasterio requests stac-asset click-logging tabulate tqdm pystac-client ipykernel loguru scikit-image rio_stac boto3==1.35.23\\n\\n/workspace/.venv/bin/python -m pip install --index-url https://test.pypi.org/simple cwl-wrapper\\n\\n/workspace/.venv/bin/python -m ipykernel install --user --name stac_env --display-name \"Python (STAC)\"\\n\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\nexport AWS_ACCESS_KEY_ID=\"test\"\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\naws s3 mb s3://results --endpoint-url=http://localstack:4566\\n\\nexit 0', persist=False), ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.local', 'XDG_DATA_HOME': '/workspace/.local/share/', 'CWLTOOL_OPTIONS': '--podman', 'CODE_SERVER_WS': '/workspace/stac-eoap', 'AWS_DEFAULT_REGION': 'us-east-1', 'AWS_ACCESS_KEY_ID': 'test', 'AWS_SECRET_ACCESS_KEY': 'test'}, default_url=None, node_selector={}, role_bindings=[], image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=[Manifest(name='manifests', key='manifests', content=[{'apiVersion': 'v1', 'kind': 'ServiceAccount', 'metadata': {'name': 'localstack'}}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'Role', 'metadata': {'name': 'localstack'}, 'rules': [{'apiGroups': [''], 'resources': ['pods'], 'verbs': ['*']}, {'apiGroups': [''], 'resources': ['pods/log'], 'verbs': ['get']}, {'apiGroups': [''], 'resources': ['pods/exec'], 'verbs': ['get', 'create']}, {'apiGroups': [''], 'resources': ['services'], 'verbs': ['get', 'list']}]}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'RoleBinding', 'metadata': {'name': 'localstack'}, 'subjects': [{'kind': 'ServiceAccount', 'name': 'localstack'}], 'roleRef': {'kind': 'Role', 'name': 'localstack', 'apiGroup': 'rbac.authorization.k8s.io'}}, {'apiVersion': 'v1', 'kind': 'Service', 'metadata': {'name': 'localstack'}, 'spec': {'type': 'ClusterIP', 'ports': [{'name': 'edge', 'port': 4566, 'targetPort': 4566}, {'name': 'external-service-port-4510', 'port': 4510, 'targetPort': 'ext-svc-4510'}, {'name': 'external-service-port-4511', 'port': 4511, 'targetPort': 'ext-svc-4511'}, {'name': 'external-service-port-4512', 'port': 4512, 'targetPort': 'ext-svc-4512'}, {'name': 'external-service-port-4513', 'port': 4513, 'targetPort': 'ext-svc-4513'}, {'name': 'external-service-port-4514', 'port': 4514, 'targetPort': 'ext-svc-4514'}, {'name': 'external-service-port-4515', 'port': 4515, 'targetPort': 'ext-svc-4515'}, {'name': 'external-service-port-4516', 'port': 4516, 'targetPort': 'ext-svc-4516'}, {'name': 'external-service-port-4517', 'port': 4517, 'targetPort': 'ext-svc-4517'}, {'name': 'external-service-port-4518', 'port': 4518, 'targetPort': 'ext-svc-4518'}, {'name': 'external-service-port-4519', 'port': 4519, 'targetPort': 'ext-svc-4519'}, {'name': 'external-service-port-4520', 'port': 4520, 'targetPort': 'ext-svc-4520'}, {'name': 'external-service-port-4521', 'port': 4521, 'targetPort': 'ext-svc-4521'}, {'name': 'external-service-port-4522', 'port': 4522, 'targetPort': 'ext-svc-4522'}, {'name': 'external-service-port-4523', 'port': 4523, 'targetPort': 'ext-svc-4523'}, {'name': 'external-service-port-4524', 'port': 4524, 'targetPort': 'ext-svc-4524'}, {'name': 'external-service-port-4525', 'port': 4525, 'targetPort': 'ext-svc-4525'}, {'name': 'external-service-port-4526', 'port': 4526, 'targetPort': 'ext-svc-4526'}, {'name': 'external-service-port-4527', 'port': 4527, 'targetPort': 'ext-svc-4527'}, {'name': 'external-service-port-4528', 'port': 4528, 'targetPort': 'ext-svc-4528'}, {'name': 'external-service-port-4529', 'port': 4529, 'targetPort': 'ext-svc-4529'}, {'name': 'external-service-port-4530', 'port': 4530, 'targetPort': 'ext-svc-4530'}, {'name': 'external-service-port-4531', 'port': 4531, 'targetPort': 'ext-svc-4531'}, {'name': 'external-service-port-4532', 'port': 4532, 'targetPort': 'ext-svc-4532'}, {'name': 'external-service-port-4533', 'port': 4533, 'targetPort': 'ext-svc-4533'}, {'name': 'external-service-port-4534', 'port': 4534, 'targetPort': 'ext-svc-4534'}, {'name': 'external-service-port-4535', 'port': 4535, 'targetPort': 'ext-svc-4535'}, {'name': 'external-service-port-4536', 'port': 4536, 'targetPort': 'ext-svc-4536'}, {'name': 'external-service-port-4537', 'port': 4537, 'targetPort': 'ext-svc-4537'}, {'name': 'external-service-port-4538', 'port': 4538, 'targetPort': 'ext-svc-4538'}, {'name': 'external-service-port-4539', 'port': 4539, 'targetPort': 'ext-svc-4539'}, {'name': 'external-service-port-4540', 'port': 4540, 'targetPort': 'ext-svc-4540'}, {'name': 'external-service-port-4541', 'port': 4541, 'targetPort': 'ext-svc-4541'}, {'name': 'external-service-port-4542', 'port': 4542, 'targetPort': 'ext-svc-4542'}, {'name': 'external-service-port-4543', 'port': 4543, 'targetPort': 'ext-svc-4543'}, {'name': 'external-service-port-4544', 'port': 4544, 'targetPort': 'ext-svc-4544'}, {'name': 'external-service-port-4545', 'port': 4545, 'targetPort': 'ext-svc-4545'}, {'name': 'external-service-port-4546', 'port': 4546, 'targetPort': 'ext-svc-4546'}, {'name': 'external-service-port-4547', 'port': 4547, 'targetPort': 'ext-svc-4547'}, {'name': 'external-service-port-4548', 'port': 4548, 'targetPort': 'ext-svc-4548'}, {'name': 'external-service-port-4549', 'port': 4549, 'targetPort': 'ext-svc-4549'}, {'name': 'external-service-port-4550', 'port': 4550, 'targetPort': 'ext-svc-4550'}, {'name': 'external-service-port-4551', 'port': 4551, 'targetPort': 'ext-svc-4551'}, {'name': 'external-service-port-4552', 'port': 4552, 'targetPort': 'ext-svc-4552'}, {'name': 'external-service-port-4553', 'port': 4553, 'targetPort': 'ext-svc-4553'}, {'name': 'external-service-port-4554', 'port': 4554, 'targetPort': 'ext-svc-4554'}, {'name': 'external-service-port-4555', 'port': 4555, 'targetPort': 'ext-svc-4555'}, {'name': 'external-service-port-4556', 'port': 4556, 'targetPort': 'ext-svc-4556'}, {'name': 'external-service-port-4557', 'port': 4557, 'targetPort': 'ext-svc-4557'}, {'name': 'external-service-port-4558', 'port': 4558, 'targetPort': 'ext-svc-4558'}, {'name': 'external-service-port-4559', 'port': 4559, 'targetPort': 'ext-svc-4559'}], 'selector': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}}, {'apiVersion': 'apps/v1', 'kind': 'Deployment', 'metadata': {'name': 'localstack'}, 'spec': {'replicas': 1, 'strategy': {'type': 'RollingUpdate'}, 'selector': {'matchLabels': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'template': {'metadata': {'labels': {'app': 'localstack-{{ spawner.user.name }}', 'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'spec': {'serviceAccountName': 'localstack', 'securityContext': {}, 'containers': [{'name': 'localstack', 'securityContext': {}, 'image': 'localstack/localstack:latest', 'imagePullPolicy': 'IfNotPresent', 'ports': [{'name': 'edge', 'containerPort': 4566, 'protocol': 'TCP'}, {'name': 'ext-svc-4510', 'containerPort': 4510, 'protocol': 'TCP'}, {'name': 'ext-svc-4511', 'containerPort': 4511, 'protocol': 'TCP'}, {'name': 'ext-svc-4512', 'containerPort': 4512, 'protocol': 'TCP'}, {'name': 'ext-svc-4513', 'containerPort': 4513, 'protocol': 'TCP'}, {'name': 'ext-svc-4514', 'containerPort': 4514, 'protocol': 'TCP'}, {'name': 'ext-svc-4515', 'containerPort': 4515, 'protocol': 'TCP'}, {'name': 'ext-svc-4516', 'containerPort': 4516, 'protocol': 'TCP'}, {'name': 'ext-svc-4517', 'containerPort': 4517, 'protocol': 'TCP'}, {'name': 'ext-svc-4518', 'containerPort': 4518, 'protocol': 'TCP'}, {'name': 'ext-svc-4519', 'containerPort': 4519, 'protocol': 'TCP'}, {'name': 'ext-svc-4520', 'containerPort': 4520, 'protocol': 'TCP'}, {'name': 'ext-svc-4521', 'containerPort': 4521, 'protocol': 'TCP'}, {'name': 'ext-svc-4522', 'containerPort': 4522, 'protocol': 'TCP'}, {'name': 'ext-svc-4523', 'containerPort': 4523, 'protocol': 'TCP'}, {'name': 'ext-svc-4524', 'containerPort': 4524, 'protocol': 'TCP'}, {'name': 'ext-svc-4525', 'containerPort': 4525, 'protocol': 'TCP'}, {'name': 'ext-svc-4526', 'containerPort': 4526, 'protocol': 'TCP'}, {'name': 'ext-svc-4527', 'containerPort': 4527, 'protocol': 'TCP'}, {'name': 'ext-svc-4528', 'containerPort': 4528, 'protocol': 'TCP'}, {'name': 'ext-svc-4529', 'containerPort': 4529, 'protocol': 'TCP'}, {'name': 'ext-svc-4530', 'containerPort': 4530, 'protocol': 'TCP'}, {'name': 'ext-svc-4531', 'containerPort': 4531, 'protocol': 'TCP'}, {'name': 'ext-svc-4532', 'containerPort': 4532, 'protocol': 'TCP'}, {'name': 'ext-svc-4533', 'containerPort': 4533, 'protocol': 'TCP'}, {'name': 'ext-svc-4534', 'containerPort': 4534, 'protocol': 'TCP'}, {'name': 'ext-svc-4535', 'containerPort': 4535, 'protocol': 'TCP'}, {'name': 'ext-svc-4536', 'containerPort': 4536, 'protocol': 'TCP'}, {'name': 'ext-svc-4537', 'containerPort': 4537, 'protocol': 'TCP'}, {'name': 'ext-svc-4538', 'containerPort': 4538, 'protocol': 'TCP'}, {'name': 'ext-svc-4539', 'containerPort': 4539, 'protocol': 'TCP'}, {'name': 'ext-svc-4540', 'containerPort': 4540, 'protocol': 'TCP'}, {'name': 'ext-svc-4541', 'containerPort': 4541, 'protocol': 'TCP'}, {'name': 'ext-svc-4542', 'containerPort': 4542, 'protocol': 'TCP'}, {'name': 'ext-svc-4543', 'containerPort': 4543, 'protocol': 'TCP'}, {'name': 'ext-svc-4544', 'containerPort': 4544, 'protocol': 'TCP'}, {'name': 'ext-svc-4545', 'containerPort': 4545, 'protocol': 'TCP'}, {'name': 'ext-svc-4546', 'containerPort': 4546, 'protocol': 'TCP'}, {'name': 'ext-svc-4547', 'containerPort': 4547, 'protocol': 'TCP'}, {'name': 'ext-svc-4548', 'containerPort': 4548, 'protocol': 'TCP'}, {'name': 'ext-svc-4549', 'containerPort': 4549, 'protocol': 'TCP'}, {'name': 'ext-svc-4550', 'containerPort': 4550, 'protocol': 'TCP'}, {'name': 'ext-svc-4551', 'containerPort': 4551, 'protocol': 'TCP'}, {'name': 'ext-svc-4552', 'containerPort': 4552, 'protocol': 'TCP'}, {'name': 'ext-svc-4553', 'containerPort': 4553, 'protocol': 'TCP'}, {'name': 'ext-svc-4554', 'containerPort': 4554, 'protocol': 'TCP'}, {'name': 'ext-svc-4555', 'containerPort': 4555, 'protocol': 'TCP'}, {'name': 'ext-svc-4556', 'containerPort': 4556, 'protocol': 'TCP'}, {'name': 'ext-svc-4557', 'containerPort': 4557, 'protocol': 'TCP'}, {'name': 'ext-svc-4558', 'containerPort': 4558, 'protocol': 'TCP'}, {'name': 'ext-svc-4559', 'containerPort': 4559, 'protocol': 'TCP'}], 'livenessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'readinessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'resources': {}, 'env': [{'name': 'DEBUG', 'value': '0'}, {'name': 'EXTERNAL_SERVICE_PORTS_START', 'value': '4510'}, {'name': 'EXTERNAL_SERVICE_PORTS_END', 'value': '4560'}, {'name': 'LOCALSTACK_K8S_SERVICE_NAME', 'value': 'localstack'}, {'name': 'LOCALSTACK_K8S_NAMESPACE', 'valueFrom': {'fieldRef': {'fieldPath': 'metadata.namespace'}}}, {'name': 'LAMBDA_RUNTIME_EXECUTOR', 'value': 'docker'}, {'name': 'LAMBDA_K8S_IMAGE_PREFIX', 'value': 'localstack/lambda-'}, {'name': 'LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT', 'value': '60'}, {'name': 'OVERRIDE_IN_DOCKER', 'value': '1'}]}], 'volumes': []}}}}, {'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': 'my-config'}, 'data': {'ENV_VAR1': 'value1', 'ENV_VAR2': 'value2'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'my-secret'}, 'type': 'Opaque', 'data': {'SECRET_KEY1': 'dmFsdWUx', 'SECRET_KEY2': 'dmFsdWUy'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'aws-credentials-{{ spawner.user.name }}'}, 'type': 'Opaque', 'data': {'credentials': 'W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg='}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'data-by-name', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'data-by-name', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': 'secret-value', 'remoteRef': {'key': 'secret-one', 'property': 'the-key'}}]}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'eoepca-plus-secret-ro', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'eoepca-plus-secret-ro', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': '.dockerconfigjson', 'remoteRef': {'key': 'eoepca-plus-secret-ro', 'property': '.dockerconfigjson'}}]}}], persist=False)], env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_desktop_qgis', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='QGIS on a Remote Desktop', description='Spatial visualization and decision-making tools for everyone', slug='eoepca_desktop_qgis', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='2G', mem_guarantee=None, image='eoepca/iga-remote-desktop-qgis:1.1.3', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace'}, default_url='desktop', node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "image = \"eoepca/iga-remote-desktop-qgis:1.1.3\"\n", + "\n", + "qgis_profile = Profile( # type: ignore\n", + " id=\"profile_studio_desktop_qgis\",\n", + " groups=[\"group-a\", \"group-b\"],\n", + " definition=ProfileDefinition( # type: ignore\n", + " display_name=\"QGIS on a Remote Desktop\",\n", + " description=\"Spatial visualization and decision-making tools for everyone\",\n", + " slug=\"eoepca_desktop_qgis\",\n", + " default=False,\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", + " cpu_limit=2,\n", + " mem_limit=\"2G\",\n", + " image=image,\n", + " ),\n", + " ),\n", + " node_selector={},\n", + " volumes=[workspace_volume],\n", + " config_maps=[bash_rc_cm, bash_login_cm],\n", + " pod_env_vars={\"HOME\": \"/workspace\"},\n", + " default_url=\"desktop\",\n", + " init_containers=[],\n", + ")\n", + "\n", + "profiles.append(qgis_profile)\n", + "profiles" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Write Configuration \n", + "In the code below, the user inspects all the configurations made in the previous section and then generates a [config.yaml](../../files/hub/config.yml) file. This file contains the complete configuration of the **App Hub**, including the different profiles defined for deployment. The `config.yaml` serves as the configuration blueprint, allowing the user to easily apply and share the profiles across different environments or clusters." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab_2', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', description='Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret', slug='eoepca_jupyter_lab_2', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder_stac', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Understanding STAC for input/output data modelling', description='Understand the role of STAC in input/output data manifests in EO data processing workflows', slug='eoepca_coder_slug_stac', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='set -x \\n\\ncd /workspace\\n\\ngit clone \\'https://github.com/eoap/stac-eoap.git\\'\\n\\ncode-server --install-extension ms-python.python \\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nmkdir -p /workspace/User/\\n\\necho \\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\' > /workspace/User/settings.json\\n\\npython -m venv /workspace/.venv\\nsource /workspace/.venv/bin/activate\\n/workspace/.venv/bin/python -m pip install --no-cache-dir stactools rasterio requests stac-asset click-logging tabulate tqdm pystac-client ipykernel loguru scikit-image rio_stac boto3==1.35.23\\n\\n/workspace/.venv/bin/python -m pip install --index-url https://test.pypi.org/simple cwl-wrapper\\n\\n/workspace/.venv/bin/python -m ipykernel install --user --name stac_env --display-name \"Python (STAC)\"\\n\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\nexport AWS_ACCESS_KEY_ID=\"test\"\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\naws s3 mb s3://results --endpoint-url=http://localstack:4566\\n\\nexit 0', persist=False), ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.local', 'XDG_DATA_HOME': '/workspace/.local/share/', 'CWLTOOL_OPTIONS': '--podman', 'CODE_SERVER_WS': '/workspace/stac-eoap', 'AWS_DEFAULT_REGION': 'us-east-1', 'AWS_ACCESS_KEY_ID': 'test', 'AWS_SECRET_ACCESS_KEY': 'test'}, default_url=None, node_selector={}, role_bindings=[], image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=[Manifest(name='manifests', key='manifests', content=[{'apiVersion': 'v1', 'kind': 'ServiceAccount', 'metadata': {'name': 'localstack'}}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'Role', 'metadata': {'name': 'localstack'}, 'rules': [{'apiGroups': [''], 'resources': ['pods'], 'verbs': ['*']}, {'apiGroups': [''], 'resources': ['pods/log'], 'verbs': ['get']}, {'apiGroups': [''], 'resources': ['pods/exec'], 'verbs': ['get', 'create']}, {'apiGroups': [''], 'resources': ['services'], 'verbs': ['get', 'list']}]}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'RoleBinding', 'metadata': {'name': 'localstack'}, 'subjects': [{'kind': 'ServiceAccount', 'name': 'localstack'}], 'roleRef': {'kind': 'Role', 'name': 'localstack', 'apiGroup': 'rbac.authorization.k8s.io'}}, {'apiVersion': 'v1', 'kind': 'Service', 'metadata': {'name': 'localstack'}, 'spec': {'type': 'ClusterIP', 'ports': [{'name': 'edge', 'port': 4566, 'targetPort': 4566}, {'name': 'external-service-port-4510', 'port': 4510, 'targetPort': 'ext-svc-4510'}, {'name': 'external-service-port-4511', 'port': 4511, 'targetPort': 'ext-svc-4511'}, {'name': 'external-service-port-4512', 'port': 4512, 'targetPort': 'ext-svc-4512'}, {'name': 'external-service-port-4513', 'port': 4513, 'targetPort': 'ext-svc-4513'}, {'name': 'external-service-port-4514', 'port': 4514, 'targetPort': 'ext-svc-4514'}, {'name': 'external-service-port-4515', 'port': 4515, 'targetPort': 'ext-svc-4515'}, {'name': 'external-service-port-4516', 'port': 4516, 'targetPort': 'ext-svc-4516'}, {'name': 'external-service-port-4517', 'port': 4517, 'targetPort': 'ext-svc-4517'}, {'name': 'external-service-port-4518', 'port': 4518, 'targetPort': 'ext-svc-4518'}, {'name': 'external-service-port-4519', 'port': 4519, 'targetPort': 'ext-svc-4519'}, {'name': 'external-service-port-4520', 'port': 4520, 'targetPort': 'ext-svc-4520'}, {'name': 'external-service-port-4521', 'port': 4521, 'targetPort': 'ext-svc-4521'}, {'name': 'external-service-port-4522', 'port': 4522, 'targetPort': 'ext-svc-4522'}, {'name': 'external-service-port-4523', 'port': 4523, 'targetPort': 'ext-svc-4523'}, {'name': 'external-service-port-4524', 'port': 4524, 'targetPort': 'ext-svc-4524'}, {'name': 'external-service-port-4525', 'port': 4525, 'targetPort': 'ext-svc-4525'}, {'name': 'external-service-port-4526', 'port': 4526, 'targetPort': 'ext-svc-4526'}, {'name': 'external-service-port-4527', 'port': 4527, 'targetPort': 'ext-svc-4527'}, {'name': 'external-service-port-4528', 'port': 4528, 'targetPort': 'ext-svc-4528'}, {'name': 'external-service-port-4529', 'port': 4529, 'targetPort': 'ext-svc-4529'}, {'name': 'external-service-port-4530', 'port': 4530, 'targetPort': 'ext-svc-4530'}, {'name': 'external-service-port-4531', 'port': 4531, 'targetPort': 'ext-svc-4531'}, {'name': 'external-service-port-4532', 'port': 4532, 'targetPort': 'ext-svc-4532'}, {'name': 'external-service-port-4533', 'port': 4533, 'targetPort': 'ext-svc-4533'}, {'name': 'external-service-port-4534', 'port': 4534, 'targetPort': 'ext-svc-4534'}, {'name': 'external-service-port-4535', 'port': 4535, 'targetPort': 'ext-svc-4535'}, {'name': 'external-service-port-4536', 'port': 4536, 'targetPort': 'ext-svc-4536'}, {'name': 'external-service-port-4537', 'port': 4537, 'targetPort': 'ext-svc-4537'}, {'name': 'external-service-port-4538', 'port': 4538, 'targetPort': 'ext-svc-4538'}, {'name': 'external-service-port-4539', 'port': 4539, 'targetPort': 'ext-svc-4539'}, {'name': 'external-service-port-4540', 'port': 4540, 'targetPort': 'ext-svc-4540'}, {'name': 'external-service-port-4541', 'port': 4541, 'targetPort': 'ext-svc-4541'}, {'name': 'external-service-port-4542', 'port': 4542, 'targetPort': 'ext-svc-4542'}, {'name': 'external-service-port-4543', 'port': 4543, 'targetPort': 'ext-svc-4543'}, {'name': 'external-service-port-4544', 'port': 4544, 'targetPort': 'ext-svc-4544'}, {'name': 'external-service-port-4545', 'port': 4545, 'targetPort': 'ext-svc-4545'}, {'name': 'external-service-port-4546', 'port': 4546, 'targetPort': 'ext-svc-4546'}, {'name': 'external-service-port-4547', 'port': 4547, 'targetPort': 'ext-svc-4547'}, {'name': 'external-service-port-4548', 'port': 4548, 'targetPort': 'ext-svc-4548'}, {'name': 'external-service-port-4549', 'port': 4549, 'targetPort': 'ext-svc-4549'}, {'name': 'external-service-port-4550', 'port': 4550, 'targetPort': 'ext-svc-4550'}, {'name': 'external-service-port-4551', 'port': 4551, 'targetPort': 'ext-svc-4551'}, {'name': 'external-service-port-4552', 'port': 4552, 'targetPort': 'ext-svc-4552'}, {'name': 'external-service-port-4553', 'port': 4553, 'targetPort': 'ext-svc-4553'}, {'name': 'external-service-port-4554', 'port': 4554, 'targetPort': 'ext-svc-4554'}, {'name': 'external-service-port-4555', 'port': 4555, 'targetPort': 'ext-svc-4555'}, {'name': 'external-service-port-4556', 'port': 4556, 'targetPort': 'ext-svc-4556'}, {'name': 'external-service-port-4557', 'port': 4557, 'targetPort': 'ext-svc-4557'}, {'name': 'external-service-port-4558', 'port': 4558, 'targetPort': 'ext-svc-4558'}, {'name': 'external-service-port-4559', 'port': 4559, 'targetPort': 'ext-svc-4559'}], 'selector': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}}, {'apiVersion': 'apps/v1', 'kind': 'Deployment', 'metadata': {'name': 'localstack'}, 'spec': {'replicas': 1, 'strategy': {'type': 'RollingUpdate'}, 'selector': {'matchLabels': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'template': {'metadata': {'labels': {'app': 'localstack-{{ spawner.user.name }}', 'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'spec': {'serviceAccountName': 'localstack', 'securityContext': {}, 'containers': [{'name': 'localstack', 'securityContext': {}, 'image': 'localstack/localstack:latest', 'imagePullPolicy': 'IfNotPresent', 'ports': [{'name': 'edge', 'containerPort': 4566, 'protocol': 'TCP'}, {'name': 'ext-svc-4510', 'containerPort': 4510, 'protocol': 'TCP'}, {'name': 'ext-svc-4511', 'containerPort': 4511, 'protocol': 'TCP'}, {'name': 'ext-svc-4512', 'containerPort': 4512, 'protocol': 'TCP'}, {'name': 'ext-svc-4513', 'containerPort': 4513, 'protocol': 'TCP'}, {'name': 'ext-svc-4514', 'containerPort': 4514, 'protocol': 'TCP'}, {'name': 'ext-svc-4515', 'containerPort': 4515, 'protocol': 'TCP'}, {'name': 'ext-svc-4516', 'containerPort': 4516, 'protocol': 'TCP'}, {'name': 'ext-svc-4517', 'containerPort': 4517, 'protocol': 'TCP'}, {'name': 'ext-svc-4518', 'containerPort': 4518, 'protocol': 'TCP'}, {'name': 'ext-svc-4519', 'containerPort': 4519, 'protocol': 'TCP'}, {'name': 'ext-svc-4520', 'containerPort': 4520, 'protocol': 'TCP'}, {'name': 'ext-svc-4521', 'containerPort': 4521, 'protocol': 'TCP'}, {'name': 'ext-svc-4522', 'containerPort': 4522, 'protocol': 'TCP'}, {'name': 'ext-svc-4523', 'containerPort': 4523, 'protocol': 'TCP'}, {'name': 'ext-svc-4524', 'containerPort': 4524, 'protocol': 'TCP'}, {'name': 'ext-svc-4525', 'containerPort': 4525, 'protocol': 'TCP'}, {'name': 'ext-svc-4526', 'containerPort': 4526, 'protocol': 'TCP'}, {'name': 'ext-svc-4527', 'containerPort': 4527, 'protocol': 'TCP'}, {'name': 'ext-svc-4528', 'containerPort': 4528, 'protocol': 'TCP'}, {'name': 'ext-svc-4529', 'containerPort': 4529, 'protocol': 'TCP'}, {'name': 'ext-svc-4530', 'containerPort': 4530, 'protocol': 'TCP'}, {'name': 'ext-svc-4531', 'containerPort': 4531, 'protocol': 'TCP'}, {'name': 'ext-svc-4532', 'containerPort': 4532, 'protocol': 'TCP'}, {'name': 'ext-svc-4533', 'containerPort': 4533, 'protocol': 'TCP'}, {'name': 'ext-svc-4534', 'containerPort': 4534, 'protocol': 'TCP'}, {'name': 'ext-svc-4535', 'containerPort': 4535, 'protocol': 'TCP'}, {'name': 'ext-svc-4536', 'containerPort': 4536, 'protocol': 'TCP'}, {'name': 'ext-svc-4537', 'containerPort': 4537, 'protocol': 'TCP'}, {'name': 'ext-svc-4538', 'containerPort': 4538, 'protocol': 'TCP'}, {'name': 'ext-svc-4539', 'containerPort': 4539, 'protocol': 'TCP'}, {'name': 'ext-svc-4540', 'containerPort': 4540, 'protocol': 'TCP'}, {'name': 'ext-svc-4541', 'containerPort': 4541, 'protocol': 'TCP'}, {'name': 'ext-svc-4542', 'containerPort': 4542, 'protocol': 'TCP'}, {'name': 'ext-svc-4543', 'containerPort': 4543, 'protocol': 'TCP'}, {'name': 'ext-svc-4544', 'containerPort': 4544, 'protocol': 'TCP'}, {'name': 'ext-svc-4545', 'containerPort': 4545, 'protocol': 'TCP'}, {'name': 'ext-svc-4546', 'containerPort': 4546, 'protocol': 'TCP'}, {'name': 'ext-svc-4547', 'containerPort': 4547, 'protocol': 'TCP'}, {'name': 'ext-svc-4548', 'containerPort': 4548, 'protocol': 'TCP'}, {'name': 'ext-svc-4549', 'containerPort': 4549, 'protocol': 'TCP'}, {'name': 'ext-svc-4550', 'containerPort': 4550, 'protocol': 'TCP'}, {'name': 'ext-svc-4551', 'containerPort': 4551, 'protocol': 'TCP'}, {'name': 'ext-svc-4552', 'containerPort': 4552, 'protocol': 'TCP'}, {'name': 'ext-svc-4553', 'containerPort': 4553, 'protocol': 'TCP'}, {'name': 'ext-svc-4554', 'containerPort': 4554, 'protocol': 'TCP'}, {'name': 'ext-svc-4555', 'containerPort': 4555, 'protocol': 'TCP'}, {'name': 'ext-svc-4556', 'containerPort': 4556, 'protocol': 'TCP'}, {'name': 'ext-svc-4557', 'containerPort': 4557, 'protocol': 'TCP'}, {'name': 'ext-svc-4558', 'containerPort': 4558, 'protocol': 'TCP'}, {'name': 'ext-svc-4559', 'containerPort': 4559, 'protocol': 'TCP'}], 'livenessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'readinessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'resources': {}, 'env': [{'name': 'DEBUG', 'value': '0'}, {'name': 'EXTERNAL_SERVICE_PORTS_START', 'value': '4510'}, {'name': 'EXTERNAL_SERVICE_PORTS_END', 'value': '4560'}, {'name': 'LOCALSTACK_K8S_SERVICE_NAME', 'value': 'localstack'}, {'name': 'LOCALSTACK_K8S_NAMESPACE', 'valueFrom': {'fieldRef': {'fieldPath': 'metadata.namespace'}}}, {'name': 'LAMBDA_RUNTIME_EXECUTOR', 'value': 'docker'}, {'name': 'LAMBDA_K8S_IMAGE_PREFIX', 'value': 'localstack/lambda-'}, {'name': 'LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT', 'value': '60'}, {'name': 'OVERRIDE_IN_DOCKER', 'value': '1'}]}], 'volumes': []}}}}, {'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': 'my-config'}, 'data': {'ENV_VAR1': 'value1', 'ENV_VAR2': 'value2'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'my-secret'}, 'type': 'Opaque', 'data': {'SECRET_KEY1': 'dmFsdWUx', 'SECRET_KEY2': 'dmFsdWUy'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'aws-credentials-{{ spawner.user.name }}'}, 'type': 'Opaque', 'data': {'credentials': 'W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg='}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'data-by-name', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'data-by-name', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': 'secret-value', 'remoteRef': {'key': 'secret-one', 'property': 'the-key'}}]}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'eoepca-plus-secret-ro', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'eoepca-plus-secret-ro', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': '.dockerconfigjson', 'remoteRef': {'key': 'eoepca-plus-secret-ro', 'property': '.dockerconfigjson'}}]}}], persist=False)], env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_desktop_qgis', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='QGIS on a Remote Desktop', description='Spatial visualization and decision-making tools for everyone', slug='eoepca_desktop_qgis', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='2G', mem_guarantee=None, image='eoepca/iga-remote-desktop-qgis:1.1.3', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace'}, default_url='desktop', node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]\n", + "(\"config: profiles=[Profile(id='profile_studio_coder1', groups=['group-a', \"\n", + " \"'group-b'], definition=ProfileDefinition(display_name='Code Server Small', \"\n", + " \"description=None, slug='ellip_studio_coder_slug_s', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, '\n", + " \"mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', \"\n", + " 'extra_resource_limits={}, extra_resource_guarantees={})), '\n", + " \"config_maps=[ConfigMap(name='bash-rc', key='bash-rc', \"\n", + " \"mount_path='/workspace/.bashrc', default_mode=None, readonly=True, \"\n", + " 'content=\\'alias ll=\"ls -l\"\\\\nalias calrissian=\"/opt/conda/bin/calrissian '\n", + " '--pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout '\n", + " '/calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix '\n", + " '/calrissian/tmp/ --outdir /calrissian/\"\\\\nalias '\n", + " 'cwltool=\"/opt/conda/bin/cwltool --podman\"\\\\n. '\n", + " '/home/jovyan/.bashrc\\\\n\\\\n#alias aws=\"aws '\n", + " '--endpoint-url=http://localstack:4566\"\\\\n\\\\n# >>> conda initialize >>>\\\\n# '\n", + " \"!! Contents within this block are managed by \\\\'conda init\\\\' \"\n", + " '!!\\\\n__conda_setup=\"$(\\\\\\'/opt/conda/bin/conda\\\\\\' \\\\\\'shell.bash\\\\\\' '\n", + " '\\\\\\'hook\\\\\\' 2> /dev/null)\"\\\\nif [ $? -eq 0 ]; then\\\\n eval '\n", + " '\"$__conda_setup\"\\\\nelse\\\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; '\n", + " 'then\\\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\\\n else\\\\n '\n", + " 'export PATH=\"/srv/conda/bin:$PATH\"\\\\n fi\\\\nfi\\\\nunset '\n", + " '__conda_setup\\\\n\\\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\\\n '\n", + " '. \"/opt/conda/etc/profile.d/mamba.sh\"\\\\nfi\\\\n# <<< conda initialize '\n", + " '<<<\\\\n\\\\na={{spawner.user.name}}\\\\n\\\\nalias aws=\"aws '\n", + " '--endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"\\', '\n", + " \"persist=False)], volumes=[Volume(name='calrissian-volume', \"\n", + " \"claim_name='calrissian-claim', size='50Gi', \"\n", + " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], \"\n", + " \"volume_mount=VolumeMount(name='calrissian-volume', \"\n", + " \"mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', \"\n", + " \"claim_name='workspace-claim', size='50Gi', \"\n", + " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': \"\n", + " \"'/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, \"\n", + " 'image_pull_secrets=[], init_containers=[], manifests=None, '\n", + " 'env_from_config_maps=None, env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], \"\n", + " \"definition=ProfileDefinition(display_name='Code Server Medium', \"\n", + " \"description=None, slug='ellip_studio_coder_slug_m', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, '\n", + " \"mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', \"\n", + " 'extra_resource_limits={}, extra_resource_guarantees={})), '\n", + " \"config_maps=[ConfigMap(name='bash-rc', key='bash-rc', \"\n", + " \"mount_path='/workspace/.bashrc', default_mode=None, readonly=True, \"\n", + " 'content=\\'alias ll=\"ls -l\"\\\\nalias calrissian=\"/opt/conda/bin/calrissian '\n", + " '--pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout '\n", + " '/calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix '\n", + " '/calrissian/tmp/ --outdir /calrissian/\"\\\\nalias '\n", + " 'cwltool=\"/opt/conda/bin/cwltool --podman\"\\\\n. '\n", + " '/home/jovyan/.bashrc\\\\n\\\\n#alias aws=\"aws '\n", + " '--endpoint-url=http://localstack:4566\"\\\\n\\\\n# >>> conda initialize >>>\\\\n# '\n", + " \"!! Contents within this block are managed by \\\\'conda init\\\\' \"\n", + " '!!\\\\n__conda_setup=\"$(\\\\\\'/opt/conda/bin/conda\\\\\\' \\\\\\'shell.bash\\\\\\' '\n", + " '\\\\\\'hook\\\\\\' 2> /dev/null)\"\\\\nif [ $? -eq 0 ]; then\\\\n eval '\n", + " '\"$__conda_setup\"\\\\nelse\\\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; '\n", + " 'then\\\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\\\n else\\\\n '\n", + " 'export PATH=\"/srv/conda/bin:$PATH\"\\\\n fi\\\\nfi\\\\nunset '\n", + " '__conda_setup\\\\n\\\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\\\n '\n", + " '. \"/opt/conda/etc/profile.d/mamba.sh\"\\\\nfi\\\\n# <<< conda initialize '\n", + " '<<<\\\\n\\\\na={{spawner.user.name}}\\\\n\\\\nalias aws=\"aws '\n", + " '--endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"\\', '\n", + " \"persist=False)], volumes=[Volume(name='calrissian-volume', \"\n", + " \"claim_name='calrissian-claim', size='50Gi', \"\n", + " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], \"\n", + " \"volume_mount=VolumeMount(name='calrissian-volume', \"\n", + " \"mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', \"\n", + " \"claim_name='workspace-claim', size='50Gi', \"\n", + " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': \"\n", + " \"'/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, \"\n", + " 'image_pull_secrets=[], init_containers=[], manifests=None, '\n", + " 'env_from_config_maps=None, env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], \"\n", + " \"definition=ProfileDefinition(display_name='Coder demo init script', \"\n", + " \"description='This profile is used to demonstrate the use of an init script', \"\n", + " \"slug='eoepca_demo_init_script', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, '\n", + " \"mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', \"\n", + " 'extra_resource_limits={}, extra_resource_guarantees={})), '\n", + " \"config_maps=[ConfigMap(name='init', key='init', \"\n", + " \"mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, \"\n", + " 'content=\"set -x\\\\n\\\\ncd /workspace\\\\n\\\\ngit clone '\n", + " \"'https://github.com/eoap/mastering-app-package.git'\\\\n\\\\ncode-server \"\n", + " '--install-extension ms-python.python\\\\ncode-server --install-extension '\n", + " 'redhat.vscode-yaml\\\\ncode-server --install-extension '\n", + " 'sbg-rabix.benten-cwl\\\\ncode-server --install-extension '\n", + " 'ms-toolsai.jupyter\\\\n\\\\nln -s /workspace/.local/share/code-server/extensions '\n", + " '/workspace/extensions\\\\n\\\\nexit 0\\\\n\", persist=False)], '\n", + " \"volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', \"\n", + " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", + " \"access_modes=['ReadWriteMany'], \"\n", + " \"volume_mount=VolumeMount(name='calrissian-volume', \"\n", + " \"mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', \"\n", + " \"claim_name='workspace-claim', size='50Gi', \"\n", + " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': \"\n", + " \"'/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': \"\n", + " \"'/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, \"\n", + " 'default_url=None, node_selector={}, role_bindings=None, '\n", + " 'image_pull_secrets=[], '\n", + " \"init_containers=[InitContainer(name='init-file-on-volume', \"\n", + " \"image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh \"\n", + " \"/opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', \"\n", + " \"mount_path='/workspace'), InitContainerVolumeMount(name='init', \"\n", + " \"mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, \"\n", + " 'env_from_config_maps=None, env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_jupyter_lab', groups=['group-c'], \"\n", + " \"definition=ProfileDefinition(display_name='Jupyter Lab', \"\n", + " \"description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', \"\n", + " 'default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, '\n", + " \"cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', \"\n", + " \"image='jupyter/scipy-notebook', extra_resource_limits={}, \"\n", + " 'extra_resource_guarantees={})), config_maps=[], '\n", + " \"volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', \"\n", + " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", + " \"access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': \"\n", + " \"'/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, \"\n", + " 'default_url=None, node_selector={}, role_bindings=None, '\n", + " 'image_pull_secrets=[], init_containers=[], manifests=None, '\n", + " 'env_from_config_maps=None, env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_jupyter_lab_2', groups=['group-c'], \"\n", + " \"definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', \"\n", + " \"description='Jupyter Lab with Python 3.11 private image - demoes the use of \"\n", + " \"an image pull secret', slug='eoepca_jupyter_lab_2', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, '\n", + " \"mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', \"\n", + " 'extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], '\n", + " \"volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', \"\n", + " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", + " \"access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': \"\n", + " \"'/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, \"\n", + " 'default_url=None, node_selector={}, role_bindings=None, '\n", + " \"image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, \"\n", + " \"data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], \"\n", + " 'init_containers=[], manifests=None, env_from_config_maps=None, '\n", + " 'env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_studio_coder_stac', groups=['group-a', 'group-b'], \"\n", + " \"definition=ProfileDefinition(display_name='Understanding STAC for \"\n", + " \"input/output data modelling', description='Understand the role of STAC in \"\n", + " \"input/output data manifests in EO data processing workflows', \"\n", + " \"slug='eoepca_coder_slug_stac', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, '\n", + " \"mem_limit='6G', mem_guarantee='4G', \"\n", + " \"image='docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40', \"\n", + " 'extra_resource_limits={}, extra_resource_guarantees={})), '\n", + " \"config_maps=[ConfigMap(name='init', key='init', \"\n", + " \"mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, \"\n", + " \"content='set -x \\\\n\\\\ncd /workspace\\\\n\\\\ngit clone \"\n", + " \"\\\\'https://github.com/eoap/stac-eoap.git\\\\'\\\\n\\\\ncode-server \"\n", + " '--install-extension ms-python.python \\\\ncode-server --install-extension '\n", + " 'redhat.vscode-yaml\\\\ncode-server --install-extension '\n", + " 'sbg-rabix.benten-cwl\\\\ncode-server --install-extension '\n", + " 'ms-toolsai.jupyter\\\\n\\\\nln -s /workspace/.local/share/code-server/extensions '\n", + " '/workspace/extensions\\\\n\\\\nmkdir -p /workspace/User/\\\\n\\\\necho '\n", + " '\\\\\\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\\\\\' > '\n", + " '/workspace/User/settings.json\\\\n\\\\npython -m venv /workspace/.venv\\\\nsource '\n", + " '/workspace/.venv/bin/activate\\\\n/workspace/.venv/bin/python -m pip install '\n", + " '--no-cache-dir stactools rasterio requests stac-asset click-logging tabulate '\n", + " 'tqdm pystac-client ipykernel loguru scikit-image rio_stac '\n", + " 'boto3==1.35.23\\\\n\\\\n/workspace/.venv/bin/python -m pip install --index-url '\n", + " 'https://test.pypi.org/simple cwl-wrapper\\\\n\\\\n/workspace/.venv/bin/python -m '\n", + " 'ipykernel install --user --name stac_env --display-name \"Python '\n", + " '(STAC)\"\\\\n\\\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\\\nexport '\n", + " 'AWS_ACCESS_KEY_ID=\"test\"\\\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\\\naws s3 mb '\n", + " \"s3://results --endpoint-url=http://localstack:4566\\\\n\\\\nexit 0', \"\n", + " \"persist=False), ConfigMap(name='bash-rc', key='bash-rc', \"\n", + " \"mount_path='/workspace/.bashrc', default_mode=None, readonly=True, \"\n", + " 'content=\\'alias ll=\"ls -l\"\\\\nalias calrissian=\"/opt/conda/bin/calrissian '\n", + " '--pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout '\n", + " '/calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix '\n", + " '/calrissian/tmp/ --outdir /calrissian/\"\\\\nalias '\n", + " 'cwltool=\"/opt/conda/bin/cwltool --podman\"\\\\n. '\n", + " '/home/jovyan/.bashrc\\\\n\\\\n#alias aws=\"aws '\n", + " '--endpoint-url=http://localstack:4566\"\\\\n\\\\n# >>> conda initialize >>>\\\\n# '\n", + " \"!! Contents within this block are managed by \\\\'conda init\\\\' \"\n", + " '!!\\\\n__conda_setup=\"$(\\\\\\'/opt/conda/bin/conda\\\\\\' \\\\\\'shell.bash\\\\\\' '\n", + " '\\\\\\'hook\\\\\\' 2> /dev/null)\"\\\\nif [ $? -eq 0 ]; then\\\\n eval '\n", + " '\"$__conda_setup\"\\\\nelse\\\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; '\n", + " 'then\\\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\\\n else\\\\n '\n", + " 'export PATH=\"/srv/conda/bin:$PATH\"\\\\n fi\\\\nfi\\\\nunset '\n", + " '__conda_setup\\\\n\\\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\\\n '\n", + " '. \"/opt/conda/etc/profile.d/mamba.sh\"\\\\nfi\\\\n# <<< conda initialize '\n", + " '<<<\\\\n\\\\na={{spawner.user.name}}\\\\n\\\\nalias aws=\"aws '\n", + " '--endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"\\', '\n", + " \"persist=False), ConfigMap(name='bash-login', key='bash-login', \"\n", + " \"mount_path='/workspace/.bash_login', default_mode=None, readonly=True, \"\n", + " \"content='source /workspace/.bashrc\\\\n', persist=False)], \"\n", + " \"volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', \"\n", + " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", + " \"access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': \"\n", + " \"'/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': \"\n", + " \"'/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.local', \"\n", + " \"'XDG_DATA_HOME': '/workspace/.local/share/', 'CWLTOOL_OPTIONS': '--podman', \"\n", + " \"'CODE_SERVER_WS': '/workspace/stac-eoap', 'AWS_DEFAULT_REGION': 'us-east-1', \"\n", + " \"'AWS_ACCESS_KEY_ID': 'test', 'AWS_SECRET_ACCESS_KEY': 'test'}, \"\n", + " 'default_url=None, node_selector={}, role_bindings=[], '\n", + " \"image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, \"\n", + " \"data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], \"\n", + " \"init_containers=[InitContainer(name='init-file-on-volume', \"\n", + " \"image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh \"\n", + " \"/opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', \"\n", + " \"mount_path='/workspace'), InitContainerVolumeMount(name='init', \"\n", + " \"mount_path='/opt/init/.init.sh', sub_path='init')])], \"\n", + " \"manifests=[Manifest(name='manifests', key='manifests', \"\n", + " \"content=[{'apiVersion': 'v1', 'kind': 'ServiceAccount', 'metadata': {'name': \"\n", + " \"'localstack'}}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': \"\n", + " \"'Role', 'metadata': {'name': 'localstack'}, 'rules': [{'apiGroups': [''], \"\n", + " \"'resources': ['pods'], 'verbs': ['*']}, {'apiGroups': [''], 'resources': \"\n", + " \"['pods/log'], 'verbs': ['get']}, {'apiGroups': [''], 'resources': \"\n", + " \"['pods/exec'], 'verbs': ['get', 'create']}, {'apiGroups': [''], 'resources': \"\n", + " \"['services'], 'verbs': ['get', 'list']}]}, {'apiVersion': \"\n", + " \"'rbac.authorization.k8s.io/v1', 'kind': 'RoleBinding', 'metadata': {'name': \"\n", + " \"'localstack'}, 'subjects': [{'kind': 'ServiceAccount', 'name': \"\n", + " \"'localstack'}], 'roleRef': {'kind': 'Role', 'name': 'localstack', \"\n", + " \"'apiGroup': 'rbac.authorization.k8s.io'}}, {'apiVersion': 'v1', 'kind': \"\n", + " \"'Service', 'metadata': {'name': 'localstack'}, 'spec': {'type': 'ClusterIP', \"\n", + " \"'ports': [{'name': 'edge', 'port': 4566, 'targetPort': 4566}, {'name': \"\n", + " \"'external-service-port-4510', 'port': 4510, 'targetPort': 'ext-svc-4510'}, \"\n", + " \"{'name': 'external-service-port-4511', 'port': 4511, 'targetPort': \"\n", + " \"'ext-svc-4511'}, {'name': 'external-service-port-4512', 'port': 4512, \"\n", + " \"'targetPort': 'ext-svc-4512'}, {'name': 'external-service-port-4513', \"\n", + " \"'port': 4513, 'targetPort': 'ext-svc-4513'}, {'name': \"\n", + " \"'external-service-port-4514', 'port': 4514, 'targetPort': 'ext-svc-4514'}, \"\n", + " \"{'name': 'external-service-port-4515', 'port': 4515, 'targetPort': \"\n", + " \"'ext-svc-4515'}, {'name': 'external-service-port-4516', 'port': 4516, \"\n", + " \"'targetPort': 'ext-svc-4516'}, {'name': 'external-service-port-4517', \"\n", + " \"'port': 4517, 'targetPort': 'ext-svc-4517'}, {'name': \"\n", + " \"'external-service-port-4518', 'port': 4518, 'targetPort': 'ext-svc-4518'}, \"\n", + " \"{'name': 'external-service-port-4519', 'port': 4519, 'targetPort': \"\n", + " \"'ext-svc-4519'}, {'name': 'external-service-port-4520', 'port': 4520, \"\n", + " \"'targetPort': 'ext-svc-4520'}, {'name': 'external-service-port-4521', \"\n", + " \"'port': 4521, 'targetPort': 'ext-svc-4521'}, {'name': \"\n", + " \"'external-service-port-4522', 'port': 4522, 'targetPort': 'ext-svc-4522'}, \"\n", + " \"{'name': 'external-service-port-4523', 'port': 4523, 'targetPort': \"\n", + " \"'ext-svc-4523'}, {'name': 'external-service-port-4524', 'port': 4524, \"\n", + " \"'targetPort': 'ext-svc-4524'}, {'name': 'external-service-port-4525', \"\n", + " \"'port': 4525, 'targetPort': 'ext-svc-4525'}, {'name': \"\n", + " \"'external-service-port-4526', 'port': 4526, 'targetPort': 'ext-svc-4526'}, \"\n", + " \"{'name': 'external-service-port-4527', 'port': 4527, 'targetPort': \"\n", + " \"'ext-svc-4527'}, {'name': 'external-service-port-4528', 'port': 4528, \"\n", + " \"'targetPort': 'ext-svc-4528'}, {'name': 'external-service-port-4529', \"\n", + " \"'port': 4529, 'targetPort': 'ext-svc-4529'}, {'name': \"\n", + " \"'external-service-port-4530', 'port': 4530, 'targetPort': 'ext-svc-4530'}, \"\n", + " \"{'name': 'external-service-port-4531', 'port': 4531, 'targetPort': \"\n", + " \"'ext-svc-4531'}, {'name': 'external-service-port-4532', 'port': 4532, \"\n", + " \"'targetPort': 'ext-svc-4532'}, {'name': 'external-service-port-4533', \"\n", + " \"'port': 4533, 'targetPort': 'ext-svc-4533'}, {'name': \"\n", + " \"'external-service-port-4534', 'port': 4534, 'targetPort': 'ext-svc-4534'}, \"\n", + " \"{'name': 'external-service-port-4535', 'port': 4535, 'targetPort': \"\n", + " \"'ext-svc-4535'}, {'name': 'external-service-port-4536', 'port': 4536, \"\n", + " \"'targetPort': 'ext-svc-4536'}, {'name': 'external-service-port-4537', \"\n", + " \"'port': 4537, 'targetPort': 'ext-svc-4537'}, {'name': \"\n", + " \"'external-service-port-4538', 'port': 4538, 'targetPort': 'ext-svc-4538'}, \"\n", + " \"{'name': 'external-service-port-4539', 'port': 4539, 'targetPort': \"\n", + " \"'ext-svc-4539'}, {'name': 'external-service-port-4540', 'port': 4540, \"\n", + " \"'targetPort': 'ext-svc-4540'}, {'name': 'external-service-port-4541', \"\n", + " \"'port': 4541, 'targetPort': 'ext-svc-4541'}, {'name': \"\n", + " \"'external-service-port-4542', 'port': 4542, 'targetPort': 'ext-svc-4542'}, \"\n", + " \"{'name': 'external-service-port-4543', 'port': 4543, 'targetPort': \"\n", + " \"'ext-svc-4543'}, {'name': 'external-service-port-4544', 'port': 4544, \"\n", + " \"'targetPort': 'ext-svc-4544'}, {'name': 'external-service-port-4545', \"\n", + " \"'port': 4545, 'targetPort': 'ext-svc-4545'}, {'name': \"\n", + " \"'external-service-port-4546', 'port': 4546, 'targetPort': 'ext-svc-4546'}, \"\n", + " \"{'name': 'external-service-port-4547', 'port': 4547, 'targetPort': \"\n", + " \"'ext-svc-4547'}, {'name': 'external-service-port-4548', 'port': 4548, \"\n", + " \"'targetPort': 'ext-svc-4548'}, {'name': 'external-service-port-4549', \"\n", + " \"'port': 4549, 'targetPort': 'ext-svc-4549'}, {'name': \"\n", + " \"'external-service-port-4550', 'port': 4550, 'targetPort': 'ext-svc-4550'}, \"\n", + " \"{'name': 'external-service-port-4551', 'port': 4551, 'targetPort': \"\n", + " \"'ext-svc-4551'}, {'name': 'external-service-port-4552', 'port': 4552, \"\n", + " \"'targetPort': 'ext-svc-4552'}, {'name': 'external-service-port-4553', \"\n", + " \"'port': 4553, 'targetPort': 'ext-svc-4553'}, {'name': \"\n", + " \"'external-service-port-4554', 'port': 4554, 'targetPort': 'ext-svc-4554'}, \"\n", + " \"{'name': 'external-service-port-4555', 'port': 4555, 'targetPort': \"\n", + " \"'ext-svc-4555'}, {'name': 'external-service-port-4556', 'port': 4556, \"\n", + " \"'targetPort': 'ext-svc-4556'}, {'name': 'external-service-port-4557', \"\n", + " \"'port': 4557, 'targetPort': 'ext-svc-4557'}, {'name': \"\n", + " \"'external-service-port-4558', 'port': 4558, 'targetPort': 'ext-svc-4558'}, \"\n", + " \"{'name': 'external-service-port-4559', 'port': 4559, 'targetPort': \"\n", + " \"'ext-svc-4559'}], 'selector': {'app.kubernetes.io/name': 'localstack', \"\n", + " \"'app.kubernetes.io/instance': 'localstack'}}}, {'apiVersion': 'apps/v1', \"\n", + " \"'kind': 'Deployment', 'metadata': {'name': 'localstack'}, 'spec': \"\n", + " \"{'replicas': 1, 'strategy': {'type': 'RollingUpdate'}, 'selector': \"\n", + " \"{'matchLabels': {'app.kubernetes.io/name': 'localstack', \"\n", + " \"'app.kubernetes.io/instance': 'localstack'}}, 'template': {'metadata': \"\n", + " \"{'labels': {'app': 'localstack-{{ spawner.user.name }}', \"\n", + " \"'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': \"\n", + " \"'localstack'}}, 'spec': {'serviceAccountName': 'localstack', \"\n", + " \"'securityContext': {}, 'containers': [{'name': 'localstack', \"\n", + " \"'securityContext': {}, 'image': 'localstack/localstack:latest', \"\n", + " \"'imagePullPolicy': 'IfNotPresent', 'ports': [{'name': 'edge', \"\n", + " \"'containerPort': 4566, 'protocol': 'TCP'}, {'name': 'ext-svc-4510', \"\n", + " \"'containerPort': 4510, 'protocol': 'TCP'}, {'name': 'ext-svc-4511', \"\n", + " \"'containerPort': 4511, 'protocol': 'TCP'}, {'name': 'ext-svc-4512', \"\n", + " \"'containerPort': 4512, 'protocol': 'TCP'}, {'name': 'ext-svc-4513', \"\n", + " \"'containerPort': 4513, 'protocol': 'TCP'}, {'name': 'ext-svc-4514', \"\n", + " \"'containerPort': 4514, 'protocol': 'TCP'}, {'name': 'ext-svc-4515', \"\n", + " \"'containerPort': 4515, 'protocol': 'TCP'}, {'name': 'ext-svc-4516', \"\n", + " \"'containerPort': 4516, 'protocol': 'TCP'}, {'name': 'ext-svc-4517', \"\n", + " \"'containerPort': 4517, 'protocol': 'TCP'}, {'name': 'ext-svc-4518', \"\n", + " \"'containerPort': 4518, 'protocol': 'TCP'}, {'name': 'ext-svc-4519', \"\n", + " \"'containerPort': 4519, 'protocol': 'TCP'}, {'name': 'ext-svc-4520', \"\n", + " \"'containerPort': 4520, 'protocol': 'TCP'}, {'name': 'ext-svc-4521', \"\n", + " \"'containerPort': 4521, 'protocol': 'TCP'}, {'name': 'ext-svc-4522', \"\n", + " \"'containerPort': 4522, 'protocol': 'TCP'}, {'name': 'ext-svc-4523', \"\n", + " \"'containerPort': 4523, 'protocol': 'TCP'}, {'name': 'ext-svc-4524', \"\n", + " \"'containerPort': 4524, 'protocol': 'TCP'}, {'name': 'ext-svc-4525', \"\n", + " \"'containerPort': 4525, 'protocol': 'TCP'}, {'name': 'ext-svc-4526', \"\n", + " \"'containerPort': 4526, 'protocol': 'TCP'}, {'name': 'ext-svc-4527', \"\n", + " \"'containerPort': 4527, 'protocol': 'TCP'}, {'name': 'ext-svc-4528', \"\n", + " \"'containerPort': 4528, 'protocol': 'TCP'}, {'name': 'ext-svc-4529', \"\n", + " \"'containerPort': 4529, 'protocol': 'TCP'}, {'name': 'ext-svc-4530', \"\n", + " \"'containerPort': 4530, 'protocol': 'TCP'}, {'name': 'ext-svc-4531', \"\n", + " \"'containerPort': 4531, 'protocol': 'TCP'}, {'name': 'ext-svc-4532', \"\n", + " \"'containerPort': 4532, 'protocol': 'TCP'}, {'name': 'ext-svc-4533', \"\n", + " \"'containerPort': 4533, 'protocol': 'TCP'}, {'name': 'ext-svc-4534', \"\n", + " \"'containerPort': 4534, 'protocol': 'TCP'}, {'name': 'ext-svc-4535', \"\n", + " \"'containerPort': 4535, 'protocol': 'TCP'}, {'name': 'ext-svc-4536', \"\n", + " \"'containerPort': 4536, 'protocol': 'TCP'}, {'name': 'ext-svc-4537', \"\n", + " \"'containerPort': 4537, 'protocol': 'TCP'}, {'name': 'ext-svc-4538', \"\n", + " \"'containerPort': 4538, 'protocol': 'TCP'}, {'name': 'ext-svc-4539', \"\n", + " \"'containerPort': 4539, 'protocol': 'TCP'}, {'name': 'ext-svc-4540', \"\n", + " \"'containerPort': 4540, 'protocol': 'TCP'}, {'name': 'ext-svc-4541', \"\n", + " \"'containerPort': 4541, 'protocol': 'TCP'}, {'name': 'ext-svc-4542', \"\n", + " \"'containerPort': 4542, 'protocol': 'TCP'}, {'name': 'ext-svc-4543', \"\n", + " \"'containerPort': 4543, 'protocol': 'TCP'}, {'name': 'ext-svc-4544', \"\n", + " \"'containerPort': 4544, 'protocol': 'TCP'}, {'name': 'ext-svc-4545', \"\n", + " \"'containerPort': 4545, 'protocol': 'TCP'}, {'name': 'ext-svc-4546', \"\n", + " \"'containerPort': 4546, 'protocol': 'TCP'}, {'name': 'ext-svc-4547', \"\n", + " \"'containerPort': 4547, 'protocol': 'TCP'}, {'name': 'ext-svc-4548', \"\n", + " \"'containerPort': 4548, 'protocol': 'TCP'}, {'name': 'ext-svc-4549', \"\n", + " \"'containerPort': 4549, 'protocol': 'TCP'}, {'name': 'ext-svc-4550', \"\n", + " \"'containerPort': 4550, 'protocol': 'TCP'}, {'name': 'ext-svc-4551', \"\n", + " \"'containerPort': 4551, 'protocol': 'TCP'}, {'name': 'ext-svc-4552', \"\n", + " \"'containerPort': 4552, 'protocol': 'TCP'}, {'name': 'ext-svc-4553', \"\n", + " \"'containerPort': 4553, 'protocol': 'TCP'}, {'name': 'ext-svc-4554', \"\n", + " \"'containerPort': 4554, 'protocol': 'TCP'}, {'name': 'ext-svc-4555', \"\n", + " \"'containerPort': 4555, 'protocol': 'TCP'}, {'name': 'ext-svc-4556', \"\n", + " \"'containerPort': 4556, 'protocol': 'TCP'}, {'name': 'ext-svc-4557', \"\n", + " \"'containerPort': 4557, 'protocol': 'TCP'}, {'name': 'ext-svc-4558', \"\n", + " \"'containerPort': 4558, 'protocol': 'TCP'}, {'name': 'ext-svc-4559', \"\n", + " \"'containerPort': 4559, 'protocol': 'TCP'}], 'livenessProbe': \"\n", + " \"{'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, \"\n", + " \"'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': \"\n", + " \"'/_localstack/health', 'port': 'edge'}}, 'readinessProbe': \"\n", + " \"{'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, \"\n", + " \"'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': \"\n", + " \"'/_localstack/health', 'port': 'edge'}}, 'resources': {}, 'env': [{'name': \"\n", + " \"'DEBUG', 'value': '0'}, {'name': 'EXTERNAL_SERVICE_PORTS_START', 'value': \"\n", + " \"'4510'}, {'name': 'EXTERNAL_SERVICE_PORTS_END', 'value': '4560'}, {'name': \"\n", + " \"'LOCALSTACK_K8S_SERVICE_NAME', 'value': 'localstack'}, {'name': \"\n", + " \"'LOCALSTACK_K8S_NAMESPACE', 'valueFrom': {'fieldRef': {'fieldPath': \"\n", + " \"'metadata.namespace'}}}, {'name': 'LAMBDA_RUNTIME_EXECUTOR', 'value': \"\n", + " \"'docker'}, {'name': 'LAMBDA_K8S_IMAGE_PREFIX', 'value': \"\n", + " \"'localstack/lambda-'}, {'name': 'LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT', \"\n", + " \"'value': '60'}, {'name': 'OVERRIDE_IN_DOCKER', 'value': '1'}]}], 'volumes': \"\n", + " \"[]}}}}, {'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': \"\n", + " \"'my-config'}, 'data': {'ENV_VAR1': 'value1', 'ENV_VAR2': 'value2'}}, \"\n", + " \"{'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'my-secret'}, \"\n", + " \"'type': 'Opaque', 'data': {'SECRET_KEY1': 'dmFsdWUx', 'SECRET_KEY2': \"\n", + " \"'dmFsdWUy'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': \"\n", + " \"'aws-credentials-{{ spawner.user.name }}'}, 'type': 'Opaque', 'data': \"\n", + " \"{'credentials': \"\n", + " \"'W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg='}}, \"\n", + " \"{'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', \"\n", + " \"'metadata': {'name': 'data-by-name', 'namespace': 'jupyter-{{ \"\n", + " \"spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': \"\n", + " \"{'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': \"\n", + " \"{'name': 'data-by-name', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': \"\n", + " \"'secret-value', 'remoteRef': {'key': 'secret-one', 'property': \"\n", + " \"'the-key'}}]}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': \"\n", + " \"'ExternalSecret', 'metadata': {'name': 'eoepca-plus-secret-ro', 'namespace': \"\n", + " \"'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', \"\n", + " \"'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': \"\n", + " \"'k8s-secret-store'}, 'target': {'name': 'eoepca-plus-secret-ro', \"\n", + " \"'creationPolicy': 'Owner'}, 'data': [{'secretKey': '.dockerconfigjson', \"\n", + " \"'remoteRef': {'key': 'eoepca-plus-secret-ro', 'property': \"\n", + " \"'.dockerconfigjson'}}]}}], persist=False)], env_from_config_maps=None, \"\n", + " 'env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_studio_desktop_qgis', groups=['group-a', 'group-b'], \"\n", + " \"definition=ProfileDefinition(display_name='QGIS on a Remote Desktop', \"\n", + " \"description='Spatial visualization and decision-making tools for everyone', \"\n", + " \"slug='eoepca_desktop_qgis', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, '\n", + " \"mem_limit='2G', mem_guarantee=None, \"\n", + " \"image='eoepca/iga-remote-desktop-qgis:1.1.3', extra_resource_limits={}, \"\n", + " \"extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', \"\n", + " \"key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, \"\n", + " 'readonly=True, content=\\'alias ll=\"ls -l\"\\\\nalias '\n", + " 'calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors '\n", + " '/etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json '\n", + " '--max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir '\n", + " '/calrissian/\"\\\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\\\n. '\n", + " '/home/jovyan/.bashrc\\\\n\\\\n#alias aws=\"aws '\n", + " '--endpoint-url=http://localstack:4566\"\\\\n\\\\n# >>> conda initialize >>>\\\\n# '\n", + " \"!! Contents within this block are managed by \\\\'conda init\\\\' \"\n", + " '!!\\\\n__conda_setup=\"$(\\\\\\'/opt/conda/bin/conda\\\\\\' \\\\\\'shell.bash\\\\\\' '\n", + " '\\\\\\'hook\\\\\\' 2> /dev/null)\"\\\\nif [ $? -eq 0 ]; then\\\\n eval '\n", + " '\"$__conda_setup\"\\\\nelse\\\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; '\n", + " 'then\\\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\\\n else\\\\n '\n", + " 'export PATH=\"/srv/conda/bin:$PATH\"\\\\n fi\\\\nfi\\\\nunset '\n", + " '__conda_setup\\\\n\\\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\\\n '\n", + " '. \"/opt/conda/etc/profile.d/mamba.sh\"\\\\nfi\\\\n# <<< conda initialize '\n", + " '<<<\\\\n\\\\na={{spawner.user.name}}\\\\n\\\\nalias aws=\"aws '\n", + " '--endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"\\', '\n", + " \"persist=False), ConfigMap(name='bash-login', key='bash-login', \"\n", + " \"mount_path='/workspace/.bash_login', default_mode=None, readonly=True, \"\n", + " \"content='source /workspace/.bashrc\\\\n', persist=False)], \"\n", + " \"volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', \"\n", + " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", + " \"access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace'}, default_url='desktop', \"\n", + " 'node_selector={}, role_bindings=None, image_pull_secrets=[], '\n", + " 'init_containers=[], manifests=None, env_from_config_maps=None, '\n", + " 'env_from_secrets=None, secret_mounts=None)]')\n" + ] + } + ], + "source": [ + "pprint(profiles)\n", + "config = Config(profiles=profiles) # type: ignore\n", + "pprint(f'config: {config}')\n", + "eoepca_demo_config_path = str(Path(current_dir).parent.parent / 'eoepca-demo' / 'config.yml')\n", + "with open(eoepca_demo_config_path, \"w\") as file:\n", + " yaml.dump(config.model_dump(), file)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "apphub_configurator", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/apphub-configurator/examples/generate-config.sh b/apphub-configurator/examples/generate-config.sh new file mode 100755 index 0000000..0238b9d --- /dev/null +++ b/apphub-configurator/examples/generate-config.sh @@ -0,0 +1,4 @@ +#exit 0 +export PYTHONPATH=$PWD/`dirname $0`/. + +.env-apphub-configurator/bin/python3 -m generate_config \ No newline at end of file diff --git a/config-generator/examples/generate_config.py b/apphub-configurator/examples/generate_config.py similarity index 97% rename from config-generator/examples/generate_config.py rename to apphub-configurator/examples/generate_config.py index 7fecb8b..a09a990 100644 --- a/config-generator/examples/generate_config.py +++ b/apphub-configurator/examples/generate_config.py @@ -11,6 +11,8 @@ ) import click +logger.info("Generating config file...") + storage_class_rwo = "standard" storage_class_rwx = "standard" profiles = [] @@ -160,3 +162,7 @@ with open(config_file_path, "w") as file: yaml.dump(config.model_dump(), file, width=200) + +logger.success( + f"Config file generated successfully at {config_file_path}" +) \ No newline at end of file diff --git a/config-generator/manifests/dask-gateway.yaml b/apphub-configurator/manifests/dask-gateway.yaml similarity index 100% rename from config-generator/manifests/dask-gateway.yaml rename to apphub-configurator/manifests/dask-gateway.yaml diff --git a/config-generator/manifests/kaniko.yaml b/apphub-configurator/manifests/kaniko.yaml similarity index 100% rename from config-generator/manifests/kaniko.yaml rename to apphub-configurator/manifests/kaniko.yaml diff --git a/config-generator/manifests/manifest.yaml b/apphub-configurator/manifests/manifest.yaml similarity index 100% rename from config-generator/manifests/manifest.yaml rename to apphub-configurator/manifests/manifest.yaml diff --git a/config-generator/pyproject.toml b/apphub-configurator/pyproject.toml similarity index 75% rename from config-generator/pyproject.toml rename to apphub-configurator/pyproject.toml index bf4326f..d47dd4a 100644 --- a/config-generator/pyproject.toml +++ b/apphub-configurator/pyproject.toml @@ -6,12 +6,12 @@ build-backend = "hatchling.build" name = "apphub-configurator" dynamic = ["version"] description = "apphub-configurator" -readme = "README.md" +readme = "../docs/app-hub-configurator.md" requires-python = ">=3.10" license = "Apache-2.0" keywords = ["kubernetes", "configuration", "EOEPCA"] authors = [ - { name = "Parham Membari", email = "parham.membari@terradue.com" } + { name = "Terradue", email = "info@terradue.com" } ] classifiers = [ "Development Status :: 4 - Beta", @@ -24,10 +24,10 @@ classifiers = [ dependencies = [] [project.urls] -Documentation = "https://github.com/EOEPCA/application-hub-context/config-generator/apphub-configurator#readme" +Documentation = "https://github.com/EOEPCA/application-hub-context/apphub-configurator/apphub-configurator#readme" Issues = "https://github.com/EOEPCA/application-hub-context/issues" -Source = "https://github.com/EOEPCA/application-hub-context/config-generator/apphub-configurator" -Examples = "https://github.com/EOEPCA/application-hub-context/config-generator/examples" +Source = "https://github.com/EOEPCA/application-hub-context/apphub-configurator/apphub-configurator" +Examples = "https://github.com/EOEPCA/application-hub-context/apphub-configurator/examples" [tool.hatch.version] path = "src/apphub_configurator/__about__.py" @@ -41,15 +41,18 @@ examples = ["examples"] [tool.hatch.envs.default] -path = "/code/hatch/envs/apphub_configurator" +path = "hatch/envs/apphub_configurator" [tool.hatch.envs.prod] -path = "/code/hatch/envs/apphub_configurator" +path = "hatch/envs/apphub_configurator" dependencies = ["pytest", "pytest-cov", "pydantic", "pyyaml", "ipykernel", - "jupyter"] + "jupyter", + "loguru", + "click", + ] [[tool.hatch.envs.test.matrix]] python = ["3.10", "3.11", "3.12"] diff --git a/config-generator/src/apphub_configurator/__about__.py b/apphub-configurator/src/apphub_configurator/__about__.py similarity index 83% rename from config-generator/src/apphub_configurator/__about__.py rename to apphub-configurator/src/apphub_configurator/__about__.py index 5f6f7b4..06f57eb 100644 --- a/config-generator/src/apphub_configurator/__about__.py +++ b/apphub-configurator/src/apphub_configurator/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2025-present pmembari # # SPDX-License-Identifier: MIT -__version__ = "0.0.4" +__version__ = "0.0.6" diff --git a/config-generator/src/apphub_configurator/__init__.py b/apphub-configurator/src/apphub_configurator/__init__.py similarity index 100% rename from config-generator/src/apphub_configurator/__init__.py rename to apphub-configurator/src/apphub_configurator/__init__.py diff --git a/config-generator/src/apphub_configurator/helpers.py b/apphub-configurator/src/apphub_configurator/helpers.py similarity index 100% rename from config-generator/src/apphub_configurator/helpers.py rename to apphub-configurator/src/apphub_configurator/helpers.py diff --git a/config-generator/src/apphub_configurator/models.py b/apphub-configurator/src/apphub_configurator/models.py similarity index 100% rename from config-generator/src/apphub_configurator/models.py rename to apphub-configurator/src/apphub_configurator/models.py diff --git a/config-generator/tests/__init__.py b/apphub-configurator/tests/__init__.py similarity index 100% rename from config-generator/tests/__init__.py rename to apphub-configurator/tests/__init__.py diff --git a/config-generator/examples/config-generator-eoepca-demo.ipynb b/config-generator/examples/config-generator-eoepca-demo.ipynb deleted file mode 100644 index af5aa61..0000000 --- a/config-generator/examples/config-generator-eoepca-demo.ipynb +++ /dev/null @@ -1,729 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "PosixPath('/home/p/Desktop/p/Terradue/EOEPCA/application-hub-context/config-generator/examples')" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import yaml\n", - "from apphub_configurator.models import *\n", - "from pathlib import Path\n", - "import os\n", - "current_dir = Path(os.getcwd())\n", - "parent_dir = current_dir.parent\n", - "current_dir" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Configuration\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "storage_class_rwo = \"managed-nfs-storage\"\n", - "storage_class_rwx = \"managed-nfs-storage\"\n", - "\n", - "workspace_volume_size = \"50Gi\"\n", - "calrissian_volume_size = \"50Gi\"" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Volumes\n", - "\n", - "Create the Volumes" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Workspace Volume\n", - "\n", - "The workspace volume is persisted." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "workspace_volume = Volume(\n", - " name=\"workspace-volume\",\n", - " size=workspace_volume_size,\n", - " claim_name=\"workspace-claim\",\n", - " mount_path=\"/workspace\",\n", - " storage_class=storage_class_rwo,\n", - " access_modes=[\"ReadWriteOnce\"],\n", - " volume_mount=VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"),\n", - " persist=False,\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Calrissian Volume\n", - "\n", - "This is a RWX volume, not persisted" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "calrissian_volume = Volume(\n", - " name=\"calrissian-volume\",\n", - " claim_name=\"calrissian-claim\",\n", - " size=calrissian_volume_size,\n", - " storage_class=storage_class_rwx,\n", - " access_modes=[\"ReadWriteMany\"],\n", - " volume_mount=VolumeMount(name=\"calrissian-volume\", mount_path=\"/calrissian\"),\n", - " persist=False,\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ConfigMaps\n", - "\n", - "These configmaps are mounted as files on the pod." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### bash login\n", - "\n", - "This file is used for the JupyterLab Terminal configuration" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "bash_login_file_path = os.path.join(parent_dir, \"config-maps/bash-login\") \n", - "with open(bash_login_file_path, \"r\") as f:\n", - " content = f.read()\n", - "\n", - "bash_login_cm = ConfigMap(\n", - " name=\"bash-login\",\n", - " key=\"bash-login\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/workspace/.bash_login\",\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### bash.rc\n" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "bash_rc_cm_file_path = os.path.join(parent_dir, \"config-maps/bash-rc\") \n", - "with open(bash_rc_cm_file_path, \"r\") as f:\n", - " content = f.read()\n", - "bash_rc_cm = ConfigMap(\n", - " name=\"bash-rc\",\n", - " key=\"bash-rc\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/workspace/.bashrc\",\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## bash login\n" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "bash_login_file_path = os.path.join(parent_dir, \"config-maps/bash-login\") \n", - "with open(bash_login_file_path, \"r\") as f:\n", - " content = f.read()\n", - "\n", - "bash_login_cm = ConfigMap(\n", - " name=\"bash-login\",\n", - " key=\"bash-login\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/workspace/.bash_login\",\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Profiles" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "profiles = []" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Coder" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "coders = {\n", - " \"coder1\": {\n", - " \"display_name\": \"Code Server Small\",\n", - " \"slug\": \"ellip_studio_coder_slug_s\",\n", - " \"cpu_limit\": 2,\n", - " \"mem_limit\": \"8G\",\n", - " },\n", - " \"coder2\": {\n", - " \"display_name\": \"Code Server Medium\",\n", - " \"slug\": \"ellip_studio_coder_slug_m\",\n", - " \"cpu_limit\": 4,\n", - " \"mem_limit\": \"12G\",\n", - " },\n", - "}\n", - "\n", - "for key, value in coders.items():\n", - " coder_definition = ProfileDefinition(\n", - " display_name=value[\"display_name\"],\n", - " slug=value[\"slug\"],\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_limit=value[\"cpu_limit\"],\n", - " mem_limit=value[\"mem_limit\"],\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " ),\n", - " )\n", - "\n", - " coder_profile = Profile(\n", - " id=f\"profile_studio_{key}\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=coder_definition,\n", - " node_selector={},\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[\n", - " bash_rc_cm,\n", - " ],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " },\n", - " )\n", - "\n", - " profiles.append(coder_profile)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## init.sh script" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "init_cm_file_path = os.path.join(parent_dir, \"config-maps/init.sh\") \n", - "with open(init_cm_file_path, \"r\") as f:\n", - " content = f.read()\n", - "\n", - "init_cm = ConfigMap(\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - " default_mode=\"0660\",\n", - ")\n", - "\n", - "\n", - "init_context_volume_mount = InitContainerVolumeMount(\n", - " mount_path=\"/opt/init/.init.sh\", name=\"init\", sub_path=\"init\"\n", - ")\n", - "init_container = InitContainer(\n", - " name=\"init-file-on-volume\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " command=[\"sh\", \"-c\", \"sh /opt/init/.init.sh\"],\n", - " volume_mounts=[\n", - " VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"),\n", - " init_context_volume_mount,\n", - " ],\n", - ")\n", - "\n", - "eoepca_demo_init_script_profile = Profile(\n", - " id=f\"profile_demo_init_script\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Coder demo init script\",\n", - " description=\"This profile is used to demonstrate the use of an init script\",\n", - " slug=\"eoepca_demo_init_script\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[init_cm],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " \"CONDARC\": \"/workspace/.condarc\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"CODE_SERVER_WS\": \"/workspace/mastering-app-package\",\n", - " },\n", - " init_containers=[init_container],\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "profiles.append(eoepca_demo_init_script_profile)\n", - "\n", - "eoepca_demo_init_script_profile = Profile(\n", - " id=f\"profile_demo_init_script\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Coder demo init script\",\n", - " description=\"This profile is used to demonstrate the use of an init script\",\n", - " slug=\"eoepca_demo_init_script\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[init_cm],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " \"CONDARC\": \"/workspace/.condarc\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"CODE_SERVER_WS\": \"/workspace/mastering-app-package\",\n", - " },\n", - " init_containers=[init_container],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## JupyterLab" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "image = \"jupyter/scipy-notebook\"\n", - "\n", - "\n", - "eoepca_jupyter_lab_profile = Profile(\n", - " id=\"profile_jupyter_lab\",\n", - " groups=[\"group-c\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Jupyter Lab\",\n", - " description=\"Jupyter Lab with Python 3.11\",\n", - " slug=\"eoepca_jupyter_lab\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[workspace_volume],\n", - " config_maps=[],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", - " },\n", - ")\n", - "\n", - "profiles.append(eoepca_jupyter_lab_profile)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Image pull secret\n" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "image_pull_secret = ImagePullSecret(\n", - " name=\"cr-config\",\n", - " persist=False,\n", - " data=\"ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9\",\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "image = \"cr.terradue.com/eoepca-plus/scipy-notebook@sha256:f339a9fa98d3d0c1fa8d7cc850e7f5a46845781f49bee86aacba059669d02d54\"\n", - "image = \"eoepca/iat-jupyterlab:develop\"\n", - "\n", - "eoepca_jupyter_lab_profile_2 = Profile(\n", - " id=\"profile_jupyter_lab_2\",\n", - " groups=[\"group-c\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Jupyter Lab - profile 2\",\n", - " description=\"Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret\",\n", - " slug=\"eoepca_jupyter_lab_2\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[workspace_volume],\n", - " config_maps=[],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", - " },\n", - " image_pull_secrets=[image_pull_secret],\n", - ")\n", - "\n", - "profiles.append(eoepca_jupyter_lab_profile_2)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Stage-in/out" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "init_stac_file_path = os.path.join(parent_dir, \"config-maps/init-stac.sh\")\n", - "with open(init_stac_file_path, \"r\") as f:\n", - " content = f.read()\n", - "\n", - "init_cm = ConfigMap(\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [], - "source": [ - "localstack_manifest_path = os.path.join(parent_dir, \"manifests/manifest.yaml\") \n", - "with open(localstack_manifest_path, \"r\") as f:\n", - " content = yaml.safe_load_all(f.read())\n", - "\n", - "\n", - "localstack_manifest = Manifest(\n", - " name=\"manifests\",\n", - " key=\"manifests\",\n", - " readonly=True,\n", - " persist=False,\n", - " content=[e for e in content],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## e-learning" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "image = \"docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40\"\n", - "\n", - "coder_profile_stac = Profile(\n", - " id=f\"profile_studio_coder_stac\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Understanding STAC for input/output data modelling\",\n", - " description=\"Understand the role of STAC in input/output data manifests in EO data processing workflows\",\n", - " slug=\"eoepca_coder_slug_stac\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[workspace_volume],\n", - " config_maps=[init_cm, bash_rc_cm, bash_login_cm],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " \"CONDARC\": \"/workspace/.condarc\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_CONFIG_HOME\": \"/workspace/.local\",\n", - " \"XDG_DATA_HOME\": \"/workspace/.local/share/\",\n", - " \"CWLTOOL_OPTIONS\": \"--podman\",\n", - " \"CODE_SERVER_WS\": \"/workspace/stac-eoap\",\n", - " \"AWS_DEFAULT_REGION\": \"us-east-1\",\n", - " \"AWS_ACCESS_KEY_ID\": \"test\",\n", - " \"AWS_SECRET_ACCESS_KEY\": \"test\",\n", - " },\n", - " role_bindings=[],\n", - " init_containers=[init_container],\n", - " image_pull_secrets=[image_pull_secret],\n", - " manifests=[localstack_manifest],\n", - ")\n", - "\n", - "profiles.append(coder_profile_stac)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## QGIS" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "metadata": {}, - "outputs": [], - "source": [ - "init_qgis_file_path = os.path.join(parent_dir, \"config-maps/init-qgis.sh\") \n", - "with open(init_qgis_file_path, \"r\") as f:\n", - " content = f.read()\n", - "\n", - "init_qgis_cm = ConfigMap(\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - ")\n", - "\n", - "image = \"eoepca/iga-remote-desktop-qgis:1.1.3\"\n", - "\n", - "qgis_profile = Profile(\n", - " id=\"profile_studio_desktop_qgis\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"QGIS on a Remote Desktop\",\n", - " description=\"Spatial visualization and decision-making tools for everyone\",\n", - " slug=\"eoepca_desktop_qgis\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_limit=2,\n", - " mem_limit=\"2G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[workspace_volume],\n", - " config_maps=[bash_rc_cm, bash_login_cm],\n", - " pod_env_vars={\"HOME\": \"/workspace\"},\n", - " default_url=\"desktop\",\n", - " init_containers=[],\n", - ")\n", - "\n", - "profiles.append(qgis_profile)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Configuration" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "metadata": {}, - "outputs": [], - "source": [ - "config = Config(profiles=profiles)\n", - "eoepca_demo_config_path = str(Path(current_dir).parent.parent / 'eoepca-demo' / 'config.yml')\n", - "with open(eoepca_demo_config_path, \"w\") as file:\n", - " yaml.dump(config.model_dump(), file)" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "metadata": {}, - "outputs": [], - "source": [ - "# profiles" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "apphub_configurator", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.2" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/config-generator/examples/config-generator.ipynb b/config-generator/examples/config-generator.ipynb deleted file mode 100644 index 36f1e11..0000000 --- a/config-generator/examples/config-generator.ipynb +++ /dev/null @@ -1,610 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "PosixPath('/home/p/Desktop/p/Terradue/EOEPCA/application-hub-context/config-generator/examples')" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import yaml\n", - "from apphub_configurator.models import *\n", - "import os\n", - "from pathlib import Path\n", - "\n", - "current_dir = Path(os.getcwd())\n", - "parent_dir = current_dir.parent\n", - "current_dir" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "localstack_manifest_path = os.path.join(parent_dir, \"manifests/manifest.yaml\") \n", - "with open(localstack_manifest_path, \"r\") as f:\n", - " content = yaml.safe_load_all(f.read())\n", - "\n", - "\n", - "localstack_manifest = Manifest(\n", - " name=\"manifests\",\n", - " key=\"manifests\",\n", - " readonly=True,\n", - " persist=False,\n", - " content=[e for e in content],\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Configuration\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "storage_class_rwo = \"standard\"\n", - "storage_class_rwx = \"standard\"\n", - "\n", - "workspace_volume_size = \"50Gi\"\n", - "calrissian_volume_size = \"50Gi\"\n", - "\n", - "node_selector = {} # \"k8s.scaleway.com/pool-name\": \"application-hub\"}" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Volumes\n", - "\n", - "Create the Volumes" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Workspace Volume\n", - "\n", - "The workspace volume is persisted." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [], - "source": [ - "workspace_volume = Volume(\n", - " name=\"workspace-volume\",\n", - " size=workspace_volume_size,\n", - " claim_name=\"workspace-claim\",\n", - " mount_path=\"/workspace\",\n", - " storage_class=storage_class_rwo,\n", - " access_modes=[\"ReadWriteOnce\"],\n", - " volume_mount=VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"),\n", - " persist=True,\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Calrissian Volume\n", - "\n", - "This is a RWX volume, not persisted" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [], - "source": [ - "calrissian_volume = Volume(\n", - " name=\"calrissian-volume\",\n", - " claim_name=\"calrissian-claim\",\n", - " size=calrissian_volume_size,\n", - " storage_class=storage_class_rwx,\n", - " access_modes=[\"ReadWriteMany\"],\n", - " volume_mount=VolumeMount(name=\"calrissian-volume\", mount_path=\"/calrissian\"),\n", - " persist=False,\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ConfigMaps\n", - "\n", - "These configmaps are mounted as files on the pod." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### bash login\n", - "\n", - "This file is used for the JupyterLab Terminal configuration" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "bash_login_file_path = os.path.join(parent_dir, \"config-maps/bash-login\") \n", - "with open(bash_login_file_path, \"r\") as f:\n", - " content = f.read()\n", - "\n", - "bash_login_cm = ConfigMap(\n", - " name=\"bash-login\",\n", - " key=\"bash-login\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=True,\n", - " mount_path=\"/workspace/.bash_login\",\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### bash.rc\n" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [], - "source": [ - "bash_rc_cm_file_path = os.path.join(parent_dir, \"config-maps/bash-rc\") \n", - "with open(bash_rc_cm_file_path, \"r\") as f:\n", - " content = f.read()\n", - "bash_rc_cm = ConfigMap(\n", - " name=\"bash-rc\",\n", - " key=\"bash-rc\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=True,\n", - " mount_path=\"/workspace/.bashrc\",\n", - ")" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Profiles" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "profiles = []" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Coder" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], - "source": [ - "coders = {\n", - " \"coder1\": {\n", - " \"display_name\": \"Code Server Small\",\n", - " \"slug\": \"eoepca_coder_slug_s\",\n", - " \"cpu_limit\": 2,\n", - " \"mem_limit\": \"8G\",\n", - " },\n", - " \"coder2\": {\n", - " \"display_name\": \"Code Server Medium\",\n", - " \"slug\": \"eoepca_coder_slug_m\",\n", - " \"cpu_limit\": 4,\n", - " \"mem_limit\": \"12G\",\n", - " },\n", - "}\n", - "\n", - "for key, value in coders.items():\n", - " coder_definition = ProfileDefinition(\n", - " display_name=value[\"display_name\"],\n", - " slug=value[\"slug\"],\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_limit=value[\"cpu_limit\"],\n", - " mem_limit=value[\"mem_limit\"],\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " ),\n", - " )\n", - "\n", - " coder_profile = Profile(\n", - " id=f\"profile_studio_{key}\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=coder_definition,\n", - " node_selector=node_selector,\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[\n", - " bash_rc_cm,\n", - " ],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \" /workspace/.envs\",\n", - " },\n", - " manifests=[localstack_manifest],\n", - " env_from_config_maps=[\"my-config\"],\n", - " env_from_secrets=[\"my-secret\", \"data-by-name\"],\n", - " secret_mounts=[\n", - " SecretMount(\n", - " name=\"aws-credentials-{{ spawner.user.name }}\",\n", - " mount_path=\"/workspace/.aws\",\n", - " ),\n", - " SecretMount(name=\"data-by-name\", mount_path=\"/workspace/.data-by-name\"),\n", - " SecretMount(name=\"eoepca-plus-secret-ro\", mount_path=\"/workspace/.docker\"),\n", - " ],\n", - " image_pull_secrets=[ImagePullSecret(name=\"eoepca-plus-secret-ro\")],\n", - " )\n", - "\n", - " profiles.append(coder_profile)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## init.sh script" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "init_cm_file_path = os.path.join(parent_dir, \"config-maps/init.sh\") \n", - "with open(init_cm_file_path, \"r\") as f:\n", - " content = f.read()\n", - "\n", - "init_cm = ConfigMap(\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - " default_mode=\"0660\",\n", - ")\n", - "\n", - "\n", - "init_context_volume_mount = InitContainerVolumeMount(\n", - " mount_path=\"/opt/init/.init.sh\", name=\"init\", sub_path=\"init\"\n", - ")\n", - "init_container = InitContainer(\n", - " name=\"init-file-on-volume\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " command=[\"sh\", \"-c\", \"sh /opt/init/.init.sh\"],\n", - " volume_mounts=[\n", - " VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"),\n", - " init_context_volume_mount,\n", - " ],\n", - ")\n", - "\n", - "eoepca_demo_init_script_profile = Profile(\n", - " id=f\"profile_demo_init_script\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Coder demo init script\",\n", - " description=\"This profile is used to demonstrate the use of an init script\",\n", - " slug=\"eoepca_demo_init_script\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " ),\n", - " ),\n", - " node_selector=node_selector,\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[init_cm],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " \"CONDARC\": \"/workspace/.condarc\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"CODE_SERVER_WS\": \"/workspace/mastering-app-package\",\n", - " },\n", - " init_containers=[init_container],\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [], - "source": [ - "profiles.append(eoepca_demo_init_script_profile)\n", - "\n", - "eoepca_demo_init_script_profile = Profile(\n", - " id=f\"profile_demo_init_script\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Coder demo init script\",\n", - " description=\"This profile is used to demonstrate the use of an init script\",\n", - " slug=\"eoepca_demo_init_script\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " ),\n", - " ),\n", - " node_selector=node_selector,\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[init_cm],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " \"CONDARC\": \"/workspace/.condarc\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"CODE_SERVER_WS\": \"/workspace/mastering-app-package\",\n", - " },\n", - " init_containers=[init_container],\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## JupyterLab" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [], - "source": [ - "image = \"jupyter/scipy-notebook\"\n", - "\n", - "\n", - "eoepca_jupyter_lab_profile = Profile(\n", - " id=\"profile_jupyter_lab\",\n", - " groups=[\"group-c\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Jupyter Lab\",\n", - " description=\"Jupyter Lab with Python 3.11\",\n", - " slug=\"eoepca_jupyter_lab\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector=node_selector,\n", - " volumes=[workspace_volume],\n", - " config_maps=[],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", - " },\n", - ")\n", - "\n", - "profiles.append(eoepca_jupyter_lab_profile)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Image pull secret\n" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [], - "source": [ - "image_pull_secret = ImagePullSecret(\n", - " name=\"cr-config\",\n", - " persist=False,\n", - " data=\"\",\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [], - "source": [ - "with open(localstack_manifest_path, \"r\") as f:\n", - " content = yaml.safe_load_all(f.read())\n", - "\n", - "\n", - "localstack_manifest = Manifest(\n", - " name=\"manifests\",\n", - " key=\"manifests\",\n", - " readonly=True,\n", - " persist=False,\n", - " content=[e for e in content],\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [], - "source": [ - "image = \"jupyter/scipy-notebook\"\n", - "\n", - "\n", - "eoepca_jupyter_lab_profile_2 = Profile(\n", - " id=\"profile_jupyter_lab_2\",\n", - " groups=[\"group-c\"],\n", - " definition=ProfileDefinition(\n", - " display_name=\"Jupyter Lab - profile 2\",\n", - " description=\"Jupyter Lab with Python 3.11 - demoes the use of an image pull secret\",\n", - " slug=\"eoepca_jupyter_lab_2\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride(\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector=node_selector,\n", - " volumes=[workspace_volume],\n", - " config_maps=[],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", - " },\n", - " image_pull_secrets=[image_pull_secret],\n", - " manifests=[localstack_manifest],\n", - ")\n", - "\n", - "profiles.append(eoepca_jupyter_lab_profile_2)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Configuration" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/tmp/ipykernel_181750/2113836574.py:5: PydanticDeprecatedSince20: The `dict` method is deprecated; use `model_dump` instead. Deprecated in Pydantic V2.0 to be removed in V3.0. See Pydantic V2 Migration Guide at https://errors.pydantic.dev/2.10/migration/\n", - " yaml.dump(config.dict(), file)\n" - ] - } - ], - "source": [ - "config = Config(profiles=profiles)\n", - "config_file_path = str(Path(current_dir).parent.parent / 'files' / 'hub' / 'config.yml')\n", - "\n", - "with open(config_file_path, \"w\") as file:\n", - " yaml.dump(config.dict(), file)" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [], - "source": [ - "# profiles" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "apphub_configurator", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.2" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/config-generator/examples/generate-config.sh b/config-generator/examples/generate-config.sh deleted file mode 100755 index 279e056..0000000 --- a/config-generator/examples/generate-config.sh +++ /dev/null @@ -1,4 +0,0 @@ -#exit 0 -export PYTHONPATH=$PWD/`dirname $0`/. - -.env-config-generator/bin/python3 -m generate_config \ No newline at end of file diff --git a/docs/app-hub-configurator.md b/docs/app-hub-configurator.md new file mode 100644 index 0000000..e12c8cd --- /dev/null +++ b/docs/app-hub-configurator.md @@ -0,0 +1,33 @@ +## ApplicationHub configuration generator +This application aim to produce a `config.yml` file to generate all the configuration needs to be set for deployment of App Hub on a remote cluster using Devops tools. +# apphub-configurator + +[![PyPI - Version](https://img.shields.io/pypi/v/apphub-configurator.svg)](https://pypi.org/project/apphub-configurator) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/apphub-configurator.svg)](https://pypi.org/project/apphub-configurator) + +----- + +## Table of Contents + +- [Installation](#installation) +- [Overview](#overview) +- [Examples](#Examples) +- [License](#license) + +## Installation +Create a hatch environment with the dependencies listed in the file `pyproject.toml`. + +```console +hatch shell prod +hatch -e prod run python -m ipykernel install --user --name=apphub_configurator --display-name "apphub_configurator" +``` +> Note: you may need to reopen visual studio code to set the jupyter kernel successfully. +## Overview +This package contains a notebook and the python modules to support the generation of ApplicationHub configurations for a minikube cluster. For more information about ApplicationHub please check this [link](https://github.com/EOEPCA/application-hub-context) + +## Examples: +The [example](../apphub-configurator/examples/) folder contains a notebook and the python modules to support the generation of ApplicationHub configurations. + +## License + +`apphub-configurator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. diff --git a/eoepca-demo/config.yml b/eoepca-demo/config.yml index 61b05c9..f944149 100644 --- a/eoepca-demo/config.yml +++ b/eoepca-demo/config.yml @@ -4,14 +4,15 @@ profiles: \ /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram\ \ 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\ \nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\n. /home/jovyan/.bashrc\n\ - \nalias aws=\"aws --endpoint-url=http://localstack:4566\"\n\n# >>> conda initialize\ + \n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\n\n# >>> conda initialize\ \ >>>\n# !! Contents within this block are managed by 'conda init' !!\n__conda_setup=\"\ $('/opt/conda/bin/conda' 'shell.bash' 'hook' 2> /dev/null)\"\nif [ $? -eq 0\ \ ]; then\n eval \"$__conda_setup\"\nelse\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\"\ \ ]; then\n . \"/opt/conda/etc/profile.d/conda.sh\"\n else\n \ \ export PATH=\"/srv/conda/bin:$PATH\"\n fi\nfi\nunset __conda_setup\n\n\ if [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\n . \"/opt/conda/etc/profile.d/mamba.sh\"\ - \nfi\n# <<< conda initialize <<<\n" + \nfi\n# <<< conda initialize <<<\n\na={{spawner.user.name}}\n\nalias aws=\"\ + aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"" default_mode: null key: bash-rc mount_path: /workspace/.bashrc @@ -32,6 +33,8 @@ profiles: mem_guarantee: null mem_limit: 8G slug: ellip_studio_coder_slug_s + env_from_config_maps: null + env_from_secrets: null groups: - group-a - group-b @@ -44,6 +47,7 @@ profiles: CONDA_ENVS_PATH: /workspace/.envs HOME: /workspace role_bindings: null + secret_mounts: null volumes: - access_modes: - ReadWriteMany @@ -59,7 +63,7 @@ profiles: - ReadWriteOnce claim_name: workspace-claim name: workspace-volume - persist: false + persist: true size: 50Gi storage_class: managed-nfs-storage volume_mount: @@ -70,14 +74,15 @@ profiles: \ /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram\ \ 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\ \nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\n. /home/jovyan/.bashrc\n\ - \nalias aws=\"aws --endpoint-url=http://localstack:4566\"\n\n# >>> conda initialize\ + \n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\n\n# >>> conda initialize\ \ >>>\n# !! Contents within this block are managed by 'conda init' !!\n__conda_setup=\"\ $('/opt/conda/bin/conda' 'shell.bash' 'hook' 2> /dev/null)\"\nif [ $? -eq 0\ \ ]; then\n eval \"$__conda_setup\"\nelse\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\"\ \ ]; then\n . \"/opt/conda/etc/profile.d/conda.sh\"\n else\n \ \ export PATH=\"/srv/conda/bin:$PATH\"\n fi\nfi\nunset __conda_setup\n\n\ if [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\n . \"/opt/conda/etc/profile.d/mamba.sh\"\ - \nfi\n# <<< conda initialize <<<\n" + \nfi\n# <<< conda initialize <<<\n\na={{spawner.user.name}}\n\nalias aws=\"\ + aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"" default_mode: null key: bash-rc mount_path: /workspace/.bashrc @@ -98,6 +103,8 @@ profiles: mem_guarantee: null mem_limit: 12G slug: ellip_studio_coder_slug_m + env_from_config_maps: null + env_from_secrets: null groups: - group-a - group-b @@ -110,6 +117,7 @@ profiles: CONDA_ENVS_PATH: /workspace/.envs HOME: /workspace role_bindings: null + secret_mounts: null volumes: - access_modes: - ReadWriteMany @@ -125,7 +133,7 @@ profiles: - ReadWriteOnce claim_name: workspace-claim name: workspace-volume - persist: false + persist: true size: 50Gi storage_class: managed-nfs-storage volume_mount: @@ -176,6 +184,8 @@ profiles: mem_guarantee: 4G mem_limit: 6G slug: eoepca_demo_init_script + env_from_config_maps: null + env_from_secrets: null groups: - group-a - group-b @@ -203,6 +213,7 @@ profiles: HOME: /workspace XDG_RUNTIME_DIR: /workspace/.local role_bindings: null + secret_mounts: null volumes: - access_modes: - ReadWriteMany @@ -218,7 +229,7 @@ profiles: - ReadWriteOnce claim_name: workspace-claim name: workspace-volume - persist: false + persist: true size: 50Gi storage_class: managed-nfs-storage volume_mount: @@ -239,6 +250,8 @@ profiles: mem_guarantee: 4G mem_limit: 6G slug: eoepca_jupyter_lab + env_from_config_maps: null + env_from_secrets: null groups: - group-c id: profile_jupyter_lab @@ -251,12 +264,13 @@ profiles: XDG_CONFIG_HOME: /workspace/.config XDG_RUNTIME_DIR: /workspace/.local role_bindings: null + secret_mounts: null volumes: - access_modes: - ReadWriteOnce claim_name: workspace-claim name: workspace-volume - persist: false + persist: true size: 50Gi storage_class: managed-nfs-storage volume_mount: @@ -278,12 +292,13 @@ profiles: mem_guarantee: 4G mem_limit: 6G slug: eoepca_jupyter_lab_2 + env_from_config_maps: null + env_from_secrets: null groups: - group-c id: profile_jupyter_lab_2 image_pull_secrets: - - data: null - - data: '' + - data: ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9 name: cr-config persist: false init_containers: [] @@ -294,12 +309,13 @@ profiles: XDG_CONFIG_HOME: /workspace/.config XDG_RUNTIME_DIR: /workspace/.local role_bindings: null + secret_mounts: null volumes: - access_modes: - ReadWriteOnce claim_name: workspace-claim name: workspace-volume - persist: false + persist: true size: 50Gi storage_class: managed-nfs-storage volume_mount: @@ -331,14 +347,15 @@ profiles: \ /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram\ \ 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\ \nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\n. /home/jovyan/.bashrc\n\ - \nalias aws=\"aws --endpoint-url=http://localstack:4566\"\n\n# >>> conda initialize\ + \n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\n\n# >>> conda initialize\ \ >>>\n# !! Contents within this block are managed by 'conda init' !!\n__conda_setup=\"\ $('/opt/conda/bin/conda' 'shell.bash' 'hook' 2> /dev/null)\"\nif [ $? -eq 0\ \ ]; then\n eval \"$__conda_setup\"\nelse\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\"\ \ ]; then\n . \"/opt/conda/etc/profile.d/conda.sh\"\n else\n \ \ export PATH=\"/srv/conda/bin:$PATH\"\n fi\nfi\nunset __conda_setup\n\n\ if [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\n . \"/opt/conda/etc/profile.d/mamba.sh\"\ - \nfi\n# <<< conda initialize <<<\n" + \nfi\n# <<< conda initialize <<<\n\na={{spawner.user.name}}\n\nalias aws=\"\ + aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"" default_mode: null key: bash-rc mount_path: /workspace/.bashrc @@ -369,12 +386,14 @@ profiles: mem_guarantee: 4G mem_limit: 6G slug: eoepca_coder_slug_stac + env_from_config_maps: null + env_from_secrets: null groups: - group-a - group-b id: profile_studio_coder_stac image_pull_secrets: - - data: null + - data: ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9 name: cr-config persist: false init_containers: @@ -616,6 +635,7 @@ profiles: template: metadata: labels: + app: localstack-{{ spawner.user.name }} app.kubernetes.io/instance: localstack app.kubernetes.io/name: localstack spec: @@ -821,6 +841,64 @@ profiles: securityContext: {} serviceAccountName: localstack volumes: [] + - apiVersion: v1 + data: + ENV_VAR1: value1 + ENV_VAR2: value2 + kind: ConfigMap + metadata: + name: my-config + - apiVersion: v1 + data: + SECRET_KEY1: dmFsdWUx + SECRET_KEY2: dmFsdWUy + kind: Secret + metadata: + name: my-secret + type: Opaque + - apiVersion: v1 + data: + credentials: W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg= + kind: Secret + metadata: + name: aws-credentials-{{ spawner.user.name }} + type: Opaque + - apiVersion: external-secrets.io/v1beta1 + kind: ExternalSecret + metadata: + name: data-by-name + namespace: jupyter-{{ spawner.user.name }} + spec: + data: + - remoteRef: + key: secret-one + property: the-key + secretKey: secret-value + refreshInterval: 15s + secretStoreRef: + kind: ClusterSecretStore + name: k8s-secret-store + target: + creationPolicy: Owner + name: data-by-name + - apiVersion: external-secrets.io/v1beta1 + kind: ExternalSecret + metadata: + name: eoepca-plus-secret-ro + namespace: jupyter-{{ spawner.user.name }} + spec: + data: + - remoteRef: + key: eoepca-plus-secret-ro + property: .dockerconfigjson + secretKey: .dockerconfigjson + refreshInterval: 15s + secretStoreRef: + kind: ClusterSecretStore + name: k8s-secret-store + target: + creationPolicy: Owner + name: eoepca-plus-secret-ro key: manifests name: manifests persist: false @@ -838,40 +916,32 @@ profiles: XDG_DATA_HOME: /workspace/.local/share/ XDG_RUNTIME_DIR: /workspace/.local role_bindings: [] + secret_mounts: null volumes: - access_modes: - ReadWriteOnce claim_name: workspace-claim name: workspace-volume - persist: false + persist: true size: 50Gi storage_class: managed-nfs-storage volume_mount: mount_path: /workspace name: workspace-volume - config_maps: - - content: "mkdir -p /workspace/.config/autostart\n\n\ncat < /workspace/.config/autostart/qgis.desktop\ - \ \n[Desktop Entry]\nEncoding=UTF-8\nVersion=0.9.4\nType=Application\nName=qgis\n\ - Comment=qgis\nExec=qgis\nOnlyShowIn=XFCE;\nRunHook=0\nStartupNotify=false\n\ - Terminal=false\nHidden=false\nEOF" - default_mode: null - key: init - mount_path: /opt/init/.init.sh - name: init - persist: false - readonly: true - content: "alias ll=\"ls -l\"\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors\ \ /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram\ \ 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\ \nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\n. /home/jovyan/.bashrc\n\ - \nalias aws=\"aws --endpoint-url=http://localstack:4566\"\n\n# >>> conda initialize\ + \n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\n\n# >>> conda initialize\ \ >>>\n# !! Contents within this block are managed by 'conda init' !!\n__conda_setup=\"\ $('/opt/conda/bin/conda' 'shell.bash' 'hook' 2> /dev/null)\"\nif [ $? -eq 0\ \ ]; then\n eval \"$__conda_setup\"\nelse\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\"\ \ ]; then\n . \"/opt/conda/etc/profile.d/conda.sh\"\n else\n \ \ export PATH=\"/srv/conda/bin:$PATH\"\n fi\nfi\nunset __conda_setup\n\n\ if [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\n . \"/opt/conda/etc/profile.d/mamba.sh\"\ - \nfi\n# <<< conda initialize <<<\n" + \nfi\n# <<< conda initialize <<<\n\na={{spawner.user.name}}\n\nalias aws=\"\ + aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"" default_mode: null key: bash-rc mount_path: /workspace/.bashrc @@ -897,44 +967,30 @@ profiles: cpu_limit: 2 extra_resource_guarantees: {} extra_resource_limits: {} - image: eoepca/iga-remote-desktop-qgis:1.1.2 + image: eoepca/iga-remote-desktop-qgis:1.1.3 mem_guarantee: null mem_limit: 2G slug: eoepca_desktop_qgis + env_from_config_maps: null + env_from_secrets: null groups: - group-a - group-b id: profile_studio_desktop_qgis image_pull_secrets: [] - init_containers: - - command: - - sh - - -c - - sh /opt/init/.init.sh - image: eoepca/pde-code-server:develop - name: init-file-on-volume - volume_mounts: - - mount_path: /workspace - name: workspace-volume - - mount_path: /opt/init/.init.sh - name: init - sub_path: init + init_containers: [] manifests: null node_selector: {} pod_env_vars: - AWS_ACCESS_KEY_ID: SCWMRGD5GA0Y68XRKFW8 - AWS_REGION: fr-par - AWS_S3_ENDPOINT: s3.fr-par.scw.cloud - AWS_SECRET_ACCESS_KEY: a9a7dc0c-8b0a-47b1-a311-b134b258dbb3 - AWS_VIRTUAL_HOSTING: 'true' HOME: /workspace role_bindings: null + secret_mounts: null volumes: - access_modes: - ReadWriteOnce claim_name: workspace-claim name: workspace-volume - persist: false + persist: true size: 50Gi storage_class: managed-nfs-storage volume_mount: diff --git a/mkdocs.yml b/mkdocs.yml index 8d04fcb..4e7b6f2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -25,5 +25,6 @@ nav: - Introduction: 'index.md' - Kubernetes: 'k8s.md' - Configuration: 'configuration.md' + - AppHub Configurator Application Package: 'app-hub-configurator.md' - JupyterHub API: 'jupyterhub-api.md' - Config: 'apphub-config-general.md' diff --git a/setup.cfg b/setup.cfg index 03d2509..3f7496a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,4 @@ [metadata] name = application-context-hub -version = 1.3.0 +version = 1.3.1 + diff --git a/skaffold.yaml b/skaffold.yaml index 559145f..13cf1ba 100644 --- a/skaffold.yaml +++ b/skaffold.yaml @@ -31,7 +31,7 @@ profiles: hooks: before: - host: - command: ["sh", "-c", "config-generator/examples/generate-config.sh"] + command: ["sh", "-c", "apphub-configurator/examples/generate-config.sh"] os: [darwin, linux] manifests: rawYaml: From 23a2df91234ae97f69c396973dcce2e0a5a6ad4b Mon Sep 17 00:00:00 2001 From: pmembari Date: Mon, 7 Apr 2025 11:44:32 +0200 Subject: [PATCH 52/86] docs: edit intro --- docs/index.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 6c404dd..4e1cc1b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,11 +1,24 @@ # Application-Hub +The Application Hub provides a unified Cloud infrastructure to manage and deliver work environments and tools for a variety of user tasks. These tasks include developing, hosting, executing, and performing exploratory analysis of Earth Observation (EO) applications. The hub simplifies the management of these tasks within a single, cohesive platform. -## Read the configuration +## Configuration Overview + +There are many approach for deployment and one of them is using python based approach. Indeed, the user can generate, and apply the kubernetes objects using python instead of old-fashion approaches. In this tutorial, the user will configure The Application Hub using a Python script, [`jupyterhub_config.py`](../files/hub/jupyterhub_config.py). It customizes Application Hub's behavior by defining hooks, authentication, spawner settings, and other configurations. + +The key features of the python script are: +- [Read the configuration](#read-the-configuration) +- [Pre-spawn hook](#pre-spawn-hook) +- [Post-stop hook](#post-stop-hook) + +### Read the configuration + +The configuration file for the Application Hub is located at: ```python -config_path="/usr/local/etc/jupyterhub/config.yml" +config_path = "/usr/local/etc/applicationhub/config.yml" ``` +This file can be customized and generated by users through this tutorial. ## Profile list From 6b5bcca0a975f5976f8dd8afb4c387ade30b37c0 Mon Sep 17 00:00:00 2001 From: pmembari Date: Mon, 7 Apr 2025 11:44:55 +0200 Subject: [PATCH 53/86] docs: edit k8s doc --- docs/k8s.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/k8s.md b/docs/k8s.md index 7a996e7..075e3fc 100644 --- a/docs/k8s.md +++ b/docs/k8s.md @@ -1,6 +1,5 @@ # Kubernetes configuration - -JupyterHub is configured to run the pods in a dedicated workspace. +Kubernetes is an orchestration platform that automates the deployment, scaling, and management of containerized applications. Through this tutorial kubernetes profiles can be deployed on a remote cluster. The user will configure JupyterHub to run the pods in a dedicated workspace. As such, a ClusterRole is needed with the manifest: From 07eb79112f6654c9a6892d0c58fc2a2b48f3f84c Mon Sep 17 00:00:00 2001 From: pmembari Date: Mon, 7 Apr 2025 15:57:50 +0200 Subject: [PATCH 54/86] feat: use apphub-configurator pypi pacakge in examples --- apphub-configurator/README.md | 32 ------------------- .../examples/config-generator.ipynb | 13 ++++++-- 2 files changed, 11 insertions(+), 34 deletions(-) delete mode 100644 apphub-configurator/README.md diff --git a/apphub-configurator/README.md b/apphub-configurator/README.md deleted file mode 100644 index f3e861f..0000000 --- a/apphub-configurator/README.md +++ /dev/null @@ -1,32 +0,0 @@ -## ApplicationHub configuration generator -This application aim to produce a `config.yml` file to generate all the configuration needs to be set for deployment of App Hub on a remote cluster using Devops tools. -# apphub-configurator - -[![PyPI - Version](https://img.shields.io/pypi/v/apphub-configurator.svg)](https://pypi.org/project/apphub-configurator) -[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/apphub-configurator.svg)](https://pypi.org/project/apphub-configurator) - ------ -## Table of Contents - -- [Installation](#installation) -- [Overview](#overview) -- [Examples](#Examples) -- [License](#license) - -## Installation -Create a hatch environment with the dependencies listed in the file `pyproject.toml`. - -```console -hatch shell prod -hatch -e prod run python -m ipykernel install --user --name=apphub_configurator --display-name "apphub_configurator" -``` -> Note: you may need to reopen visual studio code to set the jupyter kernel successfully. -## Overview -This package contains a notebook and the python modules to support the generation of ApplicationHub configurations for a minikube cluster. For more information about ApplicationHub please check this [link](https://github.com/EOEPCA/application-hub-context) - -## Examples: -The [example](./examples/) folder contains a notebook and the python modules to support the generation of ApplicationHub configurations. - - -## License -`apphub-configurator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. diff --git a/apphub-configurator/examples/config-generator.ipynb b/apphub-configurator/examples/config-generator.ipynb index 6488944..cd1db94 100644 --- a/apphub-configurator/examples/config-generator.ipynb +++ b/apphub-configurator/examples/config-generator.ipynb @@ -43,6 +43,15 @@ "## Initial dependencies" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install apphub-configurator" + ] + }, { "cell_type": "code", "execution_count": 1, @@ -1316,7 +1325,7 @@ ], "metadata": { "kernelspec": { - "display_name": "apphub_configurator", + "display_name": "base", "language": "python", "name": "python3" }, @@ -1330,7 +1339,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.12" + "version": "3.11.5" }, "orig_nbformat": 4 }, From 6c64e898bff251f9f76821d2934c94f1df2d9c1e Mon Sep 17 00:00:00 2001 From: pmembari Date: Mon, 7 Apr 2025 16:00:34 +0200 Subject: [PATCH 55/86] docs: improve documentation --- docs/app-hub-configurator.md | 33 ---- docs/configuration.md | 337 ++++++++++------------------------- docs/hands-on.md | 6 + docs/jupyterhub-api.md | 117 ++++-------- mkdocs.yml | 3 +- 5 files changed, 132 insertions(+), 364 deletions(-) delete mode 100644 docs/app-hub-configurator.md create mode 100644 docs/hands-on.md diff --git a/docs/app-hub-configurator.md b/docs/app-hub-configurator.md deleted file mode 100644 index e12c8cd..0000000 --- a/docs/app-hub-configurator.md +++ /dev/null @@ -1,33 +0,0 @@ -## ApplicationHub configuration generator -This application aim to produce a `config.yml` file to generate all the configuration needs to be set for deployment of App Hub on a remote cluster using Devops tools. -# apphub-configurator - -[![PyPI - Version](https://img.shields.io/pypi/v/apphub-configurator.svg)](https://pypi.org/project/apphub-configurator) -[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/apphub-configurator.svg)](https://pypi.org/project/apphub-configurator) - ------ - -## Table of Contents - -- [Installation](#installation) -- [Overview](#overview) -- [Examples](#Examples) -- [License](#license) - -## Installation -Create a hatch environment with the dependencies listed in the file `pyproject.toml`. - -```console -hatch shell prod -hatch -e prod run python -m ipykernel install --user --name=apphub_configurator --display-name "apphub_configurator" -``` -> Note: you may need to reopen visual studio code to set the jupyter kernel successfully. -## Overview -This package contains a notebook and the python modules to support the generation of ApplicationHub configurations for a minikube cluster. For more information about ApplicationHub please check this [link](https://github.com/EOEPCA/application-hub-context) - -## Examples: -The [example](../apphub-configurator/examples/) folder contains a notebook and the python modules to support the generation of ApplicationHub configurations. - -## License - -`apphub-configurator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. diff --git a/docs/configuration.md b/docs/configuration.md index 788cc52..b54c189 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,246 +1,95 @@ # Configuration -## Profiles - -A profile entry is defined as an entry in the `config.yml` file: - -```yaml -profiles: -- id: profile_1 - ... -- id: profile_2 - ... -``` - -A profile is defined with: - -```yaml -# an identifier -id: profile_1 -# the group(s) this profile is included in: -groups: -- group-A -- group-B -# a definition block, see config c.KubeSpawner.profile_list in the kubespawner documentation -definition: - display_name: Profile 1 - slug: profile_1_slug - default: False - kubespawner_override: - cpu_limit: 4 - mem_limit: 8G - image: eoepca/iat-jupyterlab:main -# the default URL to redirect (optional) -default_url: "lab" -# spawned pod environment variables (optional) -pod_env_vars: - A: 10 - B: 20 - GITLAB_TOKEN: - from_config_map: - name: gitlabenv - key: GITLAB_TOKEN -# a list of volumes (optional) -volumes: [] -# a list of config maps (optional) -config_maps: [] -# kubernetes node pool selector -node_selector: {} -``` - -## Understanding the groups - -The `groups` element allows a granular access to different apps. - -Users and groups can be managed via UI in the `/hub/admin` deployment URL or via API (see https://jupyterhub.readthedocs.io/en/stable/reference/rest-api.html#/) - -With a configuration like: - -```yaml -profiles: -- id: profile_1 - groups: - - group-A - - group-B - definition: - ... -- id: profile_2 - groups: - - group-B - definition: - ... -``` - -A user that belongs to `group-A` is: - -- able to spawn the application defined in the `profile_1`. -- not able to spawn the application defined in the `profile_2`. - -A user belonging to `group-B` is: - -- able to spawn the application defined in the `profile_1`. -- able to spawn the application defined in the `profile_2`. - - -## Profile definition - -A `profile definition` example is shown below: - -```yaml -definition: - display_name: Profile 1 - slug: profile_1_slug - default: False - kubespawner_override: - cpu_limit: 4 - mem_limit: 8G - image: eoepca/iat-jupyterlab:main -``` - -The `kubespawner_override` can also include `extra_resource_limits` and `extra_resource_guarantees` to provide GPUs: - -```yaml -kubespawner_override: - cpu_limit: 4 - mem_limit: 8G - image: eoepca/iat-jupyterlab:main - extra_resource_limits: {"nvidia.com/gpu": "1"} - extra_resource_guarantees: {"nvidia.com/gpu": "1"} -``` - -## Environment variables - -Environment variables can be defined by - -* Providing a fixed global value, or -* Referencing a key in a config map (which must exist in the same Kubernetes namespace, - e.g. an individual user's namespace) - -The two ways of defining environment variables work as follows (global and specific): - -```yaml -pod_env_vars: - global: global_value - specific: - from_config_map: - name: config_map_name - key: config_map_key -``` - - -## Volumes - -A volume is defined with: - -```yaml -name: volume-workspace -claim_name: claim-workspace -size: 10Gi -storage_class: "scw-bssd" -access_modes: -- "ReadWriteOnce" -volume_mount: - name: volume-workspace - mount_path: "/workspace" -persist: true -``` - -**Note**: if the _PVC_ does not exist it is created. - -If the `persist` boolean flag is set to `false`, both the _PVC_ and _Volume_ are deleted. - -## ConfigMaps - -An existing configMap to be mounted on the spawned pod is defined with: - -```yaml -name: aws-credentials -key: aws-credentials -mount_path: /home/jovyan/.aws/credentials -default_mode: 0660 -readonly: true -``` - -A new configMap with the content inline to be mounted on the spawned pod is defined with: - -```yaml -name: aws-credentials -key: aws-credentials -mount_path: /home/jovyan/.aws/credentials -default_mode: 0660 -readonly: true -content: -| - [default] - aws_access_key_id=5...b - aws_secret_access_key=c7...3 -``` - -## Roles and role bindings - -Roles and role bindings are defined as follows (following the model of the Kubernetes [RBAC authorisation](https://kubernetes.io/docs/reference/access-authn-authz/rbac/)): - -```yaml -role_bindings: - - name: pod_reader_role_binding - subjects: - - name: default - kind: ServiceAccount - role: - name: pod_reader_role - api_group: rbac.authorization.k8s.io - verbs: - - get - - list - - watch - resources: - - pods - - pods/log - persist: false -``` - -If the `persist` boolean flag is set to `false`, both the _role_ and _role binding_ are deleted when the pod is disposed. - -## Image Pull Secrets - -Image Pull Secrets are defined with: - -```yaml -image_pull_secrets: - - data: "eyJhdXRocyI6eyJjci50ZXJyYWR1ZS5jkdWWWVXTnpiMVZuTm14VmJUWkllWGhUIn19fQ==" - name: "cr-config" - persist: false -``` - -## Init Containers - -This init container use an `.init.sh` mounted from a configMap and a volume to contextualize the environment: - -```yaml -init_containers: - - command: - - sh - - /opt/init/.init.sh - image: bitnami/git:latest - name: init-file-on-volume - volume_mounts: - - mount_path: /workspace - name: workspace-volume - - mount_path: /opt/init/.init.sh - name: init - sub_path: init -``` - -The configMap `init` is created with: - - -```yaml -- config_maps: - - name: init - key: init - content: |- - echo "# Hello World!" > /workspace/README.md - default_mode: null - mount_path: /opt/init/.init.sh - persist: false - readonly: true -``` +## Overview + +In the Application Hub, configuration is vital for defining Kubernetes objects through YAML files, ensuring consistent and scalable deployments. This approach allows users to specify the desired state of resources, with Kubernetes managing the necessary adjustments to align with this state. + +Developing Kubernetes manifests or configuring the cluster using YAML files can be challenging and prone to errors. To simplify this process, a Python-based configuration generator can be used to produce clean, well-structured YAML files. This reduces the risk of misconfiguration and improves maintainability. +Users can programmatically define and customize various Kubernetes resources(e.g.**Pods**, **Volumes**, and **ConfigMaps**), well as handling the **External Secrets Operator**, define **profiles**, and manage **Helm releases**. This approach offers flexibility and empowers users to tailor deployments to their specific requirements. + +## Apphub-configurator + +The Configuration Generator is a Python module designed to facilitate the creation of configuration files for Application Hub deployments. It offers various utilities to streamline the configuration process. +### Python utilities: +- **`load_config_map(name, key, file_name, mount_path)`**: Reads a specified file and returns a `ConfigMap` object with the file's content, intended for use within Kubernetes configurations. + +- **`load_manifests(name, key, file_path)`**: Loads a YAML file containing Kubernetes manifests and returns a `Manifest` object. This is useful for deploying one or multiple Kubernetes objects, such as Roles, RoleBindings, ServiceAccounts, and Releases. + +- **`create_init_container(image, volume, mount_path)`**: Sets up an init container that executes specified commands during pod initialization. + +- **`load_init_script(file_name)`**: Loads a bash script intended for pod initialization and returns a `ConfigMap` object. + +By utilizing these utilities, users can efficiently generate and manage configuration files, ensuring consistent and scalable deployments within the Application Hub environment. + +### Kubernetes Object Data Classes + +The module defines several Pydantic-based data classes representing Kubernetes objects, facilitating structured and validated configuration management: + +- **`ConfigMapKeyRef`**: Represents a reference to a specific key within a Kubernetes ConfigMap, including the `name` of the ConfigMap and the `key` within it. + +- **`ConfigMapEnvVarReference`**: Encapsulates a reference to a ConfigMap key, used to define environment variables in Kubernetes pods sourced from ConfigMap data. + +- **`SubjectKind`**: An enumeration specifying the type of subject (e.g., `ServiceAccount`, `User`) for role-based access control (RBAC) configurations. + +- **`Verb`**: An enumeration listing the permissible actions (e.g., `get`, `list`, `create`) for RBAC roles and role bindings. + +- **`Subject`**: Defines a subject in RBAC, comprising a `name` and a `kind` (type). + +- **`Role`**: Represents a Kubernetes Role, detailing the `name`, a list of `resources` it governs, permissible `verbs` (actions), and optional `api_groups`. + +- **`RoleBinding`**: Binds a `Role` to specific `subjects`, assigning the defined permissions within a namespace. It includes a `name`, a list of `subjects`, the associated `role`, and a `persist` flag indicating whether the binding should be saved. + +- **`VolumeMount`**: Specifies how a volume is mounted into a pod, detailing the `name` of the volume and the `mount_path` within the container's filesystem. + +- **`InitContainerVolumeMount`**: Extends `VolumeMount` by adding a `sub_path`, allowing a specific file or directory within the volume to be mounted at the desired path. + +- **`Volume`**: Describes a Kubernetes volume, including its `name`, `claim_name` for persistent volumes, `size`, `storage_class`, `access_modes`, an associated `volume_mount`, and a `persist` flag. + +- **`Manifest`**: Represents a collection of Kubernetes objects, encompassing a `name`, a `key`, optional `content` (a list of dictionaries representing Kubernetes resources), and a `persist` flag. + +- **`ConfigMap`**: Defines a Kubernetes ConfigMap, including a `name`, `key`, optional `mount_path` for mounting the ConfigMap as a file, an optional `default_mode` for file permissions, a `readonly` flag, optional `content`, and a `persist` flag. + +- **`KubespawnerOverride`**: Allows customization of Kubernetes spawner settings for JupyterHub, specifying resource limits and guarantees such as `cpu_limit`, optional `cpu_guarantee`, `mem_limit`, optional `mem_guarantee`, `image`, and dictionaries for `extra_resource_limits` and `extra_resource_guarantees`. + +- **`InitContainer`**: Details an initialization container within a pod, specifying its `name`, `image`, a list of `command` arguments to execute, and a list of `volume_mounts` (which can be either `VolumeMount` or `InitContainerVolumeMount`). + +- **`ProfileDefinition`**: Describes a user profile within the Application-Hub, including a `display_name`, an optional `description`, a `slug` for identification, a `default` flag, and `kubespawner_override` settings. + +- **`ImagePullSecret`**: Represents a secret used for pulling container images, including a `name`, a `persist` flag, and optional `data` (e.g., authentication credentials). + +- **`SecretMount`**: Specifies a secret to be mounted into a pod, detailing the `name` of the secret, the `mount_path` within the container, an optional `sub_path` to a specific file within the secret, and a `default_mode` for file permissions. + +- **`Profile`**: Encapsulates a user profile, including an `id`, a list of `groups` the user belongs to, a `definition` (of type `ProfileDefinition`), optional lists of `config_maps` and `volumes`, optional environment variable settings (`pod_env_vars`), optional `default_url`, `node_selector` for pod scheduling, optional `role_bindings`, `image_pull_secrets`, `init_containers`, `manifests`, environment variables sourced from ConfigMaps and Secrets, and optional `secret_mounts`. + +- **`Config`**: Serves as the root configuration object, containing a list of `profiles` (of type `Profile`). + +These data classes provide a structured approach to defining Kubernetes resources. + +### Examples: +The user can follow the examples provided to setup different profiles on Application Hub through the provided [notebook](../apphub-configurator/examples/config-generator.ipynb) under example folder. In the notebook, the user is able to configure different profile including: +- Coder +- Coder with a init.sh +- Jupyter Lab +- JupyterLab Plus +- E-learning +- QGIS + +### Output format: + +The `apphub-configurator` package generates a `config.yaml` file to define various profiles for your application deployment. Each profile includes several key attributes that need to be configured: + +- **id**: the profile identifier of your app +- **groups**: the group list containing the users groups that can use the declared app +- **definition**: display name, reference slug identifying the app, cpu/ram requirements alloted for it, reference docker image for the app-level +- **config-maps**: definition of env variables expressed as ':' or config/secret files together with their mount_path, access, default_mode +- **volumes**: handle the volumes, their persistency, their kubernetes access type (e.g. ReadWriteOnce, ReadWriteMany, ...), their size claim and their mount_path +- **pod_env_vars**: Handle the environment variables for the pod. +- **default_url**: default uri where to find the app +- **node_selector**: identifies on which node pool the app is executed +- **role_bindings**: A list of `RoleBinding` objects that associate users or groups with specific roles within the Kubernetes cluster, controlling access to resources. +- **image_pull_secrets**: A list of `ImagePullSecret` objects containing credentials for pulling private Docker images required by the application. +- **init_containers**: A list of `InitContainer` objects specifying containers that run before the main application containers, typically used for initialization tasks. +- **manifests**: A list of `Manifest` objects representing Kubernetes manifests to be applied during deployment, allowing for the creation and management of various Kubernetes resources. +- **env_from_config_maps**: A list of strings specifying the names of ConfigMaps whose data should be exposed as environment variables to the application pods. +- **env_from_secrets**: A list of strings specifying the names of Secrets whose data should be exposed as environment variables to the application pods. +- **secret_mounts**: A list of `SecretMount` objects defining Secrets to be mounted as files within the application pods, providing secure storage for sensitive data. diff --git a/docs/hands-on.md b/docs/hands-on.md new file mode 100644 index 0000000..7229224 --- /dev/null +++ b/docs/hands-on.md @@ -0,0 +1,6 @@ +# Hands-on + +- Config generator: +Open the an example [notebook](../apphub-configurator/examples/config-generator.ipynb) to generate a `config.yaml` with your desired profile configuration +- Add `eoepca/application-hub` from helm repositories. +- Deploy the ApplicationHub on a remote cluster using skaffold. \ No newline at end of file diff --git a/docs/jupyterhub-api.md b/docs/jupyterhub-api.md index 7b1ff86..448600f 100644 --- a/docs/jupyterhub-api.md +++ b/docs/jupyterhub-api.md @@ -1,124 +1,71 @@ # Using JupyterHub API -See [JupyterHub documentation](https://jupyterhub.readthedocs.io/en/stable/reference/rest-api.html) for more information on the JupyterHub API. +The [JupyterHub REST API](https://jupyterhub.readthedocs.io/en/stable/reference/rest-api.html) provides programmatic access to many of JupyterHub’s core functions — including user and group management, server control, and more. This allows administrators and automation tools to manage JupyterHub instances efficiently. -The code examples below show how the JupyterHub REST API can be used with Python and the `requests` module. +This section outlines how to interact with the JupyterHub API using Python and the `requests` library, with practical examples to handle tasks such as: -## Obtain an API token +- Authenticating and retrieving an API token +- Managing user groups (create, list, add/remove users) +- Starting named servers for specific users -In order to use the API, an API token must be obtained. This requires the username and password of an admin or other user with the necessary privileges. +--- -```python -import requests - -endpoint = "https://app-hub.acme.com/hub/api" +## Overview -headers = {'Content-Type': 'application/json'} +The following Python snippets demonstrate how to interact with key features of the JupyterHub API: -data = { - "username": "" - "password": "" -} +- **API Token Retrieval** : Authenticate using a username/password and receive a token for further use +- **Group Management**: Create, list, and modify user groups | +- **Named Server Control**: Launch Jupyter environments tied to specific users and profiles | -r = requests.get(f"{endpoint}/authorizations/token", headers=headers, json=data, verify=False) -token = r.json()['token'] -``` -## Groups +## 1. API Token Retrieval -### List groups +Before accessing the API, you must obtain an access token by authenticating with valid credentials. This token is required for all subsequent requests. ```python -import requests - -endpoint = "https://app-hub.acme.com/hub/api" -token = "d...8" # previously obtained API token - -headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {token}'} - -r = requests.get(f"{endpoint}/groups", headers=headers, verify=False) - -r.json() +r = requests.get(f"{endpoint}/authorizations/token", headers=headers, json=data) ``` -### Create group +> ⚠️ This method may depends on your JupyterHub authenticator setup (e.g., OAuth, native password, etc.). -```python -import requests -endpoint = "https://app-hub.acme.com/hub/api" -token = "d...8" # previously obtained API token +## 2. Group Management -headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {token}'} +The group API allows administrators to organize users into logical groups for resource or permission control. -group = 'group-a' +### a. List Existing Groups -r = requests.post(f"{endpoint}/groups/{group}", headers=headers, verify=False) -r.status_code -r.json() +```python +r = requests.get(f"{endpoint}/groups", headers=headers) ``` -### Add user to group - +### b. Create a New Group ```python -import requests - -endpoint = "https://app-hub.acme.com/hub/api" -token = "d...8" # previously obtained API token - -headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {token}'} - -group = 'group-a' - -data = { - "users": ["mrossi"] -} - -r = requests.post(f"{endpoint}/groups/{group}/users", headers=headers, json=data, verify=False) -r.status_code +r = requests.post(f"{endpoint}/groups/{group}", headers=headers) ``` -### Remove user from group +### c. Add Users to a Group ```python -import requests - -endpoint = "https://app-hub.acme.com/hub/api" -token = "d...8" # previously obtained API token - -headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {token}'} - -group = 'group-a' - -data = { - "users": ["mrossi"] -} - -r = requests.delete(f"{endpoint}/groups/{group}/users", headers=headers, json=data, verify=False) - -r.json() +r = requests.post(f"{endpoint}/groups/{group}/users", headers=headers, json=data) ``` -## Named servers - -### Create a named server +### d. Remove Users from a Group ```python -import requests - -endpoint = "https://app-hub.acme.com/hub/api" -token = "d...8" # previously obtained API token +r = requests.delete(f"{endpoint}/groups/{group}/users", headers=headers, json=data) +``` -headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {token}'} -data = {"profile": "profile_1_slug"} +## 3. Named Server Management -server_name = "labs" +You can create named servers tied to specific profiles. This is useful for launching custom environments under user control. -user = "mrossi" -r = requests.post(f"{endpoint}/users/{user}/servers/{server_name}", headers=headers, json=data, verify=False) +### Create a Named Server for a User -r.status_code, r.text +```python +r = requests.post(f"{endpoint}/users/{user}/servers/{server_name}", headers=headers, json=data) ``` diff --git a/mkdocs.yml b/mkdocs.yml index 4e7b6f2..2483cfc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -25,6 +25,5 @@ nav: - Introduction: 'index.md' - Kubernetes: 'k8s.md' - Configuration: 'configuration.md' - - AppHub Configurator Application Package: 'app-hub-configurator.md' - JupyterHub API: 'jupyterhub-api.md' - - Config: 'apphub-config-general.md' + - Hands-on: 'hands-on.md' From 98767b6277af190652d52afdeb53ae267c60c1cb Mon Sep 17 00:00:00 2001 From: pmembari Date: Mon, 7 Apr 2025 16:01:57 +0200 Subject: [PATCH 56/86] ci: ignore create new apphub-configurator --- .github/workflows/package.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index 73c68e1..07c4397 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -2,7 +2,7 @@ name: Publish to PyPI on: push: - branches: [ "ESAEOEPCA-236", "patch-1" ] + branches: ["patch-1" ] paths: - 'apphub-configurator/**' From 066855fc3c792ddaef3226a3297c148558873b96 Mon Sep 17 00:00:00 2001 From: pmembari Date: Mon, 7 Apr 2025 16:07:03 +0200 Subject: [PATCH 57/86] chore: remove redundant files --- docs/apphub-config-general.md | 354 ---------------------------------- 1 file changed, 354 deletions(-) delete mode 100644 docs/apphub-config-general.md diff --git a/docs/apphub-config-general.md b/docs/apphub-config-general.md deleted file mode 100644 index 4a449ca..0000000 --- a/docs/apphub-config-general.md +++ /dev/null @@ -1,354 +0,0 @@ -# ApplicationHubConfiguration - -The config-generator folder contains the hatch module for the jupyter notebook to support the generation of ApplicationHub configurations and its documentation is in the dedicated readme. -The definition of an application in the config.yml is addressed according to this pattern: - -```yaml -- id: - groups: - - - - - definition: - display_name: - slug: - default: - kubespawner_override: - cpu_limit: - mem_limit: - image: - default_url: - node_selector: - k8s.provider.com/pool-name: - config_maps: - - name: - key: - mount_path: - default_mode: - readonly: - pod_env_vars: - mykey1: "myvalue1" - mykey2: "myvalue2" - mykey3: - from_config_map: - name: mykeyname - key: mykey3 - - name: my-inlined-file-name - key: my-inlined-file-key - content: |- - #inline-content - mount_path: my-inlined-file-path - default_mode: my-inlined-access-mode - readonly: my-inlined-file-read-mode - volumes: - - name: my-volume-name - claim_name: my-volume-claim-name - size: my-claim-size-in-Gi - storage_class: - access_modes: - - "" - volume_mount: - name: "volume-mount-name" - mount_path: "volume-mount-destination-path" - persist: - -``` - -Breakdown: - -- **id**: the profile identifier of your app -- **groups**: the group list containing the users groups that can use the declared app -- **definition**: display name, reference slug identifying the app, cpu/ram requirements alloted for it, reference docker image for the app-level -- **default_url**: default uri where to find the app -- **node_selector**: identifies on which node pool the app is executed -- **config-maps**: definition of env variables expressed as ':' or config/secret files together with their mount_path, access, default_mode -- **volumes**: handle the volumes, their persistency, their kubernetes access type (e.g. ReadWriteOnce, ReadWriteMany, ...), their size claim and their mount_path - - -## Remote Desktop - - -```yaml -- id: profile_studio_desktop_native - groups: - - group-b - definition: - display_name: IGA - Remote Desktop base - slug: ellip_studio_desktop_native - default: False - kubespawner_override: - cpu_limit: 1 - mem_limit: 4G - image: eoepca/iga-remote-desktop:develop - default_url: "desktop" - node_selector: {} - config_maps: - - name: aws-credentials - key: aws-credentials - mount_path: /home/jovyan/.aws/credentials - default_mode: "0660" - readonly: true - - name: aws-config - key: aws-config - mount_path: /home/jovyan/.aws/config - default_mode: "0660" - readonly: true - - name: docker-config - key: docker-config - mount_path: /home/jovyan/.docker/config.json - default_mode: "0660" - readonly: true - volumes: - - name: volume-workspace - claim_name: claim-workspace - size: 10Gi - storage_class: "standard" - access_modes: - - "ReadWriteOnce" - volume_mount: - name: volume-workspace - mount_path: "/workspace" - persist: true -``` - -## JupyterLab - -```yaml -- id: profile_studio_labs - groups: - - group-c - definition: - display_name: IAT - Interactive Analysis Tool (JupyterLab) - slug: studio_labs_slug - default: False - kubespawner_override: - cpu_limit: 1 - mem_limit: 4G - image: eoepca/iat-jupyterlab:main - default_url: "lab" - config_maps: - - name: aws-credentials - key: aws-credentials - mount_path: /home/jovyan/.aws/credentials - default_mode: "0660" - readonly: true - - name: aws-config - key: aws-config - mount_path: /home/jovyan/.aws/config - default_mode: "0660" - readonly: true - - name: docker-config - key: docker-config - mount_path: /home/jovyan/.docker/config.json - default_mode: "0660" - readonly: true - - name: new-cm - key: new-cm - mount_path: /home/jovyan/new-cm - default_mode: "0660" - readonly: true - content: |- - Hello World! - persist: false - volumes: - - name: volume-workspace - claim_name: claim-workspace - size: 10Gi - storage_class: "standard" - access_modes: - - "ReadWriteOnce" - volume_mount: - name: volume-workspace - mount_path: "/workspace" - persist: true - pod_env_vars: - A: "10" - B: "20" - node_selector: {} - role_bindings: - - name: pod_reader_role_binding - subjects: - - name: default - kind: ServiceAccount - role: - name: pod_reader_role - api_group: rbac.authorization.k8s.io - verbs: - - get - - list - - watch - resources: - - pods - persist: false -``` - -## CodeServer - -```yaml -- id: profile_studio_coder - groups: - - group-b - definition: - display_name: PDE - Processor Development Environment (Code Server) - slug: ellip_studio_coder_slug - default: False - kubespawner_override: - cpu_limit: 1 - mem_limit: 8G - image: eoepca/pde-code-server@sha256:98b77ef39830aec162d9a30311957a48f2b4010930e999969656db11fa788a1b - node_selector: {} - volumes: - - name: volume-workspace - claim_name: claim-dev - size: 10Gi - storage_class: "standard" - access_modes: - - "ReadWriteOnce" - volume_mount: - name: volume-workspace - mount_path: "/workspace" - persist: true - configMaps: - - name: bash-rc - key: bash-rc - content: |- - alias ll="ls -l" - . /home/jovyan/.bashrc - mount_path: /workspace/.bashrc - default_mode: "0660" - readonly: true - - name: aws-credentials - key: aws-credentials - mount_path: /home/jovyan/.aws/credentials - default_mode: "0660" - readonly: true - - name: aws-config - key: aws-config - mount_path: /home/jovyan/.aws/config - default_mode: "0660" - readonly: true - - name: docker-config - key: docker-config - mount_path: /home/jovyan/.docker/config.json - default_mode: "0660" - readonly: true -``` - -To have a compliant candidate app to be added to the AppHub, it must comply with the proper Dockerfile definition before reference its produced docker image into the configuration. -In addition it must be pushed on a registry accessible from the AppHub deployment. -These images must expose a service on a given port. -There are two options to have JupyterHub to proxy these applications: - -- **jupyter-server-proxy** -- **jhsingle-native-proxy** - - - -## Jupyter-server-proxy Dockerfile approach -The jupyter-server-proxy exposes the application alongside with the JupyterLab instance whilst jhsingle-native-proxy proxies the application without a running JupyterLab instance. - -In example the eoepca/iga-remote-desktop_qgis uses the approach with jupyter-server-proxy. -It relies on the [jupyter-remote-desktop-proxy](https://github.com/jupyterhub/jupyter-remote-desktop-proxy) and [app-repo](https://github.com/EOEPCA/iga-remote-desktop-qgis/) - -Typically the Dockerfile starts with eoepca/iga-remote-desktop base image and then the specific app-level dependencies are added - -``` -FROM eoepca/iga-remote-desktop -USER root - -#Install your app with dependencies and add to PATH - -RUN chown -R $NB_UID:$NB_GID $HOME -USER $NB_USER -``` - -## Jhsingle-native-proxy Dockerfile approach -The jhsingle-native-proxy approach instead is based on a Dockerfile following this flow: - -``` -FROM python -RUN pip3 install \ - jhsingle-native-proxy>=0.0.9 \ - - -# create a user, since we don't want to run as root -RUN useradd -m jovyan -ENV HOME=/home/jovyan -WORKDIR $HOME -USER jovyan - -EXPOSE -CMD ["jhsingle-native-proxy", "--destport", "", "", "", "{--}server.port", "{port}", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", ""] -``` - -It relies on the [jhsingle-native-proxy](https://github.com/ideonate/jhsingle-native-proxy) and [app-repo](https://github.com/EOEPCA/iga-streamlit-demo) - -So after having pip installed the jhsingle-native-proxy, the execution entrypoint in the Dockerfile is typically: - -``` -CMD ["jhsingle-native-proxy", "--destport", "", "", "", "{--}server.port", "{port}", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", ""] -``` - -E.g. - -``` -CMD ["jhsingle-native-proxy", "--destport", "8505", "streamlit", "hello", "{--}server.port", "8888", "{--}server.headless", "True", "{--}server.enableCORS", "False", "--port", "8888"] -``` - -Params breakdown: - -**--destport**: specifies the destination port number where the target APPLICATION (in this case, Streamlit) will run. - -**--authtype**: none disables authentication, meaning the application will not require authentication through JupyterHub. - -**--port**: This sets the PROXY server's external port number to $port. - -The {--} syntax is used to pass arguments to the app, e.g. Streamlit, command itself: -{--}server.port {port}: server.port {port} sets the port number on which the Streamlit SERVER will run. Equivalently {--}bind-addr 0.0.0.0:$port - -**{--}server.headless**: True ensures that Streamlit runs in headless mode, meaning it does not require a graphical user interface (GUI). - -**{--}server.enableCORS**: False disables Cross-Origin Resource Sharing (CORS), which is useful when running behind a proxy. - - -In addition to the usual jhsingle-native-proxy params list an could require an user data path mapping and its VS code loading, e.g. with **{--}user-data-dir** & CODE\_SERVER\_WS: - -``` -CODE_SERVER_WS="/workspace" -jhsingle-native-proxy --port 8888 --destport $destport code-server {--}auth none {--}bind-addr 0.0.0.0:$destport {--}user-data-dir /workspace $CODE_SERVER_WS -``` - - -## Private app images pulling -The [docker-config](https://github.com/EOEPCA/helm-charts-dev/blob/f9c77a1e850c8e061de8b113ddcdcfd367b7fc0e/charts/application-hub/files/hub/config.yml#L198) configmap contains container registry authorization definitions. -It is a config map mounted on the pod. -This file enables the pulling of the app images from container registries. - -``` -{ - "auths": { - "my-registry-1.com": { - "auth": "" - }, - "my-registry-2.com": { - "auth": "" - } - } -} -``` - -Example: - -``` -{ - "auths": { - "my-private-registry.com": { - "auth": "am…g1Ml4=" - }, - "docker-co.domain.com": { - "auth": "Zm…Q==" - } - } -} -``` - -In this example my-private-registry.com and docker-co.domain.com are added together with their credentials to let the AppHub deployment pull some of their images as propedeutic action before the app exposure. From b35e80de5a6c16b83d81dc216415837ffcf12c64 Mon Sep 17 00:00:00 2001 From: pmembari Date: Mon, 7 Apr 2025 16:12:45 +0200 Subject: [PATCH 58/86] docs: edit configuration --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index b54c189..4769500 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -31,7 +31,7 @@ The module defines several Pydantic-based data classes representing Kubernetes o - **`SubjectKind`**: An enumeration specifying the type of subject (e.g., `ServiceAccount`, `User`) for role-based access control (RBAC) configurations. -- **`Verb`**: An enumeration listing the permissible actions (e.g., `get`, `list`, `create`) for RBAC roles and role bindings. +- **`Verb`**: An enumeration listing the allowed actions (e.g., `get`, `list`, `create`) for RBAC roles and role bindings. - **`Subject`**: Defines a subject in RBAC, comprising a `name` and a `kind` (type). From 79d95b5fc53418512fcaa2c187fc16cd559bbb3b Mon Sep 17 00:00:00 2001 From: pmembari Date: Mon, 7 Apr 2025 17:05:45 +0200 Subject: [PATCH 59/86] feat: bump a new version 0.0.7 --- apphub-configurator/src/apphub_configurator/__about__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apphub-configurator/src/apphub_configurator/__about__.py b/apphub-configurator/src/apphub_configurator/__about__.py index 06f57eb..14ed908 100644 --- a/apphub-configurator/src/apphub_configurator/__about__.py +++ b/apphub-configurator/src/apphub_configurator/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2025-present pmembari # # SPDX-License-Identifier: MIT -__version__ = "0.0.6" +__version__ = "0.0.7" From b982ae80ddda50923f54037d89d1c97e824abe39 Mon Sep 17 00:00:00 2001 From: pmembari Date: Mon, 7 Apr 2025 17:16:06 +0200 Subject: [PATCH 60/86] docs: edit configuration --- docs/configuration.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 4769500..24334fd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -67,11 +67,17 @@ These data classes provide a structured approach to defining Kubernetes resource ### Examples: The user can follow the examples provided to setup different profiles on Application Hub through the provided [notebook](../apphub-configurator/examples/config-generator.ipynb) under example folder. In the notebook, the user is able to configure different profile including: + - Coder + - Coder with a init.sh + - Jupyter Lab + - JupyterLab Plus + - E-learning + - QGIS ### Output format: From f57fa0c524ce339d68d85f6bcf96c237f0cdbb96 Mon Sep 17 00:00:00 2001 From: pmembari Date: Mon, 7 Apr 2025 17:18:28 +0200 Subject: [PATCH 61/86] ci: trigger app-hub-config on develop and main --- .github/workflows/package.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index 07c4397..56f358b 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -2,7 +2,7 @@ name: Publish to PyPI on: push: - branches: ["patch-1" ] + branches: [develop, main, "patch-1" ] paths: - 'apphub-configurator/**' From 1449eaaf4c21e070d982e44678be7866a3eaaf6c Mon Sep 17 00:00:00 2001 From: pmembari Date: Mon, 7 Apr 2025 17:21:15 +0200 Subject: [PATCH 62/86] docs: edit configuration --- docs/configuration.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 24334fd..b058c00 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -68,17 +68,17 @@ These data classes provide a structured approach to defining Kubernetes resource ### Examples: The user can follow the examples provided to setup different profiles on Application Hub through the provided [notebook](../apphub-configurator/examples/config-generator.ipynb) under example folder. In the notebook, the user is able to configure different profile including: -- Coder +* Coder -- Coder with a init.sh +* Coder with a init.sh -- Jupyter Lab +* Jupyter Lab -- JupyterLab Plus +* JupyterLab Plus -- E-learning +* E-learning -- QGIS +* QGIS ### Output format: From 36b4340c8c9f4dbab0fdf5d5c7d4cc150371dd10 Mon Sep 17 00:00:00 2001 From: pmembari Date: Mon, 7 Apr 2025 17:25:01 +0200 Subject: [PATCH 63/86] ci: build pypi package when ci is changed --- .github/workflows/package.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index 56f358b..065bdad 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -5,6 +5,7 @@ on: branches: [develop, main, "patch-1" ] paths: - 'apphub-configurator/**' + - '.github/workflows/package.yaml' release: types: [published] From a96c5808cf00b6ae253561a1b162671cef922687 Mon Sep 17 00:00:00 2001 From: pmembari Date: Tue, 8 Apr 2025 10:10:51 +0200 Subject: [PATCH 64/86] docs: adding example to doc --- docs/examples/config-generator.ipynb | 1348 ++++++++++++++++++++++++++ docs/examples/generate-config.sh | 4 + docs/examples/generate_config.py | 168 ++++ docs/hands-on.md | 2 +- 4 files changed, 1521 insertions(+), 1 deletion(-) create mode 100644 docs/examples/config-generator.ipynb create mode 100755 docs/examples/generate-config.sh create mode 100644 docs/examples/generate_config.py diff --git a/docs/examples/config-generator.ipynb b/docs/examples/config-generator.ipynb new file mode 100644 index 0000000..cd1db94 --- /dev/null +++ b/docs/examples/config-generator.ipynb @@ -0,0 +1,1348 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Generate App Hub's configuration\n", + "\n", + "This notebook aim to produce a `config.yml` file to generate all the configuration needs to be set for deployment of App Hub on a remote cluster using Devops tools. \n", + "\n", + "This Notebook can be used for the following requirements:\n", + "\n", + "* Initial dependencies\n", + "* Create the configuration for kubernetes' `PV`s, and `PVC`s(e.g. workspace volume, and calrissian volume)\n", + "* Generate the configuration for kubernetes' config maps\n", + "* Creation of different Profiles \n", + "\n", + "Table of Content:\n", + "- [Initial dependencies](#initial-dependencies)\n", + " - [Configuration](#configuration)\n", + "- [Volumes](#volumes)\n", + " - [Workspace volume](#workspace-volume)\n", + " - [Calrissian volume](#calrissian-volume)\n", + "- [Configmaps](#configmaps)\n", + " - [bash login Configmap](#bash-login-configmap)\n", + " - [bash rc Configmap](#bash-rc-configmap) \n", + " - [QGIS Configmap](#qgis-configmap) \n", + " - [Stage-in/out Configmap](#stage-inout-configmap)\n", + "- [Profiles](#profiles)\n", + " - [Coder](#coder)\n", + " - [Coder with init.sh](#coder-with-initsh)\n", + " - [JupyterLab](#jupyterlab)\n", + " - [JupyterLab Plus](#jupyterlab-plus)\n", + " - [E-Learning](#e-learning)\n", + " - [QGIS](#qgis)\n", + "- [Write Configuration](#write-configuration)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Initial dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install apphub-configurator" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "PosixPath('/home/t2/Desktop/p/EOEPCA/application-hub-context/apphub-configurator/examples')" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import yaml\n", + "from apphub_configurator.models import *\n", + "from pathlib import Path\n", + "import os\n", + "import json\n", + "from pprint import pprint\n", + "current_dir = Path(os.getcwd())\n", + "parent_dir = current_dir.parent\n", + "current_dir" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Configuration\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "storage_class_rwo = \"managed-nfs-storage\"\n", + "storage_class_rwx = \"managed-nfs-storage\"\n", + "\n", + "workspace_volume_size = \"50Gi\"\n", + "calrissian_volume_size = \"50Gi\"" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Volumes\n", + "\n", + "In this section, the user will provide the data class objects for creation of volume:\n", + "- Volume for workspace\n", + "- Volume for calrissian\n", + "These two configuration will be use " + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Workspace Volume\n", + "In this section, the user will providing the configuration of a kubernetes Volume for development environment (i.e workspace). It is important to mention that this volume must keep data persistently. therefore, the user set `persist=True`" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "workspace_volume = Volume( # type: ignore\n", + " name=\"workspace-volume\",\n", + " size=workspace_volume_size,\n", + " claim_name=\"workspace-claim\",\n", + " mount_path=\"/workspace\",\n", + " storage_class=storage_class_rwo,\n", + " access_modes=[\"ReadWriteOnce\"],\n", + " volume_mount=VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"), # type: ignore\n", + " persist=True,\n", + ")\n", + "workspace_volume" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Calrissian Volume\n", + "In this section, the user will configure a **Kubernetes Volume** for Calrissian jobs. Since the job runs within Calrissian and does not require data retention on the Calrissian pod, therefore the user should set `persist=False`. " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "calrissian_volume = Volume( # type: ignore\n", + " name=\"calrissian-volume\",\n", + " claim_name=\"calrissian-claim\",\n", + " size=calrissian_volume_size,\n", + " storage_class=storage_class_rwx,\n", + " access_modes=[\"ReadWriteMany\"],\n", + " volume_mount=VolumeMount(name=\"calrissian-volume\", mount_path=\"/calrissian\"), # type: ignore\n", + " persist=False,\n", + ")\n", + "calrissian_volume" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ConfigMaps\n", + "\n", + "In this section, the user will provide the configuration for some kubernetes configmaps that is commonly used in this notebook including:\n", + "- bash login Configmap\n", + "- bash rc Configmap\n", + "- QGIS Configmap\n", + "- Stage-in/out Configmap\n", + "These configmaps will be mounted as files on different pods. " + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### bash login Configmap\n", + "\n", + "This configmap file aims to configure the Terminal across different profiles. " + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bash_login_file_path = os.path.join(parent_dir, \"config-maps/bash-login\") \n", + "with open(bash_login_file_path, \"r\") as f:\n", + " content = f.read()\n", + "\n", + "bash_login_cm = ConfigMap( # type: ignore\n", + " name=\"bash-login\",\n", + " key=\"bash-login\",\n", + " content=content,\n", + " readonly=True,\n", + " persist=False,\n", + " mount_path=\"/workspace/.bash_login\",\n", + ")\n", + "bash_login_cm" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### bash-rc Configmap\n", + "This section explains how we configure a **Kubernetes ConfigMap** to manage the `.bashrc` file inside a pod. This config map will:\n", + "- It provides useful aliases and environment settings for users.\n", + "- It ensures a consistent environment across different pods." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "bash_rc_cm_file_path = os.path.join(parent_dir, \"config-maps/bash-rc\") \n", + "with open(bash_rc_cm_file_path, \"r\") as f:\n", + " content = f.read()\n", + "bash_rc_cm = ConfigMap( # type: ignore\n", + " name=\"bash-rc\",\n", + " key=\"bash-rc\",\n", + " content=content,\n", + " readonly=True,\n", + " persist=False,\n", + " mount_path=\"/workspace/.bashrc\",\n", + ")\n", + "bash_rc_cm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### QGIS Configmap\n", + "The cell below will provide a Configmap configuration for QGIS setup." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='mkdir -p /workspace/.config/autostart\\n\\n\\ncat < /workspace/.config/autostart/qgis.desktop \\n[Desktop Entry]\\nEncoding=UTF-8\\nVersion=0.9.4\\nType=Application\\nName=qgis\\nComment=qgis\\nExec=qgis\\nOnlyShowIn=XFCE;\\nRunHook=0\\nStartupNotify=false\\nTerminal=false\\nHidden=false\\nEOF', persist=False)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "init_qgis_file_path = os.path.join(parent_dir, \"config-maps/init-qgis.sh\") \n", + "with open(init_qgis_file_path, \"r\") as f:\n", + " content = f.read()\n", + "\n", + "init_qgis_cm = ConfigMap( # type: ignore\n", + " name=\"init\",\n", + " key=\"init\",\n", + " content=content,\n", + " readonly=True,\n", + " persist=False,\n", + " mount_path=\"/opt/init/.init.sh\",\n", + ")\n", + "init_qgis_cm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Stage-in/out Configmap\n", + "The cell below will provide a Configmap configuration for stage-in/out for [e-learning](#e-learning) profile." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='set -x \\n\\ncd /workspace\\n\\ngit clone \\'https://github.com/eoap/stac-eoap.git\\'\\n\\ncode-server --install-extension ms-python.python \\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nmkdir -p /workspace/User/\\n\\necho \\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\' > /workspace/User/settings.json\\n\\npython -m venv /workspace/.venv\\nsource /workspace/.venv/bin/activate\\n/workspace/.venv/bin/python -m pip install --no-cache-dir stactools rasterio requests stac-asset click-logging tabulate tqdm pystac-client ipykernel loguru scikit-image rio_stac boto3==1.35.23\\n\\n/workspace/.venv/bin/python -m pip install --index-url https://test.pypi.org/simple cwl-wrapper\\n\\n/workspace/.venv/bin/python -m ipykernel install --user --name stac_env --display-name \"Python (STAC)\"\\n\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\nexport AWS_ACCESS_KEY_ID=\"test\"\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\naws s3 mb s3://results --endpoint-url=http://localstack:4566\\n\\nexit 0', persist=False)" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "init_stac_file_path = os.path.join(parent_dir, \"config-maps/init-stac.sh\")\n", + "with open(init_stac_file_path, \"r\") as f:\n", + " content = f.read()\n", + "\n", + "init_stac_cm = ConfigMap( # type: ignore\n", + " name=\"init\",\n", + " key=\"init\",\n", + " content=content,\n", + " readonly=True,\n", + " persist=False,\n", + " mount_path=\"/opt/init/.init.sh\",\n", + ")\n", + "init_stac_cm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Coder init Configmap\n", + "The cell below will provide a Configmap configuration for stage-in/out for [coder](#coder) profile." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "init_cm_file_path = os.path.join(parent_dir, \"config-maps/init.sh\") \n", + "with open(init_cm_file_path, \"r\") as f:\n", + " content = f.read()\n", + "\n", + "init_coder_cm = ConfigMap(\n", + " name=\"init\",\n", + " key=\"init\",\n", + " content=content,\n", + " readonly=True,\n", + " persist=False,\n", + " mount_path=\"/opt/init/.init.sh\",\n", + " default_mode=\"0660\",\n", + ")\n", + "init_coder_cm" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Profiles\n", + "In the following section, the user will generate the configuration for different profiles on a remote cluster including:\n", + "- [Coder](#coder)\n", + "- [Coder with init.sh](#coder-with-initsh)\n", + "- [Jupyter Lab](#jupyterlab)\n", + "- [JupyterLab Plus](#jupyterlab-plus)\n", + "- [E-learning](#e-learning)\n", + "- [QGIS](#qgis)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "profiles = []" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Coder\n", + "In the cell below, the user configures two code-server profiles to be deployed on a remote cluster. One profile (**medium**) has more resources than the other (**small**). Two Kubernetes Volumes, discussed in the [Volume section](#volumes), are assigned, and a ConfigMap is mounted. \n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "coders = {\n", + " \"coder1\": {\n", + " \"display_name\": \"Code Server Small\",\n", + " \"slug\": \"ellip_studio_coder_slug_s\",\n", + " \"cpu_limit\": 2,\n", + " \"mem_limit\": \"8G\",\n", + " },\n", + " \"coder2\": {\n", + " \"display_name\": \"Code Server Medium\",\n", + " \"slug\": \"ellip_studio_coder_slug_m\",\n", + " \"cpu_limit\": 4,\n", + " \"mem_limit\": \"12G\",\n", + " },\n", + "}\n", + "\n", + "for key, value in coders.items():\n", + " coder_definition = ProfileDefinition( # type: ignore\n", + " display_name=value[\"display_name\"],\n", + " slug=value[\"slug\"],\n", + " default=False,\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", + " cpu_limit=value[\"cpu_limit\"],\n", + " mem_limit=value[\"mem_limit\"],\n", + " image=\"eoepca/pde-code-server:develop\",\n", + " ),\n", + " )\n", + "\n", + " coder_profile = Profile( # type: ignore\n", + " id=f\"profile_studio_{key}\",\n", + " groups=[\"group-a\", \"group-b\"],\n", + " definition=coder_definition,\n", + " node_selector={},\n", + " volumes=[calrissian_volume, workspace_volume],\n", + " config_maps=[\n", + " bash_rc_cm,\n", + " ],\n", + " pod_env_vars={\n", + " \"HOME\": \"/workspace\",\n", + " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", + " },\n", + " )\n", + "\n", + " profiles.append(coder_profile)\n", + "profiles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Coder with init.sh\n", + "In the cell below, the user configures the code-server with a new profile, which extends the existing [coder](#coder) profile. The init container is responsible for executing a Bash script to initialize the code-server pod. In this case, the script [`init.sh`](../config-maps/init.sh) runs during pod initialization to clone a Git repository and install extensions on the deployed code-server." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "\n", + "\n", + "init_context_volume_mount = InitContainerVolumeMount( # type: ignore\n", + " mount_path=\"/opt/init/.init.sh\", name=\"init\", sub_path=\"init\"\n", + ")\n", + "init_container = InitContainer( # type: ignore\n", + " name=\"init-file-on-volume\",\n", + " image=\"eoepca/pde-code-server:develop\",\n", + " command=[\"sh\", \"-c\", \"sh /opt/init/.init.sh\"],\n", + " volume_mounts=[\n", + " VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"), # type: ignore\n", + " init_context_volume_mount,\n", + " ],\n", + ")\n", + "\n", + "eoepca_demo_init_script_profile = Profile( # type: ignore\n", + " id=f\"profile_demo_init_script\",\n", + " groups=[\"group-a\", \"group-b\"],\n", + " definition=ProfileDefinition( # type: ignore\n", + " display_name=\"Coder demo init script\",\n", + " description=\"This profile is used to demonstrate the use of an init script\",\n", + " slug=\"eoepca_demo_init_script\",\n", + " default=False,\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", + " cpu_guarantee=1,\n", + " cpu_limit=2,\n", + " mem_guarantee=\"4G\",\n", + " mem_limit=\"6G\",\n", + " image=\"eoepca/pde-code-server:develop\",\n", + " ),\n", + " ),\n", + " node_selector={},\n", + " volumes=[calrissian_volume, workspace_volume],\n", + " config_maps=[init_coder_cm],\n", + " pod_env_vars={\n", + " \"HOME\": \"/workspace\",\n", + " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", + " \"CONDARC\": \"/workspace/.condarc\",\n", + " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", + " \"CODE_SERVER_WS\": \"/workspace/mastering-app-package\",\n", + " },\n", + " init_containers=[init_container],\n", + ")\n", + "profiles.append(eoepca_demo_init_script_profile)\n", + "profiles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### JupyterLab\n", + "\n", + "In the cell below, the user adds a [JupyterLab](https://github.com/jupyter/docker-stacks) profile to the stack of profiles. The [workspace volume](#workspace-volume) is mounted to persist user files, and necessary environment variables are set to ensure a smooth user experience." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "image = \"jupyter/scipy-notebook\"\n", + "\n", + "\n", + "eoepca_jupyter_lab_profile = Profile( # type: ignore\n", + " id=\"profile_jupyter_lab\",\n", + " groups=[\"group-c\"],\n", + " definition=ProfileDefinition( # type: ignore\n", + " display_name=\"Jupyter Lab\",\n", + " description=\"Jupyter Lab with Python 3.11\",\n", + " slug=\"eoepca_jupyter_lab\",\n", + " default=False,\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", + " cpu_guarantee=1,\n", + " cpu_limit=2,\n", + " mem_guarantee=\"4G\",\n", + " mem_limit=\"6G\",\n", + " image=image,\n", + " ),\n", + " ),\n", + " node_selector={},\n", + " volumes=[workspace_volume],\n", + " config_maps=[],\n", + " pod_env_vars={\n", + " \"HOME\": \"/workspace\",\n", + " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", + " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", + " },\n", + ")\n", + "\n", + "profiles.append(eoepca_jupyter_lab_profile)\n", + "profiles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### JupyterLab Plus \n", + "JupyterLab Plus is an extended version of the JupyterLab profile with additional features and capabilities. The Docker image for this profile is hosted on a private Docker registry, requiring the user to configure a pull secret to authenticate and successfully pull the JupyterLab Plus image on the remote cluster." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab_2', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', description='Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret', slug='eoepca_jupyter_lab_2', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "image_pull_secret = ImagePullSecret( # type: ignore\n", + " name=\"cr-config\",\n", + " persist=False,\n", + " data=\"ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9\",\n", + ")\n", + "image = \"cr.terradue.com/eoepca-plus/scipy-notebook@sha256:f339a9fa98d3d0c1fa8d7cc850e7f5a46845781f49bee86aacba059669d02d54\"\n", + "image = \"eoepca/iat-jupyterlab:develop\"\n", + "\n", + "eoepca_jupyter_lab_profile_2 = Profile( # type: ignore\n", + " id=\"profile_jupyter_lab_2\",\n", + " groups=[\"group-c\"],\n", + " definition=ProfileDefinition( # type: ignore\n", + " display_name=\"Jupyter Lab - profile 2\",\n", + " description=\"Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret\",\n", + " slug=\"eoepca_jupyter_lab_2\",\n", + " default=False,\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", + " cpu_guarantee=1,\n", + " cpu_limit=2,\n", + " mem_guarantee=\"4G\",\n", + " mem_limit=\"6G\",\n", + " image=image,\n", + " ),\n", + " ),\n", + " node_selector={},\n", + " volumes=[workspace_volume],\n", + " config_maps=[],\n", + " pod_env_vars={\n", + " \"HOME\": \"/workspace\",\n", + " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", + " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", + " },\n", + " image_pull_secrets=[image_pull_secret],\n", + ")\n", + "\n", + "profiles.append(eoepca_jupyter_lab_profile_2)\n", + "profiles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### E-Learning \n", + "This profile is an extended version of code-server, configured to initialize with an init container using [init.sh](../config-maps/init.sh). It also mounts several ConfigMaps, including [bash-rc](#bash-rc), [bash-login](#bash-login), and [stage-in/out](#stage-inout-configmap). Additionally, key Kubernetes objects such as Role, RoleBinding, and Service are defined using a Kubernetes [manifest](../manifests/manifest.yaml). This manifest ultimately exposes **LocalStack** as a Service, allowing users to emulate AWS locally." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab_2', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', description='Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret', slug='eoepca_jupyter_lab_2', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder_stac', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Understanding STAC for input/output data modelling', description='Understand the role of STAC in input/output data manifests in EO data processing workflows', slug='eoepca_coder_slug_stac', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='set -x \\n\\ncd /workspace\\n\\ngit clone \\'https://github.com/eoap/stac-eoap.git\\'\\n\\ncode-server --install-extension ms-python.python \\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nmkdir -p /workspace/User/\\n\\necho \\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\' > /workspace/User/settings.json\\n\\npython -m venv /workspace/.venv\\nsource /workspace/.venv/bin/activate\\n/workspace/.venv/bin/python -m pip install --no-cache-dir stactools rasterio requests stac-asset click-logging tabulate tqdm pystac-client ipykernel loguru scikit-image rio_stac boto3==1.35.23\\n\\n/workspace/.venv/bin/python -m pip install --index-url https://test.pypi.org/simple cwl-wrapper\\n\\n/workspace/.venv/bin/python -m ipykernel install --user --name stac_env --display-name \"Python (STAC)\"\\n\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\nexport AWS_ACCESS_KEY_ID=\"test\"\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\naws s3 mb s3://results --endpoint-url=http://localstack:4566\\n\\nexit 0', persist=False), ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.local', 'XDG_DATA_HOME': '/workspace/.local/share/', 'CWLTOOL_OPTIONS': '--podman', 'CODE_SERVER_WS': '/workspace/stac-eoap', 'AWS_DEFAULT_REGION': 'us-east-1', 'AWS_ACCESS_KEY_ID': 'test', 'AWS_SECRET_ACCESS_KEY': 'test'}, default_url=None, node_selector={}, role_bindings=[], image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=[Manifest(name='manifests', key='manifests', content=[{'apiVersion': 'v1', 'kind': 'ServiceAccount', 'metadata': {'name': 'localstack'}}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'Role', 'metadata': {'name': 'localstack'}, 'rules': [{'apiGroups': [''], 'resources': ['pods'], 'verbs': ['*']}, {'apiGroups': [''], 'resources': ['pods/log'], 'verbs': ['get']}, {'apiGroups': [''], 'resources': ['pods/exec'], 'verbs': ['get', 'create']}, {'apiGroups': [''], 'resources': ['services'], 'verbs': ['get', 'list']}]}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'RoleBinding', 'metadata': {'name': 'localstack'}, 'subjects': [{'kind': 'ServiceAccount', 'name': 'localstack'}], 'roleRef': {'kind': 'Role', 'name': 'localstack', 'apiGroup': 'rbac.authorization.k8s.io'}}, {'apiVersion': 'v1', 'kind': 'Service', 'metadata': {'name': 'localstack'}, 'spec': {'type': 'ClusterIP', 'ports': [{'name': 'edge', 'port': 4566, 'targetPort': 4566}, {'name': 'external-service-port-4510', 'port': 4510, 'targetPort': 'ext-svc-4510'}, {'name': 'external-service-port-4511', 'port': 4511, 'targetPort': 'ext-svc-4511'}, {'name': 'external-service-port-4512', 'port': 4512, 'targetPort': 'ext-svc-4512'}, {'name': 'external-service-port-4513', 'port': 4513, 'targetPort': 'ext-svc-4513'}, {'name': 'external-service-port-4514', 'port': 4514, 'targetPort': 'ext-svc-4514'}, {'name': 'external-service-port-4515', 'port': 4515, 'targetPort': 'ext-svc-4515'}, {'name': 'external-service-port-4516', 'port': 4516, 'targetPort': 'ext-svc-4516'}, {'name': 'external-service-port-4517', 'port': 4517, 'targetPort': 'ext-svc-4517'}, {'name': 'external-service-port-4518', 'port': 4518, 'targetPort': 'ext-svc-4518'}, {'name': 'external-service-port-4519', 'port': 4519, 'targetPort': 'ext-svc-4519'}, {'name': 'external-service-port-4520', 'port': 4520, 'targetPort': 'ext-svc-4520'}, {'name': 'external-service-port-4521', 'port': 4521, 'targetPort': 'ext-svc-4521'}, {'name': 'external-service-port-4522', 'port': 4522, 'targetPort': 'ext-svc-4522'}, {'name': 'external-service-port-4523', 'port': 4523, 'targetPort': 'ext-svc-4523'}, {'name': 'external-service-port-4524', 'port': 4524, 'targetPort': 'ext-svc-4524'}, {'name': 'external-service-port-4525', 'port': 4525, 'targetPort': 'ext-svc-4525'}, {'name': 'external-service-port-4526', 'port': 4526, 'targetPort': 'ext-svc-4526'}, {'name': 'external-service-port-4527', 'port': 4527, 'targetPort': 'ext-svc-4527'}, {'name': 'external-service-port-4528', 'port': 4528, 'targetPort': 'ext-svc-4528'}, {'name': 'external-service-port-4529', 'port': 4529, 'targetPort': 'ext-svc-4529'}, {'name': 'external-service-port-4530', 'port': 4530, 'targetPort': 'ext-svc-4530'}, {'name': 'external-service-port-4531', 'port': 4531, 'targetPort': 'ext-svc-4531'}, {'name': 'external-service-port-4532', 'port': 4532, 'targetPort': 'ext-svc-4532'}, {'name': 'external-service-port-4533', 'port': 4533, 'targetPort': 'ext-svc-4533'}, {'name': 'external-service-port-4534', 'port': 4534, 'targetPort': 'ext-svc-4534'}, {'name': 'external-service-port-4535', 'port': 4535, 'targetPort': 'ext-svc-4535'}, {'name': 'external-service-port-4536', 'port': 4536, 'targetPort': 'ext-svc-4536'}, {'name': 'external-service-port-4537', 'port': 4537, 'targetPort': 'ext-svc-4537'}, {'name': 'external-service-port-4538', 'port': 4538, 'targetPort': 'ext-svc-4538'}, {'name': 'external-service-port-4539', 'port': 4539, 'targetPort': 'ext-svc-4539'}, {'name': 'external-service-port-4540', 'port': 4540, 'targetPort': 'ext-svc-4540'}, {'name': 'external-service-port-4541', 'port': 4541, 'targetPort': 'ext-svc-4541'}, {'name': 'external-service-port-4542', 'port': 4542, 'targetPort': 'ext-svc-4542'}, {'name': 'external-service-port-4543', 'port': 4543, 'targetPort': 'ext-svc-4543'}, {'name': 'external-service-port-4544', 'port': 4544, 'targetPort': 'ext-svc-4544'}, {'name': 'external-service-port-4545', 'port': 4545, 'targetPort': 'ext-svc-4545'}, {'name': 'external-service-port-4546', 'port': 4546, 'targetPort': 'ext-svc-4546'}, {'name': 'external-service-port-4547', 'port': 4547, 'targetPort': 'ext-svc-4547'}, {'name': 'external-service-port-4548', 'port': 4548, 'targetPort': 'ext-svc-4548'}, {'name': 'external-service-port-4549', 'port': 4549, 'targetPort': 'ext-svc-4549'}, {'name': 'external-service-port-4550', 'port': 4550, 'targetPort': 'ext-svc-4550'}, {'name': 'external-service-port-4551', 'port': 4551, 'targetPort': 'ext-svc-4551'}, {'name': 'external-service-port-4552', 'port': 4552, 'targetPort': 'ext-svc-4552'}, {'name': 'external-service-port-4553', 'port': 4553, 'targetPort': 'ext-svc-4553'}, {'name': 'external-service-port-4554', 'port': 4554, 'targetPort': 'ext-svc-4554'}, {'name': 'external-service-port-4555', 'port': 4555, 'targetPort': 'ext-svc-4555'}, {'name': 'external-service-port-4556', 'port': 4556, 'targetPort': 'ext-svc-4556'}, {'name': 'external-service-port-4557', 'port': 4557, 'targetPort': 'ext-svc-4557'}, {'name': 'external-service-port-4558', 'port': 4558, 'targetPort': 'ext-svc-4558'}, {'name': 'external-service-port-4559', 'port': 4559, 'targetPort': 'ext-svc-4559'}], 'selector': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}}, {'apiVersion': 'apps/v1', 'kind': 'Deployment', 'metadata': {'name': 'localstack'}, 'spec': {'replicas': 1, 'strategy': {'type': 'RollingUpdate'}, 'selector': {'matchLabels': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'template': {'metadata': {'labels': {'app': 'localstack-{{ spawner.user.name }}', 'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'spec': {'serviceAccountName': 'localstack', 'securityContext': {}, 'containers': [{'name': 'localstack', 'securityContext': {}, 'image': 'localstack/localstack:latest', 'imagePullPolicy': 'IfNotPresent', 'ports': [{'name': 'edge', 'containerPort': 4566, 'protocol': 'TCP'}, {'name': 'ext-svc-4510', 'containerPort': 4510, 'protocol': 'TCP'}, {'name': 'ext-svc-4511', 'containerPort': 4511, 'protocol': 'TCP'}, {'name': 'ext-svc-4512', 'containerPort': 4512, 'protocol': 'TCP'}, {'name': 'ext-svc-4513', 'containerPort': 4513, 'protocol': 'TCP'}, {'name': 'ext-svc-4514', 'containerPort': 4514, 'protocol': 'TCP'}, {'name': 'ext-svc-4515', 'containerPort': 4515, 'protocol': 'TCP'}, {'name': 'ext-svc-4516', 'containerPort': 4516, 'protocol': 'TCP'}, {'name': 'ext-svc-4517', 'containerPort': 4517, 'protocol': 'TCP'}, {'name': 'ext-svc-4518', 'containerPort': 4518, 'protocol': 'TCP'}, {'name': 'ext-svc-4519', 'containerPort': 4519, 'protocol': 'TCP'}, {'name': 'ext-svc-4520', 'containerPort': 4520, 'protocol': 'TCP'}, {'name': 'ext-svc-4521', 'containerPort': 4521, 'protocol': 'TCP'}, {'name': 'ext-svc-4522', 'containerPort': 4522, 'protocol': 'TCP'}, {'name': 'ext-svc-4523', 'containerPort': 4523, 'protocol': 'TCP'}, {'name': 'ext-svc-4524', 'containerPort': 4524, 'protocol': 'TCP'}, {'name': 'ext-svc-4525', 'containerPort': 4525, 'protocol': 'TCP'}, {'name': 'ext-svc-4526', 'containerPort': 4526, 'protocol': 'TCP'}, {'name': 'ext-svc-4527', 'containerPort': 4527, 'protocol': 'TCP'}, {'name': 'ext-svc-4528', 'containerPort': 4528, 'protocol': 'TCP'}, {'name': 'ext-svc-4529', 'containerPort': 4529, 'protocol': 'TCP'}, {'name': 'ext-svc-4530', 'containerPort': 4530, 'protocol': 'TCP'}, {'name': 'ext-svc-4531', 'containerPort': 4531, 'protocol': 'TCP'}, {'name': 'ext-svc-4532', 'containerPort': 4532, 'protocol': 'TCP'}, {'name': 'ext-svc-4533', 'containerPort': 4533, 'protocol': 'TCP'}, {'name': 'ext-svc-4534', 'containerPort': 4534, 'protocol': 'TCP'}, {'name': 'ext-svc-4535', 'containerPort': 4535, 'protocol': 'TCP'}, {'name': 'ext-svc-4536', 'containerPort': 4536, 'protocol': 'TCP'}, {'name': 'ext-svc-4537', 'containerPort': 4537, 'protocol': 'TCP'}, {'name': 'ext-svc-4538', 'containerPort': 4538, 'protocol': 'TCP'}, {'name': 'ext-svc-4539', 'containerPort': 4539, 'protocol': 'TCP'}, {'name': 'ext-svc-4540', 'containerPort': 4540, 'protocol': 'TCP'}, {'name': 'ext-svc-4541', 'containerPort': 4541, 'protocol': 'TCP'}, {'name': 'ext-svc-4542', 'containerPort': 4542, 'protocol': 'TCP'}, {'name': 'ext-svc-4543', 'containerPort': 4543, 'protocol': 'TCP'}, {'name': 'ext-svc-4544', 'containerPort': 4544, 'protocol': 'TCP'}, {'name': 'ext-svc-4545', 'containerPort': 4545, 'protocol': 'TCP'}, {'name': 'ext-svc-4546', 'containerPort': 4546, 'protocol': 'TCP'}, {'name': 'ext-svc-4547', 'containerPort': 4547, 'protocol': 'TCP'}, {'name': 'ext-svc-4548', 'containerPort': 4548, 'protocol': 'TCP'}, {'name': 'ext-svc-4549', 'containerPort': 4549, 'protocol': 'TCP'}, {'name': 'ext-svc-4550', 'containerPort': 4550, 'protocol': 'TCP'}, {'name': 'ext-svc-4551', 'containerPort': 4551, 'protocol': 'TCP'}, {'name': 'ext-svc-4552', 'containerPort': 4552, 'protocol': 'TCP'}, {'name': 'ext-svc-4553', 'containerPort': 4553, 'protocol': 'TCP'}, {'name': 'ext-svc-4554', 'containerPort': 4554, 'protocol': 'TCP'}, {'name': 'ext-svc-4555', 'containerPort': 4555, 'protocol': 'TCP'}, {'name': 'ext-svc-4556', 'containerPort': 4556, 'protocol': 'TCP'}, {'name': 'ext-svc-4557', 'containerPort': 4557, 'protocol': 'TCP'}, {'name': 'ext-svc-4558', 'containerPort': 4558, 'protocol': 'TCP'}, {'name': 'ext-svc-4559', 'containerPort': 4559, 'protocol': 'TCP'}], 'livenessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'readinessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'resources': {}, 'env': [{'name': 'DEBUG', 'value': '0'}, {'name': 'EXTERNAL_SERVICE_PORTS_START', 'value': '4510'}, {'name': 'EXTERNAL_SERVICE_PORTS_END', 'value': '4560'}, {'name': 'LOCALSTACK_K8S_SERVICE_NAME', 'value': 'localstack'}, {'name': 'LOCALSTACK_K8S_NAMESPACE', 'valueFrom': {'fieldRef': {'fieldPath': 'metadata.namespace'}}}, {'name': 'LAMBDA_RUNTIME_EXECUTOR', 'value': 'docker'}, {'name': 'LAMBDA_K8S_IMAGE_PREFIX', 'value': 'localstack/lambda-'}, {'name': 'LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT', 'value': '60'}, {'name': 'OVERRIDE_IN_DOCKER', 'value': '1'}]}], 'volumes': []}}}}, {'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': 'my-config'}, 'data': {'ENV_VAR1': 'value1', 'ENV_VAR2': 'value2'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'my-secret'}, 'type': 'Opaque', 'data': {'SECRET_KEY1': 'dmFsdWUx', 'SECRET_KEY2': 'dmFsdWUy'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'aws-credentials-{{ spawner.user.name }}'}, 'type': 'Opaque', 'data': {'credentials': 'W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg='}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'data-by-name', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'data-by-name', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': 'secret-value', 'remoteRef': {'key': 'secret-one', 'property': 'the-key'}}]}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'eoepca-plus-secret-ro', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'eoepca-plus-secret-ro', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': '.dockerconfigjson', 'remoteRef': {'key': 'eoepca-plus-secret-ro', 'property': '.dockerconfigjson'}}]}}], persist=False)], env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "localstack_manifest_path = os.path.join(parent_dir, \"manifests/manifest.yaml\") \n", + "with open(localstack_manifest_path, \"r\") as f:\n", + " content = yaml.safe_load_all(f.read())\n", + "\n", + "\n", + "localstack_manifest = Manifest( # type: ignore\n", + " name=\"manifests\",\n", + " key=\"manifests\",\n", + " readonly=True,\n", + " persist=False,\n", + " content=[e for e in content],\n", + ")\n", + "\n", + "image = \"docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40\"\n", + "\n", + "coder_profile_stac = Profile( # type: ignore\n", + " id=f\"profile_studio_coder_stac\",\n", + " groups=[\"group-a\", \"group-b\"], \n", + " definition=ProfileDefinition( # type: ignore\n", + " display_name=\"Understanding STAC for input/output data modelling\",\n", + " description=\"Understand the role of STAC in input/output data manifests in EO data processing workflows\",\n", + " slug=\"eoepca_coder_slug_stac\",\n", + " default=False,\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", + " cpu_guarantee=1,\n", + " cpu_limit=2,\n", + " mem_guarantee=\"4G\",\n", + " mem_limit=\"6G\",\n", + " image=image,\n", + " ),\n", + " ),\n", + " node_selector={},\n", + " volumes=[workspace_volume],\n", + " config_maps=[init_stac_cm, bash_rc_cm, bash_login_cm],\n", + " pod_env_vars={\n", + " \"HOME\": \"/workspace\",\n", + " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", + " \"CONDARC\": \"/workspace/.condarc\",\n", + " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", + " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", + " \"XDG_CONFIG_HOME\": \"/workspace/.local\",\n", + " \"XDG_DATA_HOME\": \"/workspace/.local/share/\",\n", + " \"CWLTOOL_OPTIONS\": \"--podman\",\n", + " \"CODE_SERVER_WS\": \"/workspace/stac-eoap\",\n", + " \"AWS_DEFAULT_REGION\": \"us-east-1\",\n", + " \"AWS_ACCESS_KEY_ID\": \"test\",\n", + " \"AWS_SECRET_ACCESS_KEY\": \"test\",\n", + " },\n", + " role_bindings=[],\n", + " init_containers=[init_container],\n", + " image_pull_secrets=[image_pull_secret],\n", + " manifests=[localstack_manifest],\n", + ")\n", + "\n", + "profiles.append(coder_profile_stac)\n", + "profiles" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### QGIS \n", + "This profile is configured to deploy the [QGIS](https://github.com/qgis/QGIS) application on the **App Hub**, enabling users to visualize and analyze Earth observation data directly within the platform. The deployment ensures that QGIS is configured with the necessary resources, storage, and dependencies to support geospatial analysis, map rendering, and data processing. By integrating QGIS into the App Hub, users can seamlessly interact with satellite imagery, vector layers, and raster data for advanced geospatial analysis." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab_2', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', description='Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret', slug='eoepca_jupyter_lab_2', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder_stac', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Understanding STAC for input/output data modelling', description='Understand the role of STAC in input/output data manifests in EO data processing workflows', slug='eoepca_coder_slug_stac', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='set -x \\n\\ncd /workspace\\n\\ngit clone \\'https://github.com/eoap/stac-eoap.git\\'\\n\\ncode-server --install-extension ms-python.python \\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nmkdir -p /workspace/User/\\n\\necho \\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\' > /workspace/User/settings.json\\n\\npython -m venv /workspace/.venv\\nsource /workspace/.venv/bin/activate\\n/workspace/.venv/bin/python -m pip install --no-cache-dir stactools rasterio requests stac-asset click-logging tabulate tqdm pystac-client ipykernel loguru scikit-image rio_stac boto3==1.35.23\\n\\n/workspace/.venv/bin/python -m pip install --index-url https://test.pypi.org/simple cwl-wrapper\\n\\n/workspace/.venv/bin/python -m ipykernel install --user --name stac_env --display-name \"Python (STAC)\"\\n\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\nexport AWS_ACCESS_KEY_ID=\"test\"\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\naws s3 mb s3://results --endpoint-url=http://localstack:4566\\n\\nexit 0', persist=False), ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.local', 'XDG_DATA_HOME': '/workspace/.local/share/', 'CWLTOOL_OPTIONS': '--podman', 'CODE_SERVER_WS': '/workspace/stac-eoap', 'AWS_DEFAULT_REGION': 'us-east-1', 'AWS_ACCESS_KEY_ID': 'test', 'AWS_SECRET_ACCESS_KEY': 'test'}, default_url=None, node_selector={}, role_bindings=[], image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=[Manifest(name='manifests', key='manifests', content=[{'apiVersion': 'v1', 'kind': 'ServiceAccount', 'metadata': {'name': 'localstack'}}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'Role', 'metadata': {'name': 'localstack'}, 'rules': [{'apiGroups': [''], 'resources': ['pods'], 'verbs': ['*']}, {'apiGroups': [''], 'resources': ['pods/log'], 'verbs': ['get']}, {'apiGroups': [''], 'resources': ['pods/exec'], 'verbs': ['get', 'create']}, {'apiGroups': [''], 'resources': ['services'], 'verbs': ['get', 'list']}]}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'RoleBinding', 'metadata': {'name': 'localstack'}, 'subjects': [{'kind': 'ServiceAccount', 'name': 'localstack'}], 'roleRef': {'kind': 'Role', 'name': 'localstack', 'apiGroup': 'rbac.authorization.k8s.io'}}, {'apiVersion': 'v1', 'kind': 'Service', 'metadata': {'name': 'localstack'}, 'spec': {'type': 'ClusterIP', 'ports': [{'name': 'edge', 'port': 4566, 'targetPort': 4566}, {'name': 'external-service-port-4510', 'port': 4510, 'targetPort': 'ext-svc-4510'}, {'name': 'external-service-port-4511', 'port': 4511, 'targetPort': 'ext-svc-4511'}, {'name': 'external-service-port-4512', 'port': 4512, 'targetPort': 'ext-svc-4512'}, {'name': 'external-service-port-4513', 'port': 4513, 'targetPort': 'ext-svc-4513'}, {'name': 'external-service-port-4514', 'port': 4514, 'targetPort': 'ext-svc-4514'}, {'name': 'external-service-port-4515', 'port': 4515, 'targetPort': 'ext-svc-4515'}, {'name': 'external-service-port-4516', 'port': 4516, 'targetPort': 'ext-svc-4516'}, {'name': 'external-service-port-4517', 'port': 4517, 'targetPort': 'ext-svc-4517'}, {'name': 'external-service-port-4518', 'port': 4518, 'targetPort': 'ext-svc-4518'}, {'name': 'external-service-port-4519', 'port': 4519, 'targetPort': 'ext-svc-4519'}, {'name': 'external-service-port-4520', 'port': 4520, 'targetPort': 'ext-svc-4520'}, {'name': 'external-service-port-4521', 'port': 4521, 'targetPort': 'ext-svc-4521'}, {'name': 'external-service-port-4522', 'port': 4522, 'targetPort': 'ext-svc-4522'}, {'name': 'external-service-port-4523', 'port': 4523, 'targetPort': 'ext-svc-4523'}, {'name': 'external-service-port-4524', 'port': 4524, 'targetPort': 'ext-svc-4524'}, {'name': 'external-service-port-4525', 'port': 4525, 'targetPort': 'ext-svc-4525'}, {'name': 'external-service-port-4526', 'port': 4526, 'targetPort': 'ext-svc-4526'}, {'name': 'external-service-port-4527', 'port': 4527, 'targetPort': 'ext-svc-4527'}, {'name': 'external-service-port-4528', 'port': 4528, 'targetPort': 'ext-svc-4528'}, {'name': 'external-service-port-4529', 'port': 4529, 'targetPort': 'ext-svc-4529'}, {'name': 'external-service-port-4530', 'port': 4530, 'targetPort': 'ext-svc-4530'}, {'name': 'external-service-port-4531', 'port': 4531, 'targetPort': 'ext-svc-4531'}, {'name': 'external-service-port-4532', 'port': 4532, 'targetPort': 'ext-svc-4532'}, {'name': 'external-service-port-4533', 'port': 4533, 'targetPort': 'ext-svc-4533'}, {'name': 'external-service-port-4534', 'port': 4534, 'targetPort': 'ext-svc-4534'}, {'name': 'external-service-port-4535', 'port': 4535, 'targetPort': 'ext-svc-4535'}, {'name': 'external-service-port-4536', 'port': 4536, 'targetPort': 'ext-svc-4536'}, {'name': 'external-service-port-4537', 'port': 4537, 'targetPort': 'ext-svc-4537'}, {'name': 'external-service-port-4538', 'port': 4538, 'targetPort': 'ext-svc-4538'}, {'name': 'external-service-port-4539', 'port': 4539, 'targetPort': 'ext-svc-4539'}, {'name': 'external-service-port-4540', 'port': 4540, 'targetPort': 'ext-svc-4540'}, {'name': 'external-service-port-4541', 'port': 4541, 'targetPort': 'ext-svc-4541'}, {'name': 'external-service-port-4542', 'port': 4542, 'targetPort': 'ext-svc-4542'}, {'name': 'external-service-port-4543', 'port': 4543, 'targetPort': 'ext-svc-4543'}, {'name': 'external-service-port-4544', 'port': 4544, 'targetPort': 'ext-svc-4544'}, {'name': 'external-service-port-4545', 'port': 4545, 'targetPort': 'ext-svc-4545'}, {'name': 'external-service-port-4546', 'port': 4546, 'targetPort': 'ext-svc-4546'}, {'name': 'external-service-port-4547', 'port': 4547, 'targetPort': 'ext-svc-4547'}, {'name': 'external-service-port-4548', 'port': 4548, 'targetPort': 'ext-svc-4548'}, {'name': 'external-service-port-4549', 'port': 4549, 'targetPort': 'ext-svc-4549'}, {'name': 'external-service-port-4550', 'port': 4550, 'targetPort': 'ext-svc-4550'}, {'name': 'external-service-port-4551', 'port': 4551, 'targetPort': 'ext-svc-4551'}, {'name': 'external-service-port-4552', 'port': 4552, 'targetPort': 'ext-svc-4552'}, {'name': 'external-service-port-4553', 'port': 4553, 'targetPort': 'ext-svc-4553'}, {'name': 'external-service-port-4554', 'port': 4554, 'targetPort': 'ext-svc-4554'}, {'name': 'external-service-port-4555', 'port': 4555, 'targetPort': 'ext-svc-4555'}, {'name': 'external-service-port-4556', 'port': 4556, 'targetPort': 'ext-svc-4556'}, {'name': 'external-service-port-4557', 'port': 4557, 'targetPort': 'ext-svc-4557'}, {'name': 'external-service-port-4558', 'port': 4558, 'targetPort': 'ext-svc-4558'}, {'name': 'external-service-port-4559', 'port': 4559, 'targetPort': 'ext-svc-4559'}], 'selector': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}}, {'apiVersion': 'apps/v1', 'kind': 'Deployment', 'metadata': {'name': 'localstack'}, 'spec': {'replicas': 1, 'strategy': {'type': 'RollingUpdate'}, 'selector': {'matchLabels': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'template': {'metadata': {'labels': {'app': 'localstack-{{ spawner.user.name }}', 'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'spec': {'serviceAccountName': 'localstack', 'securityContext': {}, 'containers': [{'name': 'localstack', 'securityContext': {}, 'image': 'localstack/localstack:latest', 'imagePullPolicy': 'IfNotPresent', 'ports': [{'name': 'edge', 'containerPort': 4566, 'protocol': 'TCP'}, {'name': 'ext-svc-4510', 'containerPort': 4510, 'protocol': 'TCP'}, {'name': 'ext-svc-4511', 'containerPort': 4511, 'protocol': 'TCP'}, {'name': 'ext-svc-4512', 'containerPort': 4512, 'protocol': 'TCP'}, {'name': 'ext-svc-4513', 'containerPort': 4513, 'protocol': 'TCP'}, {'name': 'ext-svc-4514', 'containerPort': 4514, 'protocol': 'TCP'}, {'name': 'ext-svc-4515', 'containerPort': 4515, 'protocol': 'TCP'}, {'name': 'ext-svc-4516', 'containerPort': 4516, 'protocol': 'TCP'}, {'name': 'ext-svc-4517', 'containerPort': 4517, 'protocol': 'TCP'}, {'name': 'ext-svc-4518', 'containerPort': 4518, 'protocol': 'TCP'}, {'name': 'ext-svc-4519', 'containerPort': 4519, 'protocol': 'TCP'}, {'name': 'ext-svc-4520', 'containerPort': 4520, 'protocol': 'TCP'}, {'name': 'ext-svc-4521', 'containerPort': 4521, 'protocol': 'TCP'}, {'name': 'ext-svc-4522', 'containerPort': 4522, 'protocol': 'TCP'}, {'name': 'ext-svc-4523', 'containerPort': 4523, 'protocol': 'TCP'}, {'name': 'ext-svc-4524', 'containerPort': 4524, 'protocol': 'TCP'}, {'name': 'ext-svc-4525', 'containerPort': 4525, 'protocol': 'TCP'}, {'name': 'ext-svc-4526', 'containerPort': 4526, 'protocol': 'TCP'}, {'name': 'ext-svc-4527', 'containerPort': 4527, 'protocol': 'TCP'}, {'name': 'ext-svc-4528', 'containerPort': 4528, 'protocol': 'TCP'}, {'name': 'ext-svc-4529', 'containerPort': 4529, 'protocol': 'TCP'}, {'name': 'ext-svc-4530', 'containerPort': 4530, 'protocol': 'TCP'}, {'name': 'ext-svc-4531', 'containerPort': 4531, 'protocol': 'TCP'}, {'name': 'ext-svc-4532', 'containerPort': 4532, 'protocol': 'TCP'}, {'name': 'ext-svc-4533', 'containerPort': 4533, 'protocol': 'TCP'}, {'name': 'ext-svc-4534', 'containerPort': 4534, 'protocol': 'TCP'}, {'name': 'ext-svc-4535', 'containerPort': 4535, 'protocol': 'TCP'}, {'name': 'ext-svc-4536', 'containerPort': 4536, 'protocol': 'TCP'}, {'name': 'ext-svc-4537', 'containerPort': 4537, 'protocol': 'TCP'}, {'name': 'ext-svc-4538', 'containerPort': 4538, 'protocol': 'TCP'}, {'name': 'ext-svc-4539', 'containerPort': 4539, 'protocol': 'TCP'}, {'name': 'ext-svc-4540', 'containerPort': 4540, 'protocol': 'TCP'}, {'name': 'ext-svc-4541', 'containerPort': 4541, 'protocol': 'TCP'}, {'name': 'ext-svc-4542', 'containerPort': 4542, 'protocol': 'TCP'}, {'name': 'ext-svc-4543', 'containerPort': 4543, 'protocol': 'TCP'}, {'name': 'ext-svc-4544', 'containerPort': 4544, 'protocol': 'TCP'}, {'name': 'ext-svc-4545', 'containerPort': 4545, 'protocol': 'TCP'}, {'name': 'ext-svc-4546', 'containerPort': 4546, 'protocol': 'TCP'}, {'name': 'ext-svc-4547', 'containerPort': 4547, 'protocol': 'TCP'}, {'name': 'ext-svc-4548', 'containerPort': 4548, 'protocol': 'TCP'}, {'name': 'ext-svc-4549', 'containerPort': 4549, 'protocol': 'TCP'}, {'name': 'ext-svc-4550', 'containerPort': 4550, 'protocol': 'TCP'}, {'name': 'ext-svc-4551', 'containerPort': 4551, 'protocol': 'TCP'}, {'name': 'ext-svc-4552', 'containerPort': 4552, 'protocol': 'TCP'}, {'name': 'ext-svc-4553', 'containerPort': 4553, 'protocol': 'TCP'}, {'name': 'ext-svc-4554', 'containerPort': 4554, 'protocol': 'TCP'}, {'name': 'ext-svc-4555', 'containerPort': 4555, 'protocol': 'TCP'}, {'name': 'ext-svc-4556', 'containerPort': 4556, 'protocol': 'TCP'}, {'name': 'ext-svc-4557', 'containerPort': 4557, 'protocol': 'TCP'}, {'name': 'ext-svc-4558', 'containerPort': 4558, 'protocol': 'TCP'}, {'name': 'ext-svc-4559', 'containerPort': 4559, 'protocol': 'TCP'}], 'livenessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'readinessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'resources': {}, 'env': [{'name': 'DEBUG', 'value': '0'}, {'name': 'EXTERNAL_SERVICE_PORTS_START', 'value': '4510'}, {'name': 'EXTERNAL_SERVICE_PORTS_END', 'value': '4560'}, {'name': 'LOCALSTACK_K8S_SERVICE_NAME', 'value': 'localstack'}, {'name': 'LOCALSTACK_K8S_NAMESPACE', 'valueFrom': {'fieldRef': {'fieldPath': 'metadata.namespace'}}}, {'name': 'LAMBDA_RUNTIME_EXECUTOR', 'value': 'docker'}, {'name': 'LAMBDA_K8S_IMAGE_PREFIX', 'value': 'localstack/lambda-'}, {'name': 'LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT', 'value': '60'}, {'name': 'OVERRIDE_IN_DOCKER', 'value': '1'}]}], 'volumes': []}}}}, {'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': 'my-config'}, 'data': {'ENV_VAR1': 'value1', 'ENV_VAR2': 'value2'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'my-secret'}, 'type': 'Opaque', 'data': {'SECRET_KEY1': 'dmFsdWUx', 'SECRET_KEY2': 'dmFsdWUy'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'aws-credentials-{{ spawner.user.name }}'}, 'type': 'Opaque', 'data': {'credentials': 'W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg='}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'data-by-name', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'data-by-name', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': 'secret-value', 'remoteRef': {'key': 'secret-one', 'property': 'the-key'}}]}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'eoepca-plus-secret-ro', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'eoepca-plus-secret-ro', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': '.dockerconfigjson', 'remoteRef': {'key': 'eoepca-plus-secret-ro', 'property': '.dockerconfigjson'}}]}}], persist=False)], env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_desktop_qgis', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='QGIS on a Remote Desktop', description='Spatial visualization and decision-making tools for everyone', slug='eoepca_desktop_qgis', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='2G', mem_guarantee=None, image='eoepca/iga-remote-desktop-qgis:1.1.3', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace'}, default_url='desktop', node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "image = \"eoepca/iga-remote-desktop-qgis:1.1.3\"\n", + "\n", + "qgis_profile = Profile( # type: ignore\n", + " id=\"profile_studio_desktop_qgis\",\n", + " groups=[\"group-a\", \"group-b\"],\n", + " definition=ProfileDefinition( # type: ignore\n", + " display_name=\"QGIS on a Remote Desktop\",\n", + " description=\"Spatial visualization and decision-making tools for everyone\",\n", + " slug=\"eoepca_desktop_qgis\",\n", + " default=False,\n", + " kubespawner_override=KubespawnerOverride( # type: ignore\n", + " cpu_limit=2,\n", + " mem_limit=\"2G\",\n", + " image=image,\n", + " ),\n", + " ),\n", + " node_selector={},\n", + " volumes=[workspace_volume],\n", + " config_maps=[bash_rc_cm, bash_login_cm],\n", + " pod_env_vars={\"HOME\": \"/workspace\"},\n", + " default_url=\"desktop\",\n", + " init_containers=[],\n", + ")\n", + "\n", + "profiles.append(qgis_profile)\n", + "profiles" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Write Configuration \n", + "In the code below, the user inspects all the configurations made in the previous section and then generates a [config.yaml](../../files/hub/config.yml) file. This file contains the complete configuration of the **App Hub**, including the different profiles defined for deployment. The `config.yaml` serves as the configuration blueprint, allowing the user to easily apply and share the profiles across different environments or clusters." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_jupyter_lab_2', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', description='Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret', slug='eoepca_jupyter_lab_2', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_coder_stac', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Understanding STAC for input/output data modelling', description='Understand the role of STAC in input/output data manifests in EO data processing workflows', slug='eoepca_coder_slug_stac', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='set -x \\n\\ncd /workspace\\n\\ngit clone \\'https://github.com/eoap/stac-eoap.git\\'\\n\\ncode-server --install-extension ms-python.python \\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nmkdir -p /workspace/User/\\n\\necho \\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\' > /workspace/User/settings.json\\n\\npython -m venv /workspace/.venv\\nsource /workspace/.venv/bin/activate\\n/workspace/.venv/bin/python -m pip install --no-cache-dir stactools rasterio requests stac-asset click-logging tabulate tqdm pystac-client ipykernel loguru scikit-image rio_stac boto3==1.35.23\\n\\n/workspace/.venv/bin/python -m pip install --index-url https://test.pypi.org/simple cwl-wrapper\\n\\n/workspace/.venv/bin/python -m ipykernel install --user --name stac_env --display-name \"Python (STAC)\"\\n\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\nexport AWS_ACCESS_KEY_ID=\"test\"\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\naws s3 mb s3://results --endpoint-url=http://localstack:4566\\n\\nexit 0', persist=False), ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.local', 'XDG_DATA_HOME': '/workspace/.local/share/', 'CWLTOOL_OPTIONS': '--podman', 'CODE_SERVER_WS': '/workspace/stac-eoap', 'AWS_DEFAULT_REGION': 'us-east-1', 'AWS_ACCESS_KEY_ID': 'test', 'AWS_SECRET_ACCESS_KEY': 'test'}, default_url=None, node_selector={}, role_bindings=[], image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=[Manifest(name='manifests', key='manifests', content=[{'apiVersion': 'v1', 'kind': 'ServiceAccount', 'metadata': {'name': 'localstack'}}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'Role', 'metadata': {'name': 'localstack'}, 'rules': [{'apiGroups': [''], 'resources': ['pods'], 'verbs': ['*']}, {'apiGroups': [''], 'resources': ['pods/log'], 'verbs': ['get']}, {'apiGroups': [''], 'resources': ['pods/exec'], 'verbs': ['get', 'create']}, {'apiGroups': [''], 'resources': ['services'], 'verbs': ['get', 'list']}]}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'RoleBinding', 'metadata': {'name': 'localstack'}, 'subjects': [{'kind': 'ServiceAccount', 'name': 'localstack'}], 'roleRef': {'kind': 'Role', 'name': 'localstack', 'apiGroup': 'rbac.authorization.k8s.io'}}, {'apiVersion': 'v1', 'kind': 'Service', 'metadata': {'name': 'localstack'}, 'spec': {'type': 'ClusterIP', 'ports': [{'name': 'edge', 'port': 4566, 'targetPort': 4566}, {'name': 'external-service-port-4510', 'port': 4510, 'targetPort': 'ext-svc-4510'}, {'name': 'external-service-port-4511', 'port': 4511, 'targetPort': 'ext-svc-4511'}, {'name': 'external-service-port-4512', 'port': 4512, 'targetPort': 'ext-svc-4512'}, {'name': 'external-service-port-4513', 'port': 4513, 'targetPort': 'ext-svc-4513'}, {'name': 'external-service-port-4514', 'port': 4514, 'targetPort': 'ext-svc-4514'}, {'name': 'external-service-port-4515', 'port': 4515, 'targetPort': 'ext-svc-4515'}, {'name': 'external-service-port-4516', 'port': 4516, 'targetPort': 'ext-svc-4516'}, {'name': 'external-service-port-4517', 'port': 4517, 'targetPort': 'ext-svc-4517'}, {'name': 'external-service-port-4518', 'port': 4518, 'targetPort': 'ext-svc-4518'}, {'name': 'external-service-port-4519', 'port': 4519, 'targetPort': 'ext-svc-4519'}, {'name': 'external-service-port-4520', 'port': 4520, 'targetPort': 'ext-svc-4520'}, {'name': 'external-service-port-4521', 'port': 4521, 'targetPort': 'ext-svc-4521'}, {'name': 'external-service-port-4522', 'port': 4522, 'targetPort': 'ext-svc-4522'}, {'name': 'external-service-port-4523', 'port': 4523, 'targetPort': 'ext-svc-4523'}, {'name': 'external-service-port-4524', 'port': 4524, 'targetPort': 'ext-svc-4524'}, {'name': 'external-service-port-4525', 'port': 4525, 'targetPort': 'ext-svc-4525'}, {'name': 'external-service-port-4526', 'port': 4526, 'targetPort': 'ext-svc-4526'}, {'name': 'external-service-port-4527', 'port': 4527, 'targetPort': 'ext-svc-4527'}, {'name': 'external-service-port-4528', 'port': 4528, 'targetPort': 'ext-svc-4528'}, {'name': 'external-service-port-4529', 'port': 4529, 'targetPort': 'ext-svc-4529'}, {'name': 'external-service-port-4530', 'port': 4530, 'targetPort': 'ext-svc-4530'}, {'name': 'external-service-port-4531', 'port': 4531, 'targetPort': 'ext-svc-4531'}, {'name': 'external-service-port-4532', 'port': 4532, 'targetPort': 'ext-svc-4532'}, {'name': 'external-service-port-4533', 'port': 4533, 'targetPort': 'ext-svc-4533'}, {'name': 'external-service-port-4534', 'port': 4534, 'targetPort': 'ext-svc-4534'}, {'name': 'external-service-port-4535', 'port': 4535, 'targetPort': 'ext-svc-4535'}, {'name': 'external-service-port-4536', 'port': 4536, 'targetPort': 'ext-svc-4536'}, {'name': 'external-service-port-4537', 'port': 4537, 'targetPort': 'ext-svc-4537'}, {'name': 'external-service-port-4538', 'port': 4538, 'targetPort': 'ext-svc-4538'}, {'name': 'external-service-port-4539', 'port': 4539, 'targetPort': 'ext-svc-4539'}, {'name': 'external-service-port-4540', 'port': 4540, 'targetPort': 'ext-svc-4540'}, {'name': 'external-service-port-4541', 'port': 4541, 'targetPort': 'ext-svc-4541'}, {'name': 'external-service-port-4542', 'port': 4542, 'targetPort': 'ext-svc-4542'}, {'name': 'external-service-port-4543', 'port': 4543, 'targetPort': 'ext-svc-4543'}, {'name': 'external-service-port-4544', 'port': 4544, 'targetPort': 'ext-svc-4544'}, {'name': 'external-service-port-4545', 'port': 4545, 'targetPort': 'ext-svc-4545'}, {'name': 'external-service-port-4546', 'port': 4546, 'targetPort': 'ext-svc-4546'}, {'name': 'external-service-port-4547', 'port': 4547, 'targetPort': 'ext-svc-4547'}, {'name': 'external-service-port-4548', 'port': 4548, 'targetPort': 'ext-svc-4548'}, {'name': 'external-service-port-4549', 'port': 4549, 'targetPort': 'ext-svc-4549'}, {'name': 'external-service-port-4550', 'port': 4550, 'targetPort': 'ext-svc-4550'}, {'name': 'external-service-port-4551', 'port': 4551, 'targetPort': 'ext-svc-4551'}, {'name': 'external-service-port-4552', 'port': 4552, 'targetPort': 'ext-svc-4552'}, {'name': 'external-service-port-4553', 'port': 4553, 'targetPort': 'ext-svc-4553'}, {'name': 'external-service-port-4554', 'port': 4554, 'targetPort': 'ext-svc-4554'}, {'name': 'external-service-port-4555', 'port': 4555, 'targetPort': 'ext-svc-4555'}, {'name': 'external-service-port-4556', 'port': 4556, 'targetPort': 'ext-svc-4556'}, {'name': 'external-service-port-4557', 'port': 4557, 'targetPort': 'ext-svc-4557'}, {'name': 'external-service-port-4558', 'port': 4558, 'targetPort': 'ext-svc-4558'}, {'name': 'external-service-port-4559', 'port': 4559, 'targetPort': 'ext-svc-4559'}], 'selector': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}}, {'apiVersion': 'apps/v1', 'kind': 'Deployment', 'metadata': {'name': 'localstack'}, 'spec': {'replicas': 1, 'strategy': {'type': 'RollingUpdate'}, 'selector': {'matchLabels': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'template': {'metadata': {'labels': {'app': 'localstack-{{ spawner.user.name }}', 'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'spec': {'serviceAccountName': 'localstack', 'securityContext': {}, 'containers': [{'name': 'localstack', 'securityContext': {}, 'image': 'localstack/localstack:latest', 'imagePullPolicy': 'IfNotPresent', 'ports': [{'name': 'edge', 'containerPort': 4566, 'protocol': 'TCP'}, {'name': 'ext-svc-4510', 'containerPort': 4510, 'protocol': 'TCP'}, {'name': 'ext-svc-4511', 'containerPort': 4511, 'protocol': 'TCP'}, {'name': 'ext-svc-4512', 'containerPort': 4512, 'protocol': 'TCP'}, {'name': 'ext-svc-4513', 'containerPort': 4513, 'protocol': 'TCP'}, {'name': 'ext-svc-4514', 'containerPort': 4514, 'protocol': 'TCP'}, {'name': 'ext-svc-4515', 'containerPort': 4515, 'protocol': 'TCP'}, {'name': 'ext-svc-4516', 'containerPort': 4516, 'protocol': 'TCP'}, {'name': 'ext-svc-4517', 'containerPort': 4517, 'protocol': 'TCP'}, {'name': 'ext-svc-4518', 'containerPort': 4518, 'protocol': 'TCP'}, {'name': 'ext-svc-4519', 'containerPort': 4519, 'protocol': 'TCP'}, {'name': 'ext-svc-4520', 'containerPort': 4520, 'protocol': 'TCP'}, {'name': 'ext-svc-4521', 'containerPort': 4521, 'protocol': 'TCP'}, {'name': 'ext-svc-4522', 'containerPort': 4522, 'protocol': 'TCP'}, {'name': 'ext-svc-4523', 'containerPort': 4523, 'protocol': 'TCP'}, {'name': 'ext-svc-4524', 'containerPort': 4524, 'protocol': 'TCP'}, {'name': 'ext-svc-4525', 'containerPort': 4525, 'protocol': 'TCP'}, {'name': 'ext-svc-4526', 'containerPort': 4526, 'protocol': 'TCP'}, {'name': 'ext-svc-4527', 'containerPort': 4527, 'protocol': 'TCP'}, {'name': 'ext-svc-4528', 'containerPort': 4528, 'protocol': 'TCP'}, {'name': 'ext-svc-4529', 'containerPort': 4529, 'protocol': 'TCP'}, {'name': 'ext-svc-4530', 'containerPort': 4530, 'protocol': 'TCP'}, {'name': 'ext-svc-4531', 'containerPort': 4531, 'protocol': 'TCP'}, {'name': 'ext-svc-4532', 'containerPort': 4532, 'protocol': 'TCP'}, {'name': 'ext-svc-4533', 'containerPort': 4533, 'protocol': 'TCP'}, {'name': 'ext-svc-4534', 'containerPort': 4534, 'protocol': 'TCP'}, {'name': 'ext-svc-4535', 'containerPort': 4535, 'protocol': 'TCP'}, {'name': 'ext-svc-4536', 'containerPort': 4536, 'protocol': 'TCP'}, {'name': 'ext-svc-4537', 'containerPort': 4537, 'protocol': 'TCP'}, {'name': 'ext-svc-4538', 'containerPort': 4538, 'protocol': 'TCP'}, {'name': 'ext-svc-4539', 'containerPort': 4539, 'protocol': 'TCP'}, {'name': 'ext-svc-4540', 'containerPort': 4540, 'protocol': 'TCP'}, {'name': 'ext-svc-4541', 'containerPort': 4541, 'protocol': 'TCP'}, {'name': 'ext-svc-4542', 'containerPort': 4542, 'protocol': 'TCP'}, {'name': 'ext-svc-4543', 'containerPort': 4543, 'protocol': 'TCP'}, {'name': 'ext-svc-4544', 'containerPort': 4544, 'protocol': 'TCP'}, {'name': 'ext-svc-4545', 'containerPort': 4545, 'protocol': 'TCP'}, {'name': 'ext-svc-4546', 'containerPort': 4546, 'protocol': 'TCP'}, {'name': 'ext-svc-4547', 'containerPort': 4547, 'protocol': 'TCP'}, {'name': 'ext-svc-4548', 'containerPort': 4548, 'protocol': 'TCP'}, {'name': 'ext-svc-4549', 'containerPort': 4549, 'protocol': 'TCP'}, {'name': 'ext-svc-4550', 'containerPort': 4550, 'protocol': 'TCP'}, {'name': 'ext-svc-4551', 'containerPort': 4551, 'protocol': 'TCP'}, {'name': 'ext-svc-4552', 'containerPort': 4552, 'protocol': 'TCP'}, {'name': 'ext-svc-4553', 'containerPort': 4553, 'protocol': 'TCP'}, {'name': 'ext-svc-4554', 'containerPort': 4554, 'protocol': 'TCP'}, {'name': 'ext-svc-4555', 'containerPort': 4555, 'protocol': 'TCP'}, {'name': 'ext-svc-4556', 'containerPort': 4556, 'protocol': 'TCP'}, {'name': 'ext-svc-4557', 'containerPort': 4557, 'protocol': 'TCP'}, {'name': 'ext-svc-4558', 'containerPort': 4558, 'protocol': 'TCP'}, {'name': 'ext-svc-4559', 'containerPort': 4559, 'protocol': 'TCP'}], 'livenessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'readinessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'resources': {}, 'env': [{'name': 'DEBUG', 'value': '0'}, {'name': 'EXTERNAL_SERVICE_PORTS_START', 'value': '4510'}, {'name': 'EXTERNAL_SERVICE_PORTS_END', 'value': '4560'}, {'name': 'LOCALSTACK_K8S_SERVICE_NAME', 'value': 'localstack'}, {'name': 'LOCALSTACK_K8S_NAMESPACE', 'valueFrom': {'fieldRef': {'fieldPath': 'metadata.namespace'}}}, {'name': 'LAMBDA_RUNTIME_EXECUTOR', 'value': 'docker'}, {'name': 'LAMBDA_K8S_IMAGE_PREFIX', 'value': 'localstack/lambda-'}, {'name': 'LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT', 'value': '60'}, {'name': 'OVERRIDE_IN_DOCKER', 'value': '1'}]}], 'volumes': []}}}}, {'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': 'my-config'}, 'data': {'ENV_VAR1': 'value1', 'ENV_VAR2': 'value2'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'my-secret'}, 'type': 'Opaque', 'data': {'SECRET_KEY1': 'dmFsdWUx', 'SECRET_KEY2': 'dmFsdWUy'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'aws-credentials-{{ spawner.user.name }}'}, 'type': 'Opaque', 'data': {'credentials': 'W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg='}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'data-by-name', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'data-by-name', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': 'secret-value', 'remoteRef': {'key': 'secret-one', 'property': 'the-key'}}]}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'eoepca-plus-secret-ro', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'eoepca-plus-secret-ro', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': '.dockerconfigjson', 'remoteRef': {'key': 'eoepca-plus-secret-ro', 'property': '.dockerconfigjson'}}]}}], persist=False)], env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", + " Profile(id='profile_studio_desktop_qgis', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='QGIS on a Remote Desktop', description='Spatial visualization and decision-making tools for everyone', slug='eoepca_desktop_qgis', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='2G', mem_guarantee=None, image='eoepca/iga-remote-desktop-qgis:1.1.3', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace'}, default_url='desktop', node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]\n", + "(\"config: profiles=[Profile(id='profile_studio_coder1', groups=['group-a', \"\n", + " \"'group-b'], definition=ProfileDefinition(display_name='Code Server Small', \"\n", + " \"description=None, slug='ellip_studio_coder_slug_s', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, '\n", + " \"mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', \"\n", + " 'extra_resource_limits={}, extra_resource_guarantees={})), '\n", + " \"config_maps=[ConfigMap(name='bash-rc', key='bash-rc', \"\n", + " \"mount_path='/workspace/.bashrc', default_mode=None, readonly=True, \"\n", + " 'content=\\'alias ll=\"ls -l\"\\\\nalias calrissian=\"/opt/conda/bin/calrissian '\n", + " '--pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout '\n", + " '/calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix '\n", + " '/calrissian/tmp/ --outdir /calrissian/\"\\\\nalias '\n", + " 'cwltool=\"/opt/conda/bin/cwltool --podman\"\\\\n. '\n", + " '/home/jovyan/.bashrc\\\\n\\\\n#alias aws=\"aws '\n", + " '--endpoint-url=http://localstack:4566\"\\\\n\\\\n# >>> conda initialize >>>\\\\n# '\n", + " \"!! Contents within this block are managed by \\\\'conda init\\\\' \"\n", + " '!!\\\\n__conda_setup=\"$(\\\\\\'/opt/conda/bin/conda\\\\\\' \\\\\\'shell.bash\\\\\\' '\n", + " '\\\\\\'hook\\\\\\' 2> /dev/null)\"\\\\nif [ $? -eq 0 ]; then\\\\n eval '\n", + " '\"$__conda_setup\"\\\\nelse\\\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; '\n", + " 'then\\\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\\\n else\\\\n '\n", + " 'export PATH=\"/srv/conda/bin:$PATH\"\\\\n fi\\\\nfi\\\\nunset '\n", + " '__conda_setup\\\\n\\\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\\\n '\n", + " '. \"/opt/conda/etc/profile.d/mamba.sh\"\\\\nfi\\\\n# <<< conda initialize '\n", + " '<<<\\\\n\\\\na={{spawner.user.name}}\\\\n\\\\nalias aws=\"aws '\n", + " '--endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"\\', '\n", + " \"persist=False)], volumes=[Volume(name='calrissian-volume', \"\n", + " \"claim_name='calrissian-claim', size='50Gi', \"\n", + " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], \"\n", + " \"volume_mount=VolumeMount(name='calrissian-volume', \"\n", + " \"mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', \"\n", + " \"claim_name='workspace-claim', size='50Gi', \"\n", + " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': \"\n", + " \"'/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, \"\n", + " 'image_pull_secrets=[], init_containers=[], manifests=None, '\n", + " 'env_from_config_maps=None, env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], \"\n", + " \"definition=ProfileDefinition(display_name='Code Server Medium', \"\n", + " \"description=None, slug='ellip_studio_coder_slug_m', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, '\n", + " \"mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', \"\n", + " 'extra_resource_limits={}, extra_resource_guarantees={})), '\n", + " \"config_maps=[ConfigMap(name='bash-rc', key='bash-rc', \"\n", + " \"mount_path='/workspace/.bashrc', default_mode=None, readonly=True, \"\n", + " 'content=\\'alias ll=\"ls -l\"\\\\nalias calrissian=\"/opt/conda/bin/calrissian '\n", + " '--pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout '\n", + " '/calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix '\n", + " '/calrissian/tmp/ --outdir /calrissian/\"\\\\nalias '\n", + " 'cwltool=\"/opt/conda/bin/cwltool --podman\"\\\\n. '\n", + " '/home/jovyan/.bashrc\\\\n\\\\n#alias aws=\"aws '\n", + " '--endpoint-url=http://localstack:4566\"\\\\n\\\\n# >>> conda initialize >>>\\\\n# '\n", + " \"!! Contents within this block are managed by \\\\'conda init\\\\' \"\n", + " '!!\\\\n__conda_setup=\"$(\\\\\\'/opt/conda/bin/conda\\\\\\' \\\\\\'shell.bash\\\\\\' '\n", + " '\\\\\\'hook\\\\\\' 2> /dev/null)\"\\\\nif [ $? -eq 0 ]; then\\\\n eval '\n", + " '\"$__conda_setup\"\\\\nelse\\\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; '\n", + " 'then\\\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\\\n else\\\\n '\n", + " 'export PATH=\"/srv/conda/bin:$PATH\"\\\\n fi\\\\nfi\\\\nunset '\n", + " '__conda_setup\\\\n\\\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\\\n '\n", + " '. \"/opt/conda/etc/profile.d/mamba.sh\"\\\\nfi\\\\n# <<< conda initialize '\n", + " '<<<\\\\n\\\\na={{spawner.user.name}}\\\\n\\\\nalias aws=\"aws '\n", + " '--endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"\\', '\n", + " \"persist=False)], volumes=[Volume(name='calrissian-volume', \"\n", + " \"claim_name='calrissian-claim', size='50Gi', \"\n", + " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], \"\n", + " \"volume_mount=VolumeMount(name='calrissian-volume', \"\n", + " \"mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', \"\n", + " \"claim_name='workspace-claim', size='50Gi', \"\n", + " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': \"\n", + " \"'/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, \"\n", + " 'image_pull_secrets=[], init_containers=[], manifests=None, '\n", + " 'env_from_config_maps=None, env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], \"\n", + " \"definition=ProfileDefinition(display_name='Coder demo init script', \"\n", + " \"description='This profile is used to demonstrate the use of an init script', \"\n", + " \"slug='eoepca_demo_init_script', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, '\n", + " \"mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', \"\n", + " 'extra_resource_limits={}, extra_resource_guarantees={})), '\n", + " \"config_maps=[ConfigMap(name='init', key='init', \"\n", + " \"mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, \"\n", + " 'content=\"set -x\\\\n\\\\ncd /workspace\\\\n\\\\ngit clone '\n", + " \"'https://github.com/eoap/mastering-app-package.git'\\\\n\\\\ncode-server \"\n", + " '--install-extension ms-python.python\\\\ncode-server --install-extension '\n", + " 'redhat.vscode-yaml\\\\ncode-server --install-extension '\n", + " 'sbg-rabix.benten-cwl\\\\ncode-server --install-extension '\n", + " 'ms-toolsai.jupyter\\\\n\\\\nln -s /workspace/.local/share/code-server/extensions '\n", + " '/workspace/extensions\\\\n\\\\nexit 0\\\\n\", persist=False)], '\n", + " \"volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', \"\n", + " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", + " \"access_modes=['ReadWriteMany'], \"\n", + " \"volume_mount=VolumeMount(name='calrissian-volume', \"\n", + " \"mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', \"\n", + " \"claim_name='workspace-claim', size='50Gi', \"\n", + " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': \"\n", + " \"'/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': \"\n", + " \"'/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, \"\n", + " 'default_url=None, node_selector={}, role_bindings=None, '\n", + " 'image_pull_secrets=[], '\n", + " \"init_containers=[InitContainer(name='init-file-on-volume', \"\n", + " \"image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh \"\n", + " \"/opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', \"\n", + " \"mount_path='/workspace'), InitContainerVolumeMount(name='init', \"\n", + " \"mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, \"\n", + " 'env_from_config_maps=None, env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_jupyter_lab', groups=['group-c'], \"\n", + " \"definition=ProfileDefinition(display_name='Jupyter Lab', \"\n", + " \"description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', \"\n", + " 'default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, '\n", + " \"cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', \"\n", + " \"image='jupyter/scipy-notebook', extra_resource_limits={}, \"\n", + " 'extra_resource_guarantees={})), config_maps=[], '\n", + " \"volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', \"\n", + " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", + " \"access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': \"\n", + " \"'/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, \"\n", + " 'default_url=None, node_selector={}, role_bindings=None, '\n", + " 'image_pull_secrets=[], init_containers=[], manifests=None, '\n", + " 'env_from_config_maps=None, env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_jupyter_lab_2', groups=['group-c'], \"\n", + " \"definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', \"\n", + " \"description='Jupyter Lab with Python 3.11 private image - demoes the use of \"\n", + " \"an image pull secret', slug='eoepca_jupyter_lab_2', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, '\n", + " \"mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', \"\n", + " 'extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], '\n", + " \"volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', \"\n", + " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", + " \"access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': \"\n", + " \"'/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, \"\n", + " 'default_url=None, node_selector={}, role_bindings=None, '\n", + " \"image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, \"\n", + " \"data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], \"\n", + " 'init_containers=[], manifests=None, env_from_config_maps=None, '\n", + " 'env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_studio_coder_stac', groups=['group-a', 'group-b'], \"\n", + " \"definition=ProfileDefinition(display_name='Understanding STAC for \"\n", + " \"input/output data modelling', description='Understand the role of STAC in \"\n", + " \"input/output data manifests in EO data processing workflows', \"\n", + " \"slug='eoepca_coder_slug_stac', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, '\n", + " \"mem_limit='6G', mem_guarantee='4G', \"\n", + " \"image='docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40', \"\n", + " 'extra_resource_limits={}, extra_resource_guarantees={})), '\n", + " \"config_maps=[ConfigMap(name='init', key='init', \"\n", + " \"mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, \"\n", + " \"content='set -x \\\\n\\\\ncd /workspace\\\\n\\\\ngit clone \"\n", + " \"\\\\'https://github.com/eoap/stac-eoap.git\\\\'\\\\n\\\\ncode-server \"\n", + " '--install-extension ms-python.python \\\\ncode-server --install-extension '\n", + " 'redhat.vscode-yaml\\\\ncode-server --install-extension '\n", + " 'sbg-rabix.benten-cwl\\\\ncode-server --install-extension '\n", + " 'ms-toolsai.jupyter\\\\n\\\\nln -s /workspace/.local/share/code-server/extensions '\n", + " '/workspace/extensions\\\\n\\\\nmkdir -p /workspace/User/\\\\n\\\\necho '\n", + " '\\\\\\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\\\\\' > '\n", + " '/workspace/User/settings.json\\\\n\\\\npython -m venv /workspace/.venv\\\\nsource '\n", + " '/workspace/.venv/bin/activate\\\\n/workspace/.venv/bin/python -m pip install '\n", + " '--no-cache-dir stactools rasterio requests stac-asset click-logging tabulate '\n", + " 'tqdm pystac-client ipykernel loguru scikit-image rio_stac '\n", + " 'boto3==1.35.23\\\\n\\\\n/workspace/.venv/bin/python -m pip install --index-url '\n", + " 'https://test.pypi.org/simple cwl-wrapper\\\\n\\\\n/workspace/.venv/bin/python -m '\n", + " 'ipykernel install --user --name stac_env --display-name \"Python '\n", + " '(STAC)\"\\\\n\\\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\\\nexport '\n", + " 'AWS_ACCESS_KEY_ID=\"test\"\\\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\\\naws s3 mb '\n", + " \"s3://results --endpoint-url=http://localstack:4566\\\\n\\\\nexit 0', \"\n", + " \"persist=False), ConfigMap(name='bash-rc', key='bash-rc', \"\n", + " \"mount_path='/workspace/.bashrc', default_mode=None, readonly=True, \"\n", + " 'content=\\'alias ll=\"ls -l\"\\\\nalias calrissian=\"/opt/conda/bin/calrissian '\n", + " '--pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout '\n", + " '/calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix '\n", + " '/calrissian/tmp/ --outdir /calrissian/\"\\\\nalias '\n", + " 'cwltool=\"/opt/conda/bin/cwltool --podman\"\\\\n. '\n", + " '/home/jovyan/.bashrc\\\\n\\\\n#alias aws=\"aws '\n", + " '--endpoint-url=http://localstack:4566\"\\\\n\\\\n# >>> conda initialize >>>\\\\n# '\n", + " \"!! Contents within this block are managed by \\\\'conda init\\\\' \"\n", + " '!!\\\\n__conda_setup=\"$(\\\\\\'/opt/conda/bin/conda\\\\\\' \\\\\\'shell.bash\\\\\\' '\n", + " '\\\\\\'hook\\\\\\' 2> /dev/null)\"\\\\nif [ $? -eq 0 ]; then\\\\n eval '\n", + " '\"$__conda_setup\"\\\\nelse\\\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; '\n", + " 'then\\\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\\\n else\\\\n '\n", + " 'export PATH=\"/srv/conda/bin:$PATH\"\\\\n fi\\\\nfi\\\\nunset '\n", + " '__conda_setup\\\\n\\\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\\\n '\n", + " '. \"/opt/conda/etc/profile.d/mamba.sh\"\\\\nfi\\\\n# <<< conda initialize '\n", + " '<<<\\\\n\\\\na={{spawner.user.name}}\\\\n\\\\nalias aws=\"aws '\n", + " '--endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"\\', '\n", + " \"persist=False), ConfigMap(name='bash-login', key='bash-login', \"\n", + " \"mount_path='/workspace/.bash_login', default_mode=None, readonly=True, \"\n", + " \"content='source /workspace/.bashrc\\\\n', persist=False)], \"\n", + " \"volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', \"\n", + " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", + " \"access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': \"\n", + " \"'/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': \"\n", + " \"'/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.local', \"\n", + " \"'XDG_DATA_HOME': '/workspace/.local/share/', 'CWLTOOL_OPTIONS': '--podman', \"\n", + " \"'CODE_SERVER_WS': '/workspace/stac-eoap', 'AWS_DEFAULT_REGION': 'us-east-1', \"\n", + " \"'AWS_ACCESS_KEY_ID': 'test', 'AWS_SECRET_ACCESS_KEY': 'test'}, \"\n", + " 'default_url=None, node_selector={}, role_bindings=[], '\n", + " \"image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, \"\n", + " \"data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], \"\n", + " \"init_containers=[InitContainer(name='init-file-on-volume', \"\n", + " \"image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh \"\n", + " \"/opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', \"\n", + " \"mount_path='/workspace'), InitContainerVolumeMount(name='init', \"\n", + " \"mount_path='/opt/init/.init.sh', sub_path='init')])], \"\n", + " \"manifests=[Manifest(name='manifests', key='manifests', \"\n", + " \"content=[{'apiVersion': 'v1', 'kind': 'ServiceAccount', 'metadata': {'name': \"\n", + " \"'localstack'}}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': \"\n", + " \"'Role', 'metadata': {'name': 'localstack'}, 'rules': [{'apiGroups': [''], \"\n", + " \"'resources': ['pods'], 'verbs': ['*']}, {'apiGroups': [''], 'resources': \"\n", + " \"['pods/log'], 'verbs': ['get']}, {'apiGroups': [''], 'resources': \"\n", + " \"['pods/exec'], 'verbs': ['get', 'create']}, {'apiGroups': [''], 'resources': \"\n", + " \"['services'], 'verbs': ['get', 'list']}]}, {'apiVersion': \"\n", + " \"'rbac.authorization.k8s.io/v1', 'kind': 'RoleBinding', 'metadata': {'name': \"\n", + " \"'localstack'}, 'subjects': [{'kind': 'ServiceAccount', 'name': \"\n", + " \"'localstack'}], 'roleRef': {'kind': 'Role', 'name': 'localstack', \"\n", + " \"'apiGroup': 'rbac.authorization.k8s.io'}}, {'apiVersion': 'v1', 'kind': \"\n", + " \"'Service', 'metadata': {'name': 'localstack'}, 'spec': {'type': 'ClusterIP', \"\n", + " \"'ports': [{'name': 'edge', 'port': 4566, 'targetPort': 4566}, {'name': \"\n", + " \"'external-service-port-4510', 'port': 4510, 'targetPort': 'ext-svc-4510'}, \"\n", + " \"{'name': 'external-service-port-4511', 'port': 4511, 'targetPort': \"\n", + " \"'ext-svc-4511'}, {'name': 'external-service-port-4512', 'port': 4512, \"\n", + " \"'targetPort': 'ext-svc-4512'}, {'name': 'external-service-port-4513', \"\n", + " \"'port': 4513, 'targetPort': 'ext-svc-4513'}, {'name': \"\n", + " \"'external-service-port-4514', 'port': 4514, 'targetPort': 'ext-svc-4514'}, \"\n", + " \"{'name': 'external-service-port-4515', 'port': 4515, 'targetPort': \"\n", + " \"'ext-svc-4515'}, {'name': 'external-service-port-4516', 'port': 4516, \"\n", + " \"'targetPort': 'ext-svc-4516'}, {'name': 'external-service-port-4517', \"\n", + " \"'port': 4517, 'targetPort': 'ext-svc-4517'}, {'name': \"\n", + " \"'external-service-port-4518', 'port': 4518, 'targetPort': 'ext-svc-4518'}, \"\n", + " \"{'name': 'external-service-port-4519', 'port': 4519, 'targetPort': \"\n", + " \"'ext-svc-4519'}, {'name': 'external-service-port-4520', 'port': 4520, \"\n", + " \"'targetPort': 'ext-svc-4520'}, {'name': 'external-service-port-4521', \"\n", + " \"'port': 4521, 'targetPort': 'ext-svc-4521'}, {'name': \"\n", + " \"'external-service-port-4522', 'port': 4522, 'targetPort': 'ext-svc-4522'}, \"\n", + " \"{'name': 'external-service-port-4523', 'port': 4523, 'targetPort': \"\n", + " \"'ext-svc-4523'}, {'name': 'external-service-port-4524', 'port': 4524, \"\n", + " \"'targetPort': 'ext-svc-4524'}, {'name': 'external-service-port-4525', \"\n", + " \"'port': 4525, 'targetPort': 'ext-svc-4525'}, {'name': \"\n", + " \"'external-service-port-4526', 'port': 4526, 'targetPort': 'ext-svc-4526'}, \"\n", + " \"{'name': 'external-service-port-4527', 'port': 4527, 'targetPort': \"\n", + " \"'ext-svc-4527'}, {'name': 'external-service-port-4528', 'port': 4528, \"\n", + " \"'targetPort': 'ext-svc-4528'}, {'name': 'external-service-port-4529', \"\n", + " \"'port': 4529, 'targetPort': 'ext-svc-4529'}, {'name': \"\n", + " \"'external-service-port-4530', 'port': 4530, 'targetPort': 'ext-svc-4530'}, \"\n", + " \"{'name': 'external-service-port-4531', 'port': 4531, 'targetPort': \"\n", + " \"'ext-svc-4531'}, {'name': 'external-service-port-4532', 'port': 4532, \"\n", + " \"'targetPort': 'ext-svc-4532'}, {'name': 'external-service-port-4533', \"\n", + " \"'port': 4533, 'targetPort': 'ext-svc-4533'}, {'name': \"\n", + " \"'external-service-port-4534', 'port': 4534, 'targetPort': 'ext-svc-4534'}, \"\n", + " \"{'name': 'external-service-port-4535', 'port': 4535, 'targetPort': \"\n", + " \"'ext-svc-4535'}, {'name': 'external-service-port-4536', 'port': 4536, \"\n", + " \"'targetPort': 'ext-svc-4536'}, {'name': 'external-service-port-4537', \"\n", + " \"'port': 4537, 'targetPort': 'ext-svc-4537'}, {'name': \"\n", + " \"'external-service-port-4538', 'port': 4538, 'targetPort': 'ext-svc-4538'}, \"\n", + " \"{'name': 'external-service-port-4539', 'port': 4539, 'targetPort': \"\n", + " \"'ext-svc-4539'}, {'name': 'external-service-port-4540', 'port': 4540, \"\n", + " \"'targetPort': 'ext-svc-4540'}, {'name': 'external-service-port-4541', \"\n", + " \"'port': 4541, 'targetPort': 'ext-svc-4541'}, {'name': \"\n", + " \"'external-service-port-4542', 'port': 4542, 'targetPort': 'ext-svc-4542'}, \"\n", + " \"{'name': 'external-service-port-4543', 'port': 4543, 'targetPort': \"\n", + " \"'ext-svc-4543'}, {'name': 'external-service-port-4544', 'port': 4544, \"\n", + " \"'targetPort': 'ext-svc-4544'}, {'name': 'external-service-port-4545', \"\n", + " \"'port': 4545, 'targetPort': 'ext-svc-4545'}, {'name': \"\n", + " \"'external-service-port-4546', 'port': 4546, 'targetPort': 'ext-svc-4546'}, \"\n", + " \"{'name': 'external-service-port-4547', 'port': 4547, 'targetPort': \"\n", + " \"'ext-svc-4547'}, {'name': 'external-service-port-4548', 'port': 4548, \"\n", + " \"'targetPort': 'ext-svc-4548'}, {'name': 'external-service-port-4549', \"\n", + " \"'port': 4549, 'targetPort': 'ext-svc-4549'}, {'name': \"\n", + " \"'external-service-port-4550', 'port': 4550, 'targetPort': 'ext-svc-4550'}, \"\n", + " \"{'name': 'external-service-port-4551', 'port': 4551, 'targetPort': \"\n", + " \"'ext-svc-4551'}, {'name': 'external-service-port-4552', 'port': 4552, \"\n", + " \"'targetPort': 'ext-svc-4552'}, {'name': 'external-service-port-4553', \"\n", + " \"'port': 4553, 'targetPort': 'ext-svc-4553'}, {'name': \"\n", + " \"'external-service-port-4554', 'port': 4554, 'targetPort': 'ext-svc-4554'}, \"\n", + " \"{'name': 'external-service-port-4555', 'port': 4555, 'targetPort': \"\n", + " \"'ext-svc-4555'}, {'name': 'external-service-port-4556', 'port': 4556, \"\n", + " \"'targetPort': 'ext-svc-4556'}, {'name': 'external-service-port-4557', \"\n", + " \"'port': 4557, 'targetPort': 'ext-svc-4557'}, {'name': \"\n", + " \"'external-service-port-4558', 'port': 4558, 'targetPort': 'ext-svc-4558'}, \"\n", + " \"{'name': 'external-service-port-4559', 'port': 4559, 'targetPort': \"\n", + " \"'ext-svc-4559'}], 'selector': {'app.kubernetes.io/name': 'localstack', \"\n", + " \"'app.kubernetes.io/instance': 'localstack'}}}, {'apiVersion': 'apps/v1', \"\n", + " \"'kind': 'Deployment', 'metadata': {'name': 'localstack'}, 'spec': \"\n", + " \"{'replicas': 1, 'strategy': {'type': 'RollingUpdate'}, 'selector': \"\n", + " \"{'matchLabels': {'app.kubernetes.io/name': 'localstack', \"\n", + " \"'app.kubernetes.io/instance': 'localstack'}}, 'template': {'metadata': \"\n", + " \"{'labels': {'app': 'localstack-{{ spawner.user.name }}', \"\n", + " \"'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': \"\n", + " \"'localstack'}}, 'spec': {'serviceAccountName': 'localstack', \"\n", + " \"'securityContext': {}, 'containers': [{'name': 'localstack', \"\n", + " \"'securityContext': {}, 'image': 'localstack/localstack:latest', \"\n", + " \"'imagePullPolicy': 'IfNotPresent', 'ports': [{'name': 'edge', \"\n", + " \"'containerPort': 4566, 'protocol': 'TCP'}, {'name': 'ext-svc-4510', \"\n", + " \"'containerPort': 4510, 'protocol': 'TCP'}, {'name': 'ext-svc-4511', \"\n", + " \"'containerPort': 4511, 'protocol': 'TCP'}, {'name': 'ext-svc-4512', \"\n", + " \"'containerPort': 4512, 'protocol': 'TCP'}, {'name': 'ext-svc-4513', \"\n", + " \"'containerPort': 4513, 'protocol': 'TCP'}, {'name': 'ext-svc-4514', \"\n", + " \"'containerPort': 4514, 'protocol': 'TCP'}, {'name': 'ext-svc-4515', \"\n", + " \"'containerPort': 4515, 'protocol': 'TCP'}, {'name': 'ext-svc-4516', \"\n", + " \"'containerPort': 4516, 'protocol': 'TCP'}, {'name': 'ext-svc-4517', \"\n", + " \"'containerPort': 4517, 'protocol': 'TCP'}, {'name': 'ext-svc-4518', \"\n", + " \"'containerPort': 4518, 'protocol': 'TCP'}, {'name': 'ext-svc-4519', \"\n", + " \"'containerPort': 4519, 'protocol': 'TCP'}, {'name': 'ext-svc-4520', \"\n", + " \"'containerPort': 4520, 'protocol': 'TCP'}, {'name': 'ext-svc-4521', \"\n", + " \"'containerPort': 4521, 'protocol': 'TCP'}, {'name': 'ext-svc-4522', \"\n", + " \"'containerPort': 4522, 'protocol': 'TCP'}, {'name': 'ext-svc-4523', \"\n", + " \"'containerPort': 4523, 'protocol': 'TCP'}, {'name': 'ext-svc-4524', \"\n", + " \"'containerPort': 4524, 'protocol': 'TCP'}, {'name': 'ext-svc-4525', \"\n", + " \"'containerPort': 4525, 'protocol': 'TCP'}, {'name': 'ext-svc-4526', \"\n", + " \"'containerPort': 4526, 'protocol': 'TCP'}, {'name': 'ext-svc-4527', \"\n", + " \"'containerPort': 4527, 'protocol': 'TCP'}, {'name': 'ext-svc-4528', \"\n", + " \"'containerPort': 4528, 'protocol': 'TCP'}, {'name': 'ext-svc-4529', \"\n", + " \"'containerPort': 4529, 'protocol': 'TCP'}, {'name': 'ext-svc-4530', \"\n", + " \"'containerPort': 4530, 'protocol': 'TCP'}, {'name': 'ext-svc-4531', \"\n", + " \"'containerPort': 4531, 'protocol': 'TCP'}, {'name': 'ext-svc-4532', \"\n", + " \"'containerPort': 4532, 'protocol': 'TCP'}, {'name': 'ext-svc-4533', \"\n", + " \"'containerPort': 4533, 'protocol': 'TCP'}, {'name': 'ext-svc-4534', \"\n", + " \"'containerPort': 4534, 'protocol': 'TCP'}, {'name': 'ext-svc-4535', \"\n", + " \"'containerPort': 4535, 'protocol': 'TCP'}, {'name': 'ext-svc-4536', \"\n", + " \"'containerPort': 4536, 'protocol': 'TCP'}, {'name': 'ext-svc-4537', \"\n", + " \"'containerPort': 4537, 'protocol': 'TCP'}, {'name': 'ext-svc-4538', \"\n", + " \"'containerPort': 4538, 'protocol': 'TCP'}, {'name': 'ext-svc-4539', \"\n", + " \"'containerPort': 4539, 'protocol': 'TCP'}, {'name': 'ext-svc-4540', \"\n", + " \"'containerPort': 4540, 'protocol': 'TCP'}, {'name': 'ext-svc-4541', \"\n", + " \"'containerPort': 4541, 'protocol': 'TCP'}, {'name': 'ext-svc-4542', \"\n", + " \"'containerPort': 4542, 'protocol': 'TCP'}, {'name': 'ext-svc-4543', \"\n", + " \"'containerPort': 4543, 'protocol': 'TCP'}, {'name': 'ext-svc-4544', \"\n", + " \"'containerPort': 4544, 'protocol': 'TCP'}, {'name': 'ext-svc-4545', \"\n", + " \"'containerPort': 4545, 'protocol': 'TCP'}, {'name': 'ext-svc-4546', \"\n", + " \"'containerPort': 4546, 'protocol': 'TCP'}, {'name': 'ext-svc-4547', \"\n", + " \"'containerPort': 4547, 'protocol': 'TCP'}, {'name': 'ext-svc-4548', \"\n", + " \"'containerPort': 4548, 'protocol': 'TCP'}, {'name': 'ext-svc-4549', \"\n", + " \"'containerPort': 4549, 'protocol': 'TCP'}, {'name': 'ext-svc-4550', \"\n", + " \"'containerPort': 4550, 'protocol': 'TCP'}, {'name': 'ext-svc-4551', \"\n", + " \"'containerPort': 4551, 'protocol': 'TCP'}, {'name': 'ext-svc-4552', \"\n", + " \"'containerPort': 4552, 'protocol': 'TCP'}, {'name': 'ext-svc-4553', \"\n", + " \"'containerPort': 4553, 'protocol': 'TCP'}, {'name': 'ext-svc-4554', \"\n", + " \"'containerPort': 4554, 'protocol': 'TCP'}, {'name': 'ext-svc-4555', \"\n", + " \"'containerPort': 4555, 'protocol': 'TCP'}, {'name': 'ext-svc-4556', \"\n", + " \"'containerPort': 4556, 'protocol': 'TCP'}, {'name': 'ext-svc-4557', \"\n", + " \"'containerPort': 4557, 'protocol': 'TCP'}, {'name': 'ext-svc-4558', \"\n", + " \"'containerPort': 4558, 'protocol': 'TCP'}, {'name': 'ext-svc-4559', \"\n", + " \"'containerPort': 4559, 'protocol': 'TCP'}], 'livenessProbe': \"\n", + " \"{'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, \"\n", + " \"'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': \"\n", + " \"'/_localstack/health', 'port': 'edge'}}, 'readinessProbe': \"\n", + " \"{'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, \"\n", + " \"'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': \"\n", + " \"'/_localstack/health', 'port': 'edge'}}, 'resources': {}, 'env': [{'name': \"\n", + " \"'DEBUG', 'value': '0'}, {'name': 'EXTERNAL_SERVICE_PORTS_START', 'value': \"\n", + " \"'4510'}, {'name': 'EXTERNAL_SERVICE_PORTS_END', 'value': '4560'}, {'name': \"\n", + " \"'LOCALSTACK_K8S_SERVICE_NAME', 'value': 'localstack'}, {'name': \"\n", + " \"'LOCALSTACK_K8S_NAMESPACE', 'valueFrom': {'fieldRef': {'fieldPath': \"\n", + " \"'metadata.namespace'}}}, {'name': 'LAMBDA_RUNTIME_EXECUTOR', 'value': \"\n", + " \"'docker'}, {'name': 'LAMBDA_K8S_IMAGE_PREFIX', 'value': \"\n", + " \"'localstack/lambda-'}, {'name': 'LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT', \"\n", + " \"'value': '60'}, {'name': 'OVERRIDE_IN_DOCKER', 'value': '1'}]}], 'volumes': \"\n", + " \"[]}}}}, {'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': \"\n", + " \"'my-config'}, 'data': {'ENV_VAR1': 'value1', 'ENV_VAR2': 'value2'}}, \"\n", + " \"{'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'my-secret'}, \"\n", + " \"'type': 'Opaque', 'data': {'SECRET_KEY1': 'dmFsdWUx', 'SECRET_KEY2': \"\n", + " \"'dmFsdWUy'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': \"\n", + " \"'aws-credentials-{{ spawner.user.name }}'}, 'type': 'Opaque', 'data': \"\n", + " \"{'credentials': \"\n", + " \"'W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg='}}, \"\n", + " \"{'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', \"\n", + " \"'metadata': {'name': 'data-by-name', 'namespace': 'jupyter-{{ \"\n", + " \"spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': \"\n", + " \"{'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': \"\n", + " \"{'name': 'data-by-name', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': \"\n", + " \"'secret-value', 'remoteRef': {'key': 'secret-one', 'property': \"\n", + " \"'the-key'}}]}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': \"\n", + " \"'ExternalSecret', 'metadata': {'name': 'eoepca-plus-secret-ro', 'namespace': \"\n", + " \"'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', \"\n", + " \"'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': \"\n", + " \"'k8s-secret-store'}, 'target': {'name': 'eoepca-plus-secret-ro', \"\n", + " \"'creationPolicy': 'Owner'}, 'data': [{'secretKey': '.dockerconfigjson', \"\n", + " \"'remoteRef': {'key': 'eoepca-plus-secret-ro', 'property': \"\n", + " \"'.dockerconfigjson'}}]}}], persist=False)], env_from_config_maps=None, \"\n", + " 'env_from_secrets=None, secret_mounts=None), '\n", + " \"Profile(id='profile_studio_desktop_qgis', groups=['group-a', 'group-b'], \"\n", + " \"definition=ProfileDefinition(display_name='QGIS on a Remote Desktop', \"\n", + " \"description='Spatial visualization and decision-making tools for everyone', \"\n", + " \"slug='eoepca_desktop_qgis', default=False, \"\n", + " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, '\n", + " \"mem_limit='2G', mem_guarantee=None, \"\n", + " \"image='eoepca/iga-remote-desktop-qgis:1.1.3', extra_resource_limits={}, \"\n", + " \"extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', \"\n", + " \"key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, \"\n", + " 'readonly=True, content=\\'alias ll=\"ls -l\"\\\\nalias '\n", + " 'calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors '\n", + " '/etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json '\n", + " '--max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir '\n", + " '/calrissian/\"\\\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\\\n. '\n", + " '/home/jovyan/.bashrc\\\\n\\\\n#alias aws=\"aws '\n", + " '--endpoint-url=http://localstack:4566\"\\\\n\\\\n# >>> conda initialize >>>\\\\n# '\n", + " \"!! Contents within this block are managed by \\\\'conda init\\\\' \"\n", + " '!!\\\\n__conda_setup=\"$(\\\\\\'/opt/conda/bin/conda\\\\\\' \\\\\\'shell.bash\\\\\\' '\n", + " '\\\\\\'hook\\\\\\' 2> /dev/null)\"\\\\nif [ $? -eq 0 ]; then\\\\n eval '\n", + " '\"$__conda_setup\"\\\\nelse\\\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; '\n", + " 'then\\\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\\\n else\\\\n '\n", + " 'export PATH=\"/srv/conda/bin:$PATH\"\\\\n fi\\\\nfi\\\\nunset '\n", + " '__conda_setup\\\\n\\\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\\\n '\n", + " '. \"/opt/conda/etc/profile.d/mamba.sh\"\\\\nfi\\\\n# <<< conda initialize '\n", + " '<<<\\\\n\\\\na={{spawner.user.name}}\\\\n\\\\nalias aws=\"aws '\n", + " '--endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"\\', '\n", + " \"persist=False), ConfigMap(name='bash-login', key='bash-login', \"\n", + " \"mount_path='/workspace/.bash_login', default_mode=None, readonly=True, \"\n", + " \"content='source /workspace/.bashrc\\\\n', persist=False)], \"\n", + " \"volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', \"\n", + " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", + " \"access_modes=['ReadWriteOnce'], \"\n", + " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", + " \"persist=True)], pod_env_vars={'HOME': '/workspace'}, default_url='desktop', \"\n", + " 'node_selector={}, role_bindings=None, image_pull_secrets=[], '\n", + " 'init_containers=[], manifests=None, env_from_config_maps=None, '\n", + " 'env_from_secrets=None, secret_mounts=None)]')\n" + ] + } + ], + "source": [ + "pprint(profiles)\n", + "config = Config(profiles=profiles) # type: ignore\n", + "pprint(f'config: {config}')\n", + "eoepca_demo_config_path = str(Path(current_dir).parent.parent / 'eoepca-demo' / 'config.yml')\n", + "with open(eoepca_demo_config_path, \"w\") as file:\n", + " yaml.dump(config.model_dump(), file)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.5" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/examples/generate-config.sh b/docs/examples/generate-config.sh new file mode 100755 index 0000000..0238b9d --- /dev/null +++ b/docs/examples/generate-config.sh @@ -0,0 +1,4 @@ +#exit 0 +export PYTHONPATH=$PWD/`dirname $0`/. + +.env-apphub-configurator/bin/python3 -m generate_config \ No newline at end of file diff --git a/docs/examples/generate_config.py b/docs/examples/generate_config.py new file mode 100644 index 0000000..a09a990 --- /dev/null +++ b/docs/examples/generate_config.py @@ -0,0 +1,168 @@ +from apphub_configurator.models import * +import yaml +from pathlib import Path +import os +from loguru import logger +from apphub_configurator.helpers import ( + load_config_map, + load_manifests, + create_init_container, + load_init_script, +) +import click + +logger.info("Generating config file...") + +storage_class_rwo = "standard" +storage_class_rwx = "standard" +profiles = [] +workspace_volume_size = "50Gi" +calrissian_volume_size = "50Gi" +image = "eoepca/pde-code-server:develop" +node_selector = {} + +# get the current directory +current_dir = Path(os.path.dirname(os.path.realpath(__file__))) +parent_dir = current_dir.parent + +# load the manifests +localstack_manifest_path = os.path.join(parent_dir, "manifests/manifest.yaml") +# localstack_manifest +localstack_manifest = load_manifests( + name="localstack", + key="localstack", + file_path=localstack_manifest_path, +) + +dask_gateway_manifest_path = os.path.join(parent_dir, "manifests/dask-gateway.yaml") +# Dask Gateway manifest +dask_gateway_manifest = load_manifests( + name="dask-gateway", + key="dask-gateway", + file_path=dask_gateway_manifest_path, +) +kaniko_manifest_path = os.path.join(parent_dir, "manifests/kaniko.yaml") +kaniko_manifest = load_manifests( + name="kaniko", + key="kaniko", + file_path=kaniko_manifest_path, +) +# volumes +workspace_volume = Volume( + name="workspace-volume", + size=workspace_volume_size, + claim_name="workspace-claim", + mount_path="/workspace", + storage_class=storage_class_rwo, + access_modes=["ReadWriteOnce"], + volume_mount=VolumeMount(name="workspace-volume", mount_path="/workspace"), + persist=True, +) + +calrissian_volume = Volume( + name="calrissian-volume", + claim_name="calrissian-claim", + size=calrissian_volume_size, + storage_class=storage_class_rwx, + access_modes=["ReadWriteMany"], + volume_mount=VolumeMount(name="calrissian-volume", mount_path="/calrissian"), + persist=False, +) + +bash_login_file_path = os.path.join(parent_dir, "config-maps/bash-login") +bash_login_cm = load_config_map( + name="bash-login", + key="bash-login", + file_name=bash_login_file_path, + mount_path="/etc/profile.d/bash-login.sh", +) + +bash_rc_cm_file_path = os.path.join(parent_dir, "config-maps/bash-rc") +bash_rc_cm = load_config_map( + name="bash-rc", + key="bash-rc", + file_name=bash_rc_cm_file_path, + mount_path="/workspace/.bashrc", +) +init_cm_file_path = os.path.join(parent_dir, "config-maps/init.sh") +init_cm = load_init_script(init_cm_file_path) + +init_container = create_init_container( + image=image, + volume=workspace_volume, + mount_path="/calrissian", +) + +profile_1 = Profile( + id=f"profile_1", + groups=["group-a", "group-b"], + definition=ProfileDefinition( + display_name="Coder demo init script", + description="This profile is used to demonstrate the use of an init script", + slug="profile_1", + default=False, + kubespawner_override=KubespawnerOverride( + cpu_guarantee=1, + cpu_limit=2, + mem_guarantee="4G", + mem_limit="6G", + image=image, + ), + ), + node_selector=node_selector, + volumes=[calrissian_volume, workspace_volume], + config_maps=[ + init_cm, + ConfigMap( + name="dask-gateway-config", + key="gateway", + mount_path="/etc/dask/gateway.yaml", + readonly=True, + ), + ConfigMap( + name="dask-gateway-config-ws", + key="gateway", + mount_path="/workspace/.config/dask/gateway.yaml", + readonly=True, + ), + bash_login_cm, + bash_rc_cm, + ], + pod_env_vars={ + "HOME": "/workspace", + "CONDA_ENVS_PATH": "/workspace/.envs", + "CONDARC": "/workspace/.condarc", + "XDG_RUNTIME_DIR": "/workspace/.local", + "CODE_SERVER_WS": "/workspace/mastering-app-package", + "DASK_GATEWAY": "http://traefik-dask-gw-jupyter-{{ spawner.user.name }}-dask-gateway.jupyter-{{ spawner.user.name }}.svc.cluster.local:80", + }, + init_containers=[init_container], + manifests=[localstack_manifest, dask_gateway_manifest, kaniko_manifest], + env_from_config_maps=["my-config"], + env_from_secrets=["my-secret", "data-by-name"], + secret_mounts=[ + SecretMount( + name="aws-credentials-{{ spawner.user.name }}", mount_path="/workspace/.aws" + ), + SecretMount(name="data-by-name", mount_path="/workspace/.data-by-name"), + SecretMount( + name="eoepca-plus-secret-ro", + mount_path="/workspace/.docker/config.json", + sub_path=".dockerconfigjson", + ), + ], + image_pull_secrets=[ImagePullSecret(name="eoepca-plus-secret-ro")], +) + +profiles.append(profile_1) + +config = Config(profiles=profiles) +config_file_path = str(Path(current_dir).parent.parent / 'files' / 'hub' / 'config.yml') + + +with open(config_file_path, "w") as file: + yaml.dump(config.model_dump(), file, width=200) + +logger.success( + f"Config file generated successfully at {config_file_path}" +) \ No newline at end of file diff --git a/docs/hands-on.md b/docs/hands-on.md index 7229224..4af4559 100644 --- a/docs/hands-on.md +++ b/docs/hands-on.md @@ -1,6 +1,6 @@ # Hands-on - Config generator: -Open the an example [notebook](../apphub-configurator/examples/config-generator.ipynb) to generate a `config.yaml` with your desired profile configuration +Open the an example [notebook](./examples/config-generator.ipynb) to generate a `config.yaml` with your desired profile configuration - Add `eoepca/application-hub` from helm repositories. - Deploy the ApplicationHub on a remote cluster using skaffold. \ No newline at end of file From 106f59daf574533c5f4853d7a75c84b636301ccb Mon Sep 17 00:00:00 2001 From: pmembari Date: Tue, 8 Apr 2025 10:18:12 +0200 Subject: [PATCH 65/86] ci: copy example under docs --- .github/workflows/docs.yml | 1 + docs/configuration.md | 2 +- docs/examples/config-generator.ipynb | 1348 -------------------------- docs/examples/generate-config.sh | 4 - docs/examples/generate_config.py | 168 ---- docs/hands-on.md | 2 +- 6 files changed, 3 insertions(+), 1522 deletions(-) delete mode 100644 docs/examples/config-generator.ipynb delete mode 100755 docs/examples/generate-config.sh delete mode 100644 docs/examples/generate_config.py diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0ebef06..dc1d6b4 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -23,5 +23,6 @@ jobs: with: python-version: 3.x - run: | + cp apphub-configurator/examples/config-generator.ipynb docs/config-generator.ipynb pip install mkdocs-material mkdocs-mermaid2-plugin mkdocs-jupyter mkdocs gh-deploy --force \ No newline at end of file diff --git a/docs/configuration.md b/docs/configuration.md index b058c00..7224018 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -66,7 +66,7 @@ The module defines several Pydantic-based data classes representing Kubernetes o These data classes provide a structured approach to defining Kubernetes resources. ### Examples: -The user can follow the examples provided to setup different profiles on Application Hub through the provided [notebook](../apphub-configurator/examples/config-generator.ipynb) under example folder. In the notebook, the user is able to configure different profile including: +The user can follow the examples provided to setup different profiles on Application Hub through the provided [notebook](./config-generator.ipynb) under example folder. In the notebook, the user is able to configure different profile including: * Coder diff --git a/docs/examples/config-generator.ipynb b/docs/examples/config-generator.ipynb deleted file mode 100644 index cd1db94..0000000 --- a/docs/examples/config-generator.ipynb +++ /dev/null @@ -1,1348 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Generate App Hub's configuration\n", - "\n", - "This notebook aim to produce a `config.yml` file to generate all the configuration needs to be set for deployment of App Hub on a remote cluster using Devops tools. \n", - "\n", - "This Notebook can be used for the following requirements:\n", - "\n", - "* Initial dependencies\n", - "* Create the configuration for kubernetes' `PV`s, and `PVC`s(e.g. workspace volume, and calrissian volume)\n", - "* Generate the configuration for kubernetes' config maps\n", - "* Creation of different Profiles \n", - "\n", - "Table of Content:\n", - "- [Initial dependencies](#initial-dependencies)\n", - " - [Configuration](#configuration)\n", - "- [Volumes](#volumes)\n", - " - [Workspace volume](#workspace-volume)\n", - " - [Calrissian volume](#calrissian-volume)\n", - "- [Configmaps](#configmaps)\n", - " - [bash login Configmap](#bash-login-configmap)\n", - " - [bash rc Configmap](#bash-rc-configmap) \n", - " - [QGIS Configmap](#qgis-configmap) \n", - " - [Stage-in/out Configmap](#stage-inout-configmap)\n", - "- [Profiles](#profiles)\n", - " - [Coder](#coder)\n", - " - [Coder with init.sh](#coder-with-initsh)\n", - " - [JupyterLab](#jupyterlab)\n", - " - [JupyterLab Plus](#jupyterlab-plus)\n", - " - [E-Learning](#e-learning)\n", - " - [QGIS](#qgis)\n", - "- [Write Configuration](#write-configuration)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Initial dependencies" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!pip install apphub-configurator" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "PosixPath('/home/t2/Desktop/p/EOEPCA/application-hub-context/apphub-configurator/examples')" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "import yaml\n", - "from apphub_configurator.models import *\n", - "from pathlib import Path\n", - "import os\n", - "import json\n", - "from pprint import pprint\n", - "current_dir = Path(os.getcwd())\n", - "parent_dir = current_dir.parent\n", - "current_dir" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Configuration\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "storage_class_rwo = \"managed-nfs-storage\"\n", - "storage_class_rwx = \"managed-nfs-storage\"\n", - "\n", - "workspace_volume_size = \"50Gi\"\n", - "calrissian_volume_size = \"50Gi\"" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Volumes\n", - "\n", - "In this section, the user will provide the data class objects for creation of volume:\n", - "- Volume for workspace\n", - "- Volume for calrissian\n", - "These two configuration will be use " - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Workspace Volume\n", - "In this section, the user will providing the configuration of a kubernetes Volume for development environment (i.e workspace). It is important to mention that this volume must keep data persistently. therefore, the user set `persist=True`" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "workspace_volume = Volume( # type: ignore\n", - " name=\"workspace-volume\",\n", - " size=workspace_volume_size,\n", - " claim_name=\"workspace-claim\",\n", - " mount_path=\"/workspace\",\n", - " storage_class=storage_class_rwo,\n", - " access_modes=[\"ReadWriteOnce\"],\n", - " volume_mount=VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"), # type: ignore\n", - " persist=True,\n", - ")\n", - "workspace_volume" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Calrissian Volume\n", - "In this section, the user will configure a **Kubernetes Volume** for Calrissian jobs. Since the job runs within Calrissian and does not require data retention on the Calrissian pod, therefore the user should set `persist=False`. " - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False)" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "calrissian_volume = Volume( # type: ignore\n", - " name=\"calrissian-volume\",\n", - " claim_name=\"calrissian-claim\",\n", - " size=calrissian_volume_size,\n", - " storage_class=storage_class_rwx,\n", - " access_modes=[\"ReadWriteMany\"],\n", - " volume_mount=VolumeMount(name=\"calrissian-volume\", mount_path=\"/calrissian\"), # type: ignore\n", - " persist=False,\n", - ")\n", - "calrissian_volume" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ConfigMaps\n", - "\n", - "In this section, the user will provide the configuration for some kubernetes configmaps that is commonly used in this notebook including:\n", - "- bash login Configmap\n", - "- bash rc Configmap\n", - "- QGIS Configmap\n", - "- Stage-in/out Configmap\n", - "These configmaps will be mounted as files on different pods. " - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### bash login Configmap\n", - "\n", - "This configmap file aims to configure the Terminal across different profiles. " - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bash_login_file_path = os.path.join(parent_dir, \"config-maps/bash-login\") \n", - "with open(bash_login_file_path, \"r\") as f:\n", - " content = f.read()\n", - "\n", - "bash_login_cm = ConfigMap( # type: ignore\n", - " name=\"bash-login\",\n", - " key=\"bash-login\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/workspace/.bash_login\",\n", - ")\n", - "bash_login_cm" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### bash-rc Configmap\n", - "This section explains how we configure a **Kubernetes ConfigMap** to manage the `.bashrc` file inside a pod. This config map will:\n", - "- It provides useful aliases and environment settings for users.\n", - "- It ensures a consistent environment across different pods." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "bash_rc_cm_file_path = os.path.join(parent_dir, \"config-maps/bash-rc\") \n", - "with open(bash_rc_cm_file_path, \"r\") as f:\n", - " content = f.read()\n", - "bash_rc_cm = ConfigMap( # type: ignore\n", - " name=\"bash-rc\",\n", - " key=\"bash-rc\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/workspace/.bashrc\",\n", - ")\n", - "bash_rc_cm" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### QGIS Configmap\n", - "The cell below will provide a Configmap configuration for QGIS setup." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='mkdir -p /workspace/.config/autostart\\n\\n\\ncat < /workspace/.config/autostart/qgis.desktop \\n[Desktop Entry]\\nEncoding=UTF-8\\nVersion=0.9.4\\nType=Application\\nName=qgis\\nComment=qgis\\nExec=qgis\\nOnlyShowIn=XFCE;\\nRunHook=0\\nStartupNotify=false\\nTerminal=false\\nHidden=false\\nEOF', persist=False)" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "init_qgis_file_path = os.path.join(parent_dir, \"config-maps/init-qgis.sh\") \n", - "with open(init_qgis_file_path, \"r\") as f:\n", - " content = f.read()\n", - "\n", - "init_qgis_cm = ConfigMap( # type: ignore\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - ")\n", - "init_qgis_cm" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Stage-in/out Configmap\n", - "The cell below will provide a Configmap configuration for stage-in/out for [e-learning](#e-learning) profile." - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='set -x \\n\\ncd /workspace\\n\\ngit clone \\'https://github.com/eoap/stac-eoap.git\\'\\n\\ncode-server --install-extension ms-python.python \\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nmkdir -p /workspace/User/\\n\\necho \\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\' > /workspace/User/settings.json\\n\\npython -m venv /workspace/.venv\\nsource /workspace/.venv/bin/activate\\n/workspace/.venv/bin/python -m pip install --no-cache-dir stactools rasterio requests stac-asset click-logging tabulate tqdm pystac-client ipykernel loguru scikit-image rio_stac boto3==1.35.23\\n\\n/workspace/.venv/bin/python -m pip install --index-url https://test.pypi.org/simple cwl-wrapper\\n\\n/workspace/.venv/bin/python -m ipykernel install --user --name stac_env --display-name \"Python (STAC)\"\\n\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\nexport AWS_ACCESS_KEY_ID=\"test\"\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\naws s3 mb s3://results --endpoint-url=http://localstack:4566\\n\\nexit 0', persist=False)" - ] - }, - "execution_count": 8, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "init_stac_file_path = os.path.join(parent_dir, \"config-maps/init-stac.sh\")\n", - "with open(init_stac_file_path, \"r\") as f:\n", - " content = f.read()\n", - "\n", - "init_stac_cm = ConfigMap( # type: ignore\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - ")\n", - "init_stac_cm" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Coder init Configmap\n", - "The cell below will provide a Configmap configuration for stage-in/out for [coder](#coder) profile." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "init_cm_file_path = os.path.join(parent_dir, \"config-maps/init.sh\") \n", - "with open(init_cm_file_path, \"r\") as f:\n", - " content = f.read()\n", - "\n", - "init_coder_cm = ConfigMap(\n", - " name=\"init\",\n", - " key=\"init\",\n", - " content=content,\n", - " readonly=True,\n", - " persist=False,\n", - " mount_path=\"/opt/init/.init.sh\",\n", - " default_mode=\"0660\",\n", - ")\n", - "init_coder_cm" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Profiles\n", - "In the following section, the user will generate the configuration for different profiles on a remote cluster including:\n", - "- [Coder](#coder)\n", - "- [Coder with init.sh](#coder-with-initsh)\n", - "- [Jupyter Lab](#jupyterlab)\n", - "- [JupyterLab Plus](#jupyterlab-plus)\n", - "- [E-learning](#e-learning)\n", - "- [QGIS](#qgis)" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [], - "source": [ - "profiles = []" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Coder\n", - "In the cell below, the user configures two code-server profiles to be deployed on a remote cluster. One profile (**medium**) has more resources than the other (**small**). Two Kubernetes Volumes, discussed in the [Volume section](#volumes), are assigned, and a ConfigMap is mounted. \n" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "coders = {\n", - " \"coder1\": {\n", - " \"display_name\": \"Code Server Small\",\n", - " \"slug\": \"ellip_studio_coder_slug_s\",\n", - " \"cpu_limit\": 2,\n", - " \"mem_limit\": \"8G\",\n", - " },\n", - " \"coder2\": {\n", - " \"display_name\": \"Code Server Medium\",\n", - " \"slug\": \"ellip_studio_coder_slug_m\",\n", - " \"cpu_limit\": 4,\n", - " \"mem_limit\": \"12G\",\n", - " },\n", - "}\n", - "\n", - "for key, value in coders.items():\n", - " coder_definition = ProfileDefinition( # type: ignore\n", - " display_name=value[\"display_name\"],\n", - " slug=value[\"slug\"],\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride( # type: ignore\n", - " cpu_limit=value[\"cpu_limit\"],\n", - " mem_limit=value[\"mem_limit\"],\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " ),\n", - " )\n", - "\n", - " coder_profile = Profile( # type: ignore\n", - " id=f\"profile_studio_{key}\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=coder_definition,\n", - " node_selector={},\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[\n", - " bash_rc_cm,\n", - " ],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " },\n", - " )\n", - "\n", - " profiles.append(coder_profile)\n", - "profiles" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Coder with init.sh\n", - "In the cell below, the user configures the code-server with a new profile, which extends the existing [coder](#coder) profile. The init container is responsible for executing a Bash script to initialize the code-server pod. In this case, the script [`init.sh`](../config-maps/init.sh) runs during pod initialization to clone a Git repository and install extensions on the deployed code-server." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "\n", - "\n", - "init_context_volume_mount = InitContainerVolumeMount( # type: ignore\n", - " mount_path=\"/opt/init/.init.sh\", name=\"init\", sub_path=\"init\"\n", - ")\n", - "init_container = InitContainer( # type: ignore\n", - " name=\"init-file-on-volume\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " command=[\"sh\", \"-c\", \"sh /opt/init/.init.sh\"],\n", - " volume_mounts=[\n", - " VolumeMount(name=\"workspace-volume\", mount_path=\"/workspace\"), # type: ignore\n", - " init_context_volume_mount,\n", - " ],\n", - ")\n", - "\n", - "eoepca_demo_init_script_profile = Profile( # type: ignore\n", - " id=f\"profile_demo_init_script\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition( # type: ignore\n", - " display_name=\"Coder demo init script\",\n", - " description=\"This profile is used to demonstrate the use of an init script\",\n", - " slug=\"eoepca_demo_init_script\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride( # type: ignore\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=\"eoepca/pde-code-server:develop\",\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[calrissian_volume, workspace_volume],\n", - " config_maps=[init_coder_cm],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " \"CONDARC\": \"/workspace/.condarc\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"CODE_SERVER_WS\": \"/workspace/mastering-app-package\",\n", - " },\n", - " init_containers=[init_container],\n", - ")\n", - "profiles.append(eoepca_demo_init_script_profile)\n", - "profiles" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### JupyterLab\n", - "\n", - "In the cell below, the user adds a [JupyterLab](https://github.com/jupyter/docker-stacks) profile to the stack of profiles. The [workspace volume](#workspace-volume) is mounted to persist user files, and necessary environment variables are set to ensure a smooth user experience." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "image = \"jupyter/scipy-notebook\"\n", - "\n", - "\n", - "eoepca_jupyter_lab_profile = Profile( # type: ignore\n", - " id=\"profile_jupyter_lab\",\n", - " groups=[\"group-c\"],\n", - " definition=ProfileDefinition( # type: ignore\n", - " display_name=\"Jupyter Lab\",\n", - " description=\"Jupyter Lab with Python 3.11\",\n", - " slug=\"eoepca_jupyter_lab\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride( # type: ignore\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[workspace_volume],\n", - " config_maps=[],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", - " },\n", - ")\n", - "\n", - "profiles.append(eoepca_jupyter_lab_profile)\n", - "profiles" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### JupyterLab Plus \n", - "JupyterLab Plus is an extended version of the JupyterLab profile with additional features and capabilities. The Docker image for this profile is hosted on a private Docker registry, requiring the user to configure a pull secret to authenticate and successfully pull the JupyterLab Plus image on the remote cluster." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_jupyter_lab_2', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', description='Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret', slug='eoepca_jupyter_lab_2', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "image_pull_secret = ImagePullSecret( # type: ignore\n", - " name=\"cr-config\",\n", - " persist=False,\n", - " data=\"ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9\",\n", - ")\n", - "image = \"cr.terradue.com/eoepca-plus/scipy-notebook@sha256:f339a9fa98d3d0c1fa8d7cc850e7f5a46845781f49bee86aacba059669d02d54\"\n", - "image = \"eoepca/iat-jupyterlab:develop\"\n", - "\n", - "eoepca_jupyter_lab_profile_2 = Profile( # type: ignore\n", - " id=\"profile_jupyter_lab_2\",\n", - " groups=[\"group-c\"],\n", - " definition=ProfileDefinition( # type: ignore\n", - " display_name=\"Jupyter Lab - profile 2\",\n", - " description=\"Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret\",\n", - " slug=\"eoepca_jupyter_lab_2\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride( # type: ignore\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[workspace_volume],\n", - " config_maps=[],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_CONFIG_HOME\": \"/workspace/.config\",\n", - " },\n", - " image_pull_secrets=[image_pull_secret],\n", - ")\n", - "\n", - "profiles.append(eoepca_jupyter_lab_profile_2)\n", - "profiles" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### E-Learning \n", - "This profile is an extended version of code-server, configured to initialize with an init container using [init.sh](../config-maps/init.sh). It also mounts several ConfigMaps, including [bash-rc](#bash-rc), [bash-login](#bash-login), and [stage-in/out](#stage-inout-configmap). Additionally, key Kubernetes objects such as Role, RoleBinding, and Service are defined using a Kubernetes [manifest](../manifests/manifest.yaml). This manifest ultimately exposes **LocalStack** as a Service, allowing users to emulate AWS locally." - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_jupyter_lab_2', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', description='Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret', slug='eoepca_jupyter_lab_2', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_studio_coder_stac', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Understanding STAC for input/output data modelling', description='Understand the role of STAC in input/output data manifests in EO data processing workflows', slug='eoepca_coder_slug_stac', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='set -x \\n\\ncd /workspace\\n\\ngit clone \\'https://github.com/eoap/stac-eoap.git\\'\\n\\ncode-server --install-extension ms-python.python \\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nmkdir -p /workspace/User/\\n\\necho \\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\' > /workspace/User/settings.json\\n\\npython -m venv /workspace/.venv\\nsource /workspace/.venv/bin/activate\\n/workspace/.venv/bin/python -m pip install --no-cache-dir stactools rasterio requests stac-asset click-logging tabulate tqdm pystac-client ipykernel loguru scikit-image rio_stac boto3==1.35.23\\n\\n/workspace/.venv/bin/python -m pip install --index-url https://test.pypi.org/simple cwl-wrapper\\n\\n/workspace/.venv/bin/python -m ipykernel install --user --name stac_env --display-name \"Python (STAC)\"\\n\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\nexport AWS_ACCESS_KEY_ID=\"test\"\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\naws s3 mb s3://results --endpoint-url=http://localstack:4566\\n\\nexit 0', persist=False), ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.local', 'XDG_DATA_HOME': '/workspace/.local/share/', 'CWLTOOL_OPTIONS': '--podman', 'CODE_SERVER_WS': '/workspace/stac-eoap', 'AWS_DEFAULT_REGION': 'us-east-1', 'AWS_ACCESS_KEY_ID': 'test', 'AWS_SECRET_ACCESS_KEY': 'test'}, default_url=None, node_selector={}, role_bindings=[], image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=[Manifest(name='manifests', key='manifests', content=[{'apiVersion': 'v1', 'kind': 'ServiceAccount', 'metadata': {'name': 'localstack'}}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'Role', 'metadata': {'name': 'localstack'}, 'rules': [{'apiGroups': [''], 'resources': ['pods'], 'verbs': ['*']}, {'apiGroups': [''], 'resources': ['pods/log'], 'verbs': ['get']}, {'apiGroups': [''], 'resources': ['pods/exec'], 'verbs': ['get', 'create']}, {'apiGroups': [''], 'resources': ['services'], 'verbs': ['get', 'list']}]}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'RoleBinding', 'metadata': {'name': 'localstack'}, 'subjects': [{'kind': 'ServiceAccount', 'name': 'localstack'}], 'roleRef': {'kind': 'Role', 'name': 'localstack', 'apiGroup': 'rbac.authorization.k8s.io'}}, {'apiVersion': 'v1', 'kind': 'Service', 'metadata': {'name': 'localstack'}, 'spec': {'type': 'ClusterIP', 'ports': [{'name': 'edge', 'port': 4566, 'targetPort': 4566}, {'name': 'external-service-port-4510', 'port': 4510, 'targetPort': 'ext-svc-4510'}, {'name': 'external-service-port-4511', 'port': 4511, 'targetPort': 'ext-svc-4511'}, {'name': 'external-service-port-4512', 'port': 4512, 'targetPort': 'ext-svc-4512'}, {'name': 'external-service-port-4513', 'port': 4513, 'targetPort': 'ext-svc-4513'}, {'name': 'external-service-port-4514', 'port': 4514, 'targetPort': 'ext-svc-4514'}, {'name': 'external-service-port-4515', 'port': 4515, 'targetPort': 'ext-svc-4515'}, {'name': 'external-service-port-4516', 'port': 4516, 'targetPort': 'ext-svc-4516'}, {'name': 'external-service-port-4517', 'port': 4517, 'targetPort': 'ext-svc-4517'}, {'name': 'external-service-port-4518', 'port': 4518, 'targetPort': 'ext-svc-4518'}, {'name': 'external-service-port-4519', 'port': 4519, 'targetPort': 'ext-svc-4519'}, {'name': 'external-service-port-4520', 'port': 4520, 'targetPort': 'ext-svc-4520'}, {'name': 'external-service-port-4521', 'port': 4521, 'targetPort': 'ext-svc-4521'}, {'name': 'external-service-port-4522', 'port': 4522, 'targetPort': 'ext-svc-4522'}, {'name': 'external-service-port-4523', 'port': 4523, 'targetPort': 'ext-svc-4523'}, {'name': 'external-service-port-4524', 'port': 4524, 'targetPort': 'ext-svc-4524'}, {'name': 'external-service-port-4525', 'port': 4525, 'targetPort': 'ext-svc-4525'}, {'name': 'external-service-port-4526', 'port': 4526, 'targetPort': 'ext-svc-4526'}, {'name': 'external-service-port-4527', 'port': 4527, 'targetPort': 'ext-svc-4527'}, {'name': 'external-service-port-4528', 'port': 4528, 'targetPort': 'ext-svc-4528'}, {'name': 'external-service-port-4529', 'port': 4529, 'targetPort': 'ext-svc-4529'}, {'name': 'external-service-port-4530', 'port': 4530, 'targetPort': 'ext-svc-4530'}, {'name': 'external-service-port-4531', 'port': 4531, 'targetPort': 'ext-svc-4531'}, {'name': 'external-service-port-4532', 'port': 4532, 'targetPort': 'ext-svc-4532'}, {'name': 'external-service-port-4533', 'port': 4533, 'targetPort': 'ext-svc-4533'}, {'name': 'external-service-port-4534', 'port': 4534, 'targetPort': 'ext-svc-4534'}, {'name': 'external-service-port-4535', 'port': 4535, 'targetPort': 'ext-svc-4535'}, {'name': 'external-service-port-4536', 'port': 4536, 'targetPort': 'ext-svc-4536'}, {'name': 'external-service-port-4537', 'port': 4537, 'targetPort': 'ext-svc-4537'}, {'name': 'external-service-port-4538', 'port': 4538, 'targetPort': 'ext-svc-4538'}, {'name': 'external-service-port-4539', 'port': 4539, 'targetPort': 'ext-svc-4539'}, {'name': 'external-service-port-4540', 'port': 4540, 'targetPort': 'ext-svc-4540'}, {'name': 'external-service-port-4541', 'port': 4541, 'targetPort': 'ext-svc-4541'}, {'name': 'external-service-port-4542', 'port': 4542, 'targetPort': 'ext-svc-4542'}, {'name': 'external-service-port-4543', 'port': 4543, 'targetPort': 'ext-svc-4543'}, {'name': 'external-service-port-4544', 'port': 4544, 'targetPort': 'ext-svc-4544'}, {'name': 'external-service-port-4545', 'port': 4545, 'targetPort': 'ext-svc-4545'}, {'name': 'external-service-port-4546', 'port': 4546, 'targetPort': 'ext-svc-4546'}, {'name': 'external-service-port-4547', 'port': 4547, 'targetPort': 'ext-svc-4547'}, {'name': 'external-service-port-4548', 'port': 4548, 'targetPort': 'ext-svc-4548'}, {'name': 'external-service-port-4549', 'port': 4549, 'targetPort': 'ext-svc-4549'}, {'name': 'external-service-port-4550', 'port': 4550, 'targetPort': 'ext-svc-4550'}, {'name': 'external-service-port-4551', 'port': 4551, 'targetPort': 'ext-svc-4551'}, {'name': 'external-service-port-4552', 'port': 4552, 'targetPort': 'ext-svc-4552'}, {'name': 'external-service-port-4553', 'port': 4553, 'targetPort': 'ext-svc-4553'}, {'name': 'external-service-port-4554', 'port': 4554, 'targetPort': 'ext-svc-4554'}, {'name': 'external-service-port-4555', 'port': 4555, 'targetPort': 'ext-svc-4555'}, {'name': 'external-service-port-4556', 'port': 4556, 'targetPort': 'ext-svc-4556'}, {'name': 'external-service-port-4557', 'port': 4557, 'targetPort': 'ext-svc-4557'}, {'name': 'external-service-port-4558', 'port': 4558, 'targetPort': 'ext-svc-4558'}, {'name': 'external-service-port-4559', 'port': 4559, 'targetPort': 'ext-svc-4559'}], 'selector': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}}, {'apiVersion': 'apps/v1', 'kind': 'Deployment', 'metadata': {'name': 'localstack'}, 'spec': {'replicas': 1, 'strategy': {'type': 'RollingUpdate'}, 'selector': {'matchLabels': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'template': {'metadata': {'labels': {'app': 'localstack-{{ spawner.user.name }}', 'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'spec': {'serviceAccountName': 'localstack', 'securityContext': {}, 'containers': [{'name': 'localstack', 'securityContext': {}, 'image': 'localstack/localstack:latest', 'imagePullPolicy': 'IfNotPresent', 'ports': [{'name': 'edge', 'containerPort': 4566, 'protocol': 'TCP'}, {'name': 'ext-svc-4510', 'containerPort': 4510, 'protocol': 'TCP'}, {'name': 'ext-svc-4511', 'containerPort': 4511, 'protocol': 'TCP'}, {'name': 'ext-svc-4512', 'containerPort': 4512, 'protocol': 'TCP'}, {'name': 'ext-svc-4513', 'containerPort': 4513, 'protocol': 'TCP'}, {'name': 'ext-svc-4514', 'containerPort': 4514, 'protocol': 'TCP'}, {'name': 'ext-svc-4515', 'containerPort': 4515, 'protocol': 'TCP'}, {'name': 'ext-svc-4516', 'containerPort': 4516, 'protocol': 'TCP'}, {'name': 'ext-svc-4517', 'containerPort': 4517, 'protocol': 'TCP'}, {'name': 'ext-svc-4518', 'containerPort': 4518, 'protocol': 'TCP'}, {'name': 'ext-svc-4519', 'containerPort': 4519, 'protocol': 'TCP'}, {'name': 'ext-svc-4520', 'containerPort': 4520, 'protocol': 'TCP'}, {'name': 'ext-svc-4521', 'containerPort': 4521, 'protocol': 'TCP'}, {'name': 'ext-svc-4522', 'containerPort': 4522, 'protocol': 'TCP'}, {'name': 'ext-svc-4523', 'containerPort': 4523, 'protocol': 'TCP'}, {'name': 'ext-svc-4524', 'containerPort': 4524, 'protocol': 'TCP'}, {'name': 'ext-svc-4525', 'containerPort': 4525, 'protocol': 'TCP'}, {'name': 'ext-svc-4526', 'containerPort': 4526, 'protocol': 'TCP'}, {'name': 'ext-svc-4527', 'containerPort': 4527, 'protocol': 'TCP'}, {'name': 'ext-svc-4528', 'containerPort': 4528, 'protocol': 'TCP'}, {'name': 'ext-svc-4529', 'containerPort': 4529, 'protocol': 'TCP'}, {'name': 'ext-svc-4530', 'containerPort': 4530, 'protocol': 'TCP'}, {'name': 'ext-svc-4531', 'containerPort': 4531, 'protocol': 'TCP'}, {'name': 'ext-svc-4532', 'containerPort': 4532, 'protocol': 'TCP'}, {'name': 'ext-svc-4533', 'containerPort': 4533, 'protocol': 'TCP'}, {'name': 'ext-svc-4534', 'containerPort': 4534, 'protocol': 'TCP'}, {'name': 'ext-svc-4535', 'containerPort': 4535, 'protocol': 'TCP'}, {'name': 'ext-svc-4536', 'containerPort': 4536, 'protocol': 'TCP'}, {'name': 'ext-svc-4537', 'containerPort': 4537, 'protocol': 'TCP'}, {'name': 'ext-svc-4538', 'containerPort': 4538, 'protocol': 'TCP'}, {'name': 'ext-svc-4539', 'containerPort': 4539, 'protocol': 'TCP'}, {'name': 'ext-svc-4540', 'containerPort': 4540, 'protocol': 'TCP'}, {'name': 'ext-svc-4541', 'containerPort': 4541, 'protocol': 'TCP'}, {'name': 'ext-svc-4542', 'containerPort': 4542, 'protocol': 'TCP'}, {'name': 'ext-svc-4543', 'containerPort': 4543, 'protocol': 'TCP'}, {'name': 'ext-svc-4544', 'containerPort': 4544, 'protocol': 'TCP'}, {'name': 'ext-svc-4545', 'containerPort': 4545, 'protocol': 'TCP'}, {'name': 'ext-svc-4546', 'containerPort': 4546, 'protocol': 'TCP'}, {'name': 'ext-svc-4547', 'containerPort': 4547, 'protocol': 'TCP'}, {'name': 'ext-svc-4548', 'containerPort': 4548, 'protocol': 'TCP'}, {'name': 'ext-svc-4549', 'containerPort': 4549, 'protocol': 'TCP'}, {'name': 'ext-svc-4550', 'containerPort': 4550, 'protocol': 'TCP'}, {'name': 'ext-svc-4551', 'containerPort': 4551, 'protocol': 'TCP'}, {'name': 'ext-svc-4552', 'containerPort': 4552, 'protocol': 'TCP'}, {'name': 'ext-svc-4553', 'containerPort': 4553, 'protocol': 'TCP'}, {'name': 'ext-svc-4554', 'containerPort': 4554, 'protocol': 'TCP'}, {'name': 'ext-svc-4555', 'containerPort': 4555, 'protocol': 'TCP'}, {'name': 'ext-svc-4556', 'containerPort': 4556, 'protocol': 'TCP'}, {'name': 'ext-svc-4557', 'containerPort': 4557, 'protocol': 'TCP'}, {'name': 'ext-svc-4558', 'containerPort': 4558, 'protocol': 'TCP'}, {'name': 'ext-svc-4559', 'containerPort': 4559, 'protocol': 'TCP'}], 'livenessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'readinessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'resources': {}, 'env': [{'name': 'DEBUG', 'value': '0'}, {'name': 'EXTERNAL_SERVICE_PORTS_START', 'value': '4510'}, {'name': 'EXTERNAL_SERVICE_PORTS_END', 'value': '4560'}, {'name': 'LOCALSTACK_K8S_SERVICE_NAME', 'value': 'localstack'}, {'name': 'LOCALSTACK_K8S_NAMESPACE', 'valueFrom': {'fieldRef': {'fieldPath': 'metadata.namespace'}}}, {'name': 'LAMBDA_RUNTIME_EXECUTOR', 'value': 'docker'}, {'name': 'LAMBDA_K8S_IMAGE_PREFIX', 'value': 'localstack/lambda-'}, {'name': 'LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT', 'value': '60'}, {'name': 'OVERRIDE_IN_DOCKER', 'value': '1'}]}], 'volumes': []}}}}, {'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': 'my-config'}, 'data': {'ENV_VAR1': 'value1', 'ENV_VAR2': 'value2'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'my-secret'}, 'type': 'Opaque', 'data': {'SECRET_KEY1': 'dmFsdWUx', 'SECRET_KEY2': 'dmFsdWUy'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'aws-credentials-{{ spawner.user.name }}'}, 'type': 'Opaque', 'data': {'credentials': 'W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg='}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'data-by-name', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'data-by-name', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': 'secret-value', 'remoteRef': {'key': 'secret-one', 'property': 'the-key'}}]}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'eoepca-plus-secret-ro', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'eoepca-plus-secret-ro', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': '.dockerconfigjson', 'remoteRef': {'key': 'eoepca-plus-secret-ro', 'property': '.dockerconfigjson'}}]}}], persist=False)], env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "localstack_manifest_path = os.path.join(parent_dir, \"manifests/manifest.yaml\") \n", - "with open(localstack_manifest_path, \"r\") as f:\n", - " content = yaml.safe_load_all(f.read())\n", - "\n", - "\n", - "localstack_manifest = Manifest( # type: ignore\n", - " name=\"manifests\",\n", - " key=\"manifests\",\n", - " readonly=True,\n", - " persist=False,\n", - " content=[e for e in content],\n", - ")\n", - "\n", - "image = \"docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40\"\n", - "\n", - "coder_profile_stac = Profile( # type: ignore\n", - " id=f\"profile_studio_coder_stac\",\n", - " groups=[\"group-a\", \"group-b\"], \n", - " definition=ProfileDefinition( # type: ignore\n", - " display_name=\"Understanding STAC for input/output data modelling\",\n", - " description=\"Understand the role of STAC in input/output data manifests in EO data processing workflows\",\n", - " slug=\"eoepca_coder_slug_stac\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride( # type: ignore\n", - " cpu_guarantee=1,\n", - " cpu_limit=2,\n", - " mem_guarantee=\"4G\",\n", - " mem_limit=\"6G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[workspace_volume],\n", - " config_maps=[init_stac_cm, bash_rc_cm, bash_login_cm],\n", - " pod_env_vars={\n", - " \"HOME\": \"/workspace\",\n", - " \"CONDA_ENVS_PATH\": \"/workspace/.envs\",\n", - " \"CONDARC\": \"/workspace/.condarc\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_RUNTIME_DIR\": \"/workspace/.local\",\n", - " \"XDG_CONFIG_HOME\": \"/workspace/.local\",\n", - " \"XDG_DATA_HOME\": \"/workspace/.local/share/\",\n", - " \"CWLTOOL_OPTIONS\": \"--podman\",\n", - " \"CODE_SERVER_WS\": \"/workspace/stac-eoap\",\n", - " \"AWS_DEFAULT_REGION\": \"us-east-1\",\n", - " \"AWS_ACCESS_KEY_ID\": \"test\",\n", - " \"AWS_SECRET_ACCESS_KEY\": \"test\",\n", - " },\n", - " role_bindings=[],\n", - " init_containers=[init_container],\n", - " image_pull_secrets=[image_pull_secret],\n", - " manifests=[localstack_manifest],\n", - ")\n", - "\n", - "profiles.append(coder_profile_stac)\n", - "profiles" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### QGIS \n", - "This profile is configured to deploy the [QGIS](https://github.com/qgis/QGIS) application on the **App Hub**, enabling users to visualize and analyze Earth observation data directly within the platform. The deployment ensures that QGIS is configured with the necessary resources, storage, and dependencies to support geospatial analysis, map rendering, and data processing. By integrating QGIS into the App Hub, users can seamlessly interact with satellite imagery, vector layers, and raster data for advanced geospatial analysis." - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_jupyter_lab_2', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', description='Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret', slug='eoepca_jupyter_lab_2', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_studio_coder_stac', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Understanding STAC for input/output data modelling', description='Understand the role of STAC in input/output data manifests in EO data processing workflows', slug='eoepca_coder_slug_stac', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='set -x \\n\\ncd /workspace\\n\\ngit clone \\'https://github.com/eoap/stac-eoap.git\\'\\n\\ncode-server --install-extension ms-python.python \\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nmkdir -p /workspace/User/\\n\\necho \\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\' > /workspace/User/settings.json\\n\\npython -m venv /workspace/.venv\\nsource /workspace/.venv/bin/activate\\n/workspace/.venv/bin/python -m pip install --no-cache-dir stactools rasterio requests stac-asset click-logging tabulate tqdm pystac-client ipykernel loguru scikit-image rio_stac boto3==1.35.23\\n\\n/workspace/.venv/bin/python -m pip install --index-url https://test.pypi.org/simple cwl-wrapper\\n\\n/workspace/.venv/bin/python -m ipykernel install --user --name stac_env --display-name \"Python (STAC)\"\\n\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\nexport AWS_ACCESS_KEY_ID=\"test\"\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\naws s3 mb s3://results --endpoint-url=http://localstack:4566\\n\\nexit 0', persist=False), ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.local', 'XDG_DATA_HOME': '/workspace/.local/share/', 'CWLTOOL_OPTIONS': '--podman', 'CODE_SERVER_WS': '/workspace/stac-eoap', 'AWS_DEFAULT_REGION': 'us-east-1', 'AWS_ACCESS_KEY_ID': 'test', 'AWS_SECRET_ACCESS_KEY': 'test'}, default_url=None, node_selector={}, role_bindings=[], image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=[Manifest(name='manifests', key='manifests', content=[{'apiVersion': 'v1', 'kind': 'ServiceAccount', 'metadata': {'name': 'localstack'}}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'Role', 'metadata': {'name': 'localstack'}, 'rules': [{'apiGroups': [''], 'resources': ['pods'], 'verbs': ['*']}, {'apiGroups': [''], 'resources': ['pods/log'], 'verbs': ['get']}, {'apiGroups': [''], 'resources': ['pods/exec'], 'verbs': ['get', 'create']}, {'apiGroups': [''], 'resources': ['services'], 'verbs': ['get', 'list']}]}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'RoleBinding', 'metadata': {'name': 'localstack'}, 'subjects': [{'kind': 'ServiceAccount', 'name': 'localstack'}], 'roleRef': {'kind': 'Role', 'name': 'localstack', 'apiGroup': 'rbac.authorization.k8s.io'}}, {'apiVersion': 'v1', 'kind': 'Service', 'metadata': {'name': 'localstack'}, 'spec': {'type': 'ClusterIP', 'ports': [{'name': 'edge', 'port': 4566, 'targetPort': 4566}, {'name': 'external-service-port-4510', 'port': 4510, 'targetPort': 'ext-svc-4510'}, {'name': 'external-service-port-4511', 'port': 4511, 'targetPort': 'ext-svc-4511'}, {'name': 'external-service-port-4512', 'port': 4512, 'targetPort': 'ext-svc-4512'}, {'name': 'external-service-port-4513', 'port': 4513, 'targetPort': 'ext-svc-4513'}, {'name': 'external-service-port-4514', 'port': 4514, 'targetPort': 'ext-svc-4514'}, {'name': 'external-service-port-4515', 'port': 4515, 'targetPort': 'ext-svc-4515'}, {'name': 'external-service-port-4516', 'port': 4516, 'targetPort': 'ext-svc-4516'}, {'name': 'external-service-port-4517', 'port': 4517, 'targetPort': 'ext-svc-4517'}, {'name': 'external-service-port-4518', 'port': 4518, 'targetPort': 'ext-svc-4518'}, {'name': 'external-service-port-4519', 'port': 4519, 'targetPort': 'ext-svc-4519'}, {'name': 'external-service-port-4520', 'port': 4520, 'targetPort': 'ext-svc-4520'}, {'name': 'external-service-port-4521', 'port': 4521, 'targetPort': 'ext-svc-4521'}, {'name': 'external-service-port-4522', 'port': 4522, 'targetPort': 'ext-svc-4522'}, {'name': 'external-service-port-4523', 'port': 4523, 'targetPort': 'ext-svc-4523'}, {'name': 'external-service-port-4524', 'port': 4524, 'targetPort': 'ext-svc-4524'}, {'name': 'external-service-port-4525', 'port': 4525, 'targetPort': 'ext-svc-4525'}, {'name': 'external-service-port-4526', 'port': 4526, 'targetPort': 'ext-svc-4526'}, {'name': 'external-service-port-4527', 'port': 4527, 'targetPort': 'ext-svc-4527'}, {'name': 'external-service-port-4528', 'port': 4528, 'targetPort': 'ext-svc-4528'}, {'name': 'external-service-port-4529', 'port': 4529, 'targetPort': 'ext-svc-4529'}, {'name': 'external-service-port-4530', 'port': 4530, 'targetPort': 'ext-svc-4530'}, {'name': 'external-service-port-4531', 'port': 4531, 'targetPort': 'ext-svc-4531'}, {'name': 'external-service-port-4532', 'port': 4532, 'targetPort': 'ext-svc-4532'}, {'name': 'external-service-port-4533', 'port': 4533, 'targetPort': 'ext-svc-4533'}, {'name': 'external-service-port-4534', 'port': 4534, 'targetPort': 'ext-svc-4534'}, {'name': 'external-service-port-4535', 'port': 4535, 'targetPort': 'ext-svc-4535'}, {'name': 'external-service-port-4536', 'port': 4536, 'targetPort': 'ext-svc-4536'}, {'name': 'external-service-port-4537', 'port': 4537, 'targetPort': 'ext-svc-4537'}, {'name': 'external-service-port-4538', 'port': 4538, 'targetPort': 'ext-svc-4538'}, {'name': 'external-service-port-4539', 'port': 4539, 'targetPort': 'ext-svc-4539'}, {'name': 'external-service-port-4540', 'port': 4540, 'targetPort': 'ext-svc-4540'}, {'name': 'external-service-port-4541', 'port': 4541, 'targetPort': 'ext-svc-4541'}, {'name': 'external-service-port-4542', 'port': 4542, 'targetPort': 'ext-svc-4542'}, {'name': 'external-service-port-4543', 'port': 4543, 'targetPort': 'ext-svc-4543'}, {'name': 'external-service-port-4544', 'port': 4544, 'targetPort': 'ext-svc-4544'}, {'name': 'external-service-port-4545', 'port': 4545, 'targetPort': 'ext-svc-4545'}, {'name': 'external-service-port-4546', 'port': 4546, 'targetPort': 'ext-svc-4546'}, {'name': 'external-service-port-4547', 'port': 4547, 'targetPort': 'ext-svc-4547'}, {'name': 'external-service-port-4548', 'port': 4548, 'targetPort': 'ext-svc-4548'}, {'name': 'external-service-port-4549', 'port': 4549, 'targetPort': 'ext-svc-4549'}, {'name': 'external-service-port-4550', 'port': 4550, 'targetPort': 'ext-svc-4550'}, {'name': 'external-service-port-4551', 'port': 4551, 'targetPort': 'ext-svc-4551'}, {'name': 'external-service-port-4552', 'port': 4552, 'targetPort': 'ext-svc-4552'}, {'name': 'external-service-port-4553', 'port': 4553, 'targetPort': 'ext-svc-4553'}, {'name': 'external-service-port-4554', 'port': 4554, 'targetPort': 'ext-svc-4554'}, {'name': 'external-service-port-4555', 'port': 4555, 'targetPort': 'ext-svc-4555'}, {'name': 'external-service-port-4556', 'port': 4556, 'targetPort': 'ext-svc-4556'}, {'name': 'external-service-port-4557', 'port': 4557, 'targetPort': 'ext-svc-4557'}, {'name': 'external-service-port-4558', 'port': 4558, 'targetPort': 'ext-svc-4558'}, {'name': 'external-service-port-4559', 'port': 4559, 'targetPort': 'ext-svc-4559'}], 'selector': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}}, {'apiVersion': 'apps/v1', 'kind': 'Deployment', 'metadata': {'name': 'localstack'}, 'spec': {'replicas': 1, 'strategy': {'type': 'RollingUpdate'}, 'selector': {'matchLabels': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'template': {'metadata': {'labels': {'app': 'localstack-{{ spawner.user.name }}', 'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'spec': {'serviceAccountName': 'localstack', 'securityContext': {}, 'containers': [{'name': 'localstack', 'securityContext': {}, 'image': 'localstack/localstack:latest', 'imagePullPolicy': 'IfNotPresent', 'ports': [{'name': 'edge', 'containerPort': 4566, 'protocol': 'TCP'}, {'name': 'ext-svc-4510', 'containerPort': 4510, 'protocol': 'TCP'}, {'name': 'ext-svc-4511', 'containerPort': 4511, 'protocol': 'TCP'}, {'name': 'ext-svc-4512', 'containerPort': 4512, 'protocol': 'TCP'}, {'name': 'ext-svc-4513', 'containerPort': 4513, 'protocol': 'TCP'}, {'name': 'ext-svc-4514', 'containerPort': 4514, 'protocol': 'TCP'}, {'name': 'ext-svc-4515', 'containerPort': 4515, 'protocol': 'TCP'}, {'name': 'ext-svc-4516', 'containerPort': 4516, 'protocol': 'TCP'}, {'name': 'ext-svc-4517', 'containerPort': 4517, 'protocol': 'TCP'}, {'name': 'ext-svc-4518', 'containerPort': 4518, 'protocol': 'TCP'}, {'name': 'ext-svc-4519', 'containerPort': 4519, 'protocol': 'TCP'}, {'name': 'ext-svc-4520', 'containerPort': 4520, 'protocol': 'TCP'}, {'name': 'ext-svc-4521', 'containerPort': 4521, 'protocol': 'TCP'}, {'name': 'ext-svc-4522', 'containerPort': 4522, 'protocol': 'TCP'}, {'name': 'ext-svc-4523', 'containerPort': 4523, 'protocol': 'TCP'}, {'name': 'ext-svc-4524', 'containerPort': 4524, 'protocol': 'TCP'}, {'name': 'ext-svc-4525', 'containerPort': 4525, 'protocol': 'TCP'}, {'name': 'ext-svc-4526', 'containerPort': 4526, 'protocol': 'TCP'}, {'name': 'ext-svc-4527', 'containerPort': 4527, 'protocol': 'TCP'}, {'name': 'ext-svc-4528', 'containerPort': 4528, 'protocol': 'TCP'}, {'name': 'ext-svc-4529', 'containerPort': 4529, 'protocol': 'TCP'}, {'name': 'ext-svc-4530', 'containerPort': 4530, 'protocol': 'TCP'}, {'name': 'ext-svc-4531', 'containerPort': 4531, 'protocol': 'TCP'}, {'name': 'ext-svc-4532', 'containerPort': 4532, 'protocol': 'TCP'}, {'name': 'ext-svc-4533', 'containerPort': 4533, 'protocol': 'TCP'}, {'name': 'ext-svc-4534', 'containerPort': 4534, 'protocol': 'TCP'}, {'name': 'ext-svc-4535', 'containerPort': 4535, 'protocol': 'TCP'}, {'name': 'ext-svc-4536', 'containerPort': 4536, 'protocol': 'TCP'}, {'name': 'ext-svc-4537', 'containerPort': 4537, 'protocol': 'TCP'}, {'name': 'ext-svc-4538', 'containerPort': 4538, 'protocol': 'TCP'}, {'name': 'ext-svc-4539', 'containerPort': 4539, 'protocol': 'TCP'}, {'name': 'ext-svc-4540', 'containerPort': 4540, 'protocol': 'TCP'}, {'name': 'ext-svc-4541', 'containerPort': 4541, 'protocol': 'TCP'}, {'name': 'ext-svc-4542', 'containerPort': 4542, 'protocol': 'TCP'}, {'name': 'ext-svc-4543', 'containerPort': 4543, 'protocol': 'TCP'}, {'name': 'ext-svc-4544', 'containerPort': 4544, 'protocol': 'TCP'}, {'name': 'ext-svc-4545', 'containerPort': 4545, 'protocol': 'TCP'}, {'name': 'ext-svc-4546', 'containerPort': 4546, 'protocol': 'TCP'}, {'name': 'ext-svc-4547', 'containerPort': 4547, 'protocol': 'TCP'}, {'name': 'ext-svc-4548', 'containerPort': 4548, 'protocol': 'TCP'}, {'name': 'ext-svc-4549', 'containerPort': 4549, 'protocol': 'TCP'}, {'name': 'ext-svc-4550', 'containerPort': 4550, 'protocol': 'TCP'}, {'name': 'ext-svc-4551', 'containerPort': 4551, 'protocol': 'TCP'}, {'name': 'ext-svc-4552', 'containerPort': 4552, 'protocol': 'TCP'}, {'name': 'ext-svc-4553', 'containerPort': 4553, 'protocol': 'TCP'}, {'name': 'ext-svc-4554', 'containerPort': 4554, 'protocol': 'TCP'}, {'name': 'ext-svc-4555', 'containerPort': 4555, 'protocol': 'TCP'}, {'name': 'ext-svc-4556', 'containerPort': 4556, 'protocol': 'TCP'}, {'name': 'ext-svc-4557', 'containerPort': 4557, 'protocol': 'TCP'}, {'name': 'ext-svc-4558', 'containerPort': 4558, 'protocol': 'TCP'}, {'name': 'ext-svc-4559', 'containerPort': 4559, 'protocol': 'TCP'}], 'livenessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'readinessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'resources': {}, 'env': [{'name': 'DEBUG', 'value': '0'}, {'name': 'EXTERNAL_SERVICE_PORTS_START', 'value': '4510'}, {'name': 'EXTERNAL_SERVICE_PORTS_END', 'value': '4560'}, {'name': 'LOCALSTACK_K8S_SERVICE_NAME', 'value': 'localstack'}, {'name': 'LOCALSTACK_K8S_NAMESPACE', 'valueFrom': {'fieldRef': {'fieldPath': 'metadata.namespace'}}}, {'name': 'LAMBDA_RUNTIME_EXECUTOR', 'value': 'docker'}, {'name': 'LAMBDA_K8S_IMAGE_PREFIX', 'value': 'localstack/lambda-'}, {'name': 'LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT', 'value': '60'}, {'name': 'OVERRIDE_IN_DOCKER', 'value': '1'}]}], 'volumes': []}}}}, {'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': 'my-config'}, 'data': {'ENV_VAR1': 'value1', 'ENV_VAR2': 'value2'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'my-secret'}, 'type': 'Opaque', 'data': {'SECRET_KEY1': 'dmFsdWUx', 'SECRET_KEY2': 'dmFsdWUy'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'aws-credentials-{{ spawner.user.name }}'}, 'type': 'Opaque', 'data': {'credentials': 'W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg='}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'data-by-name', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'data-by-name', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': 'secret-value', 'remoteRef': {'key': 'secret-one', 'property': 'the-key'}}]}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'eoepca-plus-secret-ro', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'eoepca-plus-secret-ro', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': '.dockerconfigjson', 'remoteRef': {'key': 'eoepca-plus-secret-ro', 'property': '.dockerconfigjson'}}]}}], persist=False)], env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_studio_desktop_qgis', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='QGIS on a Remote Desktop', description='Spatial visualization and decision-making tools for everyone', slug='eoepca_desktop_qgis', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='2G', mem_guarantee=None, image='eoepca/iga-remote-desktop-qgis:1.1.3', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace'}, default_url='desktop', node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "image = \"eoepca/iga-remote-desktop-qgis:1.1.3\"\n", - "\n", - "qgis_profile = Profile( # type: ignore\n", - " id=\"profile_studio_desktop_qgis\",\n", - " groups=[\"group-a\", \"group-b\"],\n", - " definition=ProfileDefinition( # type: ignore\n", - " display_name=\"QGIS on a Remote Desktop\",\n", - " description=\"Spatial visualization and decision-making tools for everyone\",\n", - " slug=\"eoepca_desktop_qgis\",\n", - " default=False,\n", - " kubespawner_override=KubespawnerOverride( # type: ignore\n", - " cpu_limit=2,\n", - " mem_limit=\"2G\",\n", - " image=image,\n", - " ),\n", - " ),\n", - " node_selector={},\n", - " volumes=[workspace_volume],\n", - " config_maps=[bash_rc_cm, bash_login_cm],\n", - " pod_env_vars={\"HOME\": \"/workspace\"},\n", - " default_url=\"desktop\",\n", - " init_containers=[],\n", - ")\n", - "\n", - "profiles.append(qgis_profile)\n", - "profiles" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Write Configuration \n", - "In the code below, the user inspects all the configurations made in the previous section and then generates a [config.yaml](../../files/hub/config.yml) file. This file contains the complete configuration of the **App Hub**, including the different profiles defined for deployment. The `config.yaml` serves as the configuration blueprint, allowing the user to easily apply and share the profiles across different environments or clusters." - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[Profile(id='profile_studio_coder1', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Small', description=None, slug='ellip_studio_coder_slug_s', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Code Server Medium', description=None, slug='ellip_studio_coder_slug_m', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Coder demo init script', description='This profile is used to demonstrate the use of an init script', slug='eoepca_demo_init_script', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, content=\"set -x\\n\\ncd /workspace\\n\\ngit clone 'https://github.com/eoap/mastering-app-package.git'\\n\\ncode-server --install-extension ms-python.python\\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nexit 0\\n\", persist=False)], volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], volume_mount=VolumeMount(name='calrissian-volume', mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_jupyter_lab', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab', description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='jupyter/scipy-notebook', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_jupyter_lab_2', groups=['group-c'], definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', description='Jupyter Lab with Python 3.11 private image - demoes the use of an image pull secret', slug='eoepca_jupyter_lab_2', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, default_url=None, node_selector={}, role_bindings=None, image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_studio_coder_stac', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='Understanding STAC for input/output data modelling', description='Understand the role of STAC in input/output data manifests in EO data processing workflows', slug='eoepca_coder_slug_stac', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', image='docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='init', key='init', mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, content='set -x \\n\\ncd /workspace\\n\\ngit clone \\'https://github.com/eoap/stac-eoap.git\\'\\n\\ncode-server --install-extension ms-python.python \\ncode-server --install-extension redhat.vscode-yaml\\ncode-server --install-extension sbg-rabix.benten-cwl\\ncode-server --install-extension ms-toolsai.jupyter\\n\\nln -s /workspace/.local/share/code-server/extensions /workspace/extensions\\n\\nmkdir -p /workspace/User/\\n\\necho \\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\' > /workspace/User/settings.json\\n\\npython -m venv /workspace/.venv\\nsource /workspace/.venv/bin/activate\\n/workspace/.venv/bin/python -m pip install --no-cache-dir stactools rasterio requests stac-asset click-logging tabulate tqdm pystac-client ipykernel loguru scikit-image rio_stac boto3==1.35.23\\n\\n/workspace/.venv/bin/python -m pip install --index-url https://test.pypi.org/simple cwl-wrapper\\n\\n/workspace/.venv/bin/python -m ipykernel install --user --name stac_env --display-name \"Python (STAC)\"\\n\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\nexport AWS_ACCESS_KEY_ID=\"test\"\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\naws s3 mb s3://results --endpoint-url=http://localstack:4566\\n\\nexit 0', persist=False), ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': '/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': '/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.local', 'XDG_DATA_HOME': '/workspace/.local/share/', 'CWLTOOL_OPTIONS': '--podman', 'CODE_SERVER_WS': '/workspace/stac-eoap', 'AWS_DEFAULT_REGION': 'us-east-1', 'AWS_ACCESS_KEY_ID': 'test', 'AWS_SECRET_ACCESS_KEY': 'test'}, default_url=None, node_selector={}, role_bindings=[], image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], init_containers=[InitContainer(name='init-file-on-volume', image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh /opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', mount_path='/workspace'), InitContainerVolumeMount(name='init', mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=[Manifest(name='manifests', key='manifests', content=[{'apiVersion': 'v1', 'kind': 'ServiceAccount', 'metadata': {'name': 'localstack'}}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'Role', 'metadata': {'name': 'localstack'}, 'rules': [{'apiGroups': [''], 'resources': ['pods'], 'verbs': ['*']}, {'apiGroups': [''], 'resources': ['pods/log'], 'verbs': ['get']}, {'apiGroups': [''], 'resources': ['pods/exec'], 'verbs': ['get', 'create']}, {'apiGroups': [''], 'resources': ['services'], 'verbs': ['get', 'list']}]}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': 'RoleBinding', 'metadata': {'name': 'localstack'}, 'subjects': [{'kind': 'ServiceAccount', 'name': 'localstack'}], 'roleRef': {'kind': 'Role', 'name': 'localstack', 'apiGroup': 'rbac.authorization.k8s.io'}}, {'apiVersion': 'v1', 'kind': 'Service', 'metadata': {'name': 'localstack'}, 'spec': {'type': 'ClusterIP', 'ports': [{'name': 'edge', 'port': 4566, 'targetPort': 4566}, {'name': 'external-service-port-4510', 'port': 4510, 'targetPort': 'ext-svc-4510'}, {'name': 'external-service-port-4511', 'port': 4511, 'targetPort': 'ext-svc-4511'}, {'name': 'external-service-port-4512', 'port': 4512, 'targetPort': 'ext-svc-4512'}, {'name': 'external-service-port-4513', 'port': 4513, 'targetPort': 'ext-svc-4513'}, {'name': 'external-service-port-4514', 'port': 4514, 'targetPort': 'ext-svc-4514'}, {'name': 'external-service-port-4515', 'port': 4515, 'targetPort': 'ext-svc-4515'}, {'name': 'external-service-port-4516', 'port': 4516, 'targetPort': 'ext-svc-4516'}, {'name': 'external-service-port-4517', 'port': 4517, 'targetPort': 'ext-svc-4517'}, {'name': 'external-service-port-4518', 'port': 4518, 'targetPort': 'ext-svc-4518'}, {'name': 'external-service-port-4519', 'port': 4519, 'targetPort': 'ext-svc-4519'}, {'name': 'external-service-port-4520', 'port': 4520, 'targetPort': 'ext-svc-4520'}, {'name': 'external-service-port-4521', 'port': 4521, 'targetPort': 'ext-svc-4521'}, {'name': 'external-service-port-4522', 'port': 4522, 'targetPort': 'ext-svc-4522'}, {'name': 'external-service-port-4523', 'port': 4523, 'targetPort': 'ext-svc-4523'}, {'name': 'external-service-port-4524', 'port': 4524, 'targetPort': 'ext-svc-4524'}, {'name': 'external-service-port-4525', 'port': 4525, 'targetPort': 'ext-svc-4525'}, {'name': 'external-service-port-4526', 'port': 4526, 'targetPort': 'ext-svc-4526'}, {'name': 'external-service-port-4527', 'port': 4527, 'targetPort': 'ext-svc-4527'}, {'name': 'external-service-port-4528', 'port': 4528, 'targetPort': 'ext-svc-4528'}, {'name': 'external-service-port-4529', 'port': 4529, 'targetPort': 'ext-svc-4529'}, {'name': 'external-service-port-4530', 'port': 4530, 'targetPort': 'ext-svc-4530'}, {'name': 'external-service-port-4531', 'port': 4531, 'targetPort': 'ext-svc-4531'}, {'name': 'external-service-port-4532', 'port': 4532, 'targetPort': 'ext-svc-4532'}, {'name': 'external-service-port-4533', 'port': 4533, 'targetPort': 'ext-svc-4533'}, {'name': 'external-service-port-4534', 'port': 4534, 'targetPort': 'ext-svc-4534'}, {'name': 'external-service-port-4535', 'port': 4535, 'targetPort': 'ext-svc-4535'}, {'name': 'external-service-port-4536', 'port': 4536, 'targetPort': 'ext-svc-4536'}, {'name': 'external-service-port-4537', 'port': 4537, 'targetPort': 'ext-svc-4537'}, {'name': 'external-service-port-4538', 'port': 4538, 'targetPort': 'ext-svc-4538'}, {'name': 'external-service-port-4539', 'port': 4539, 'targetPort': 'ext-svc-4539'}, {'name': 'external-service-port-4540', 'port': 4540, 'targetPort': 'ext-svc-4540'}, {'name': 'external-service-port-4541', 'port': 4541, 'targetPort': 'ext-svc-4541'}, {'name': 'external-service-port-4542', 'port': 4542, 'targetPort': 'ext-svc-4542'}, {'name': 'external-service-port-4543', 'port': 4543, 'targetPort': 'ext-svc-4543'}, {'name': 'external-service-port-4544', 'port': 4544, 'targetPort': 'ext-svc-4544'}, {'name': 'external-service-port-4545', 'port': 4545, 'targetPort': 'ext-svc-4545'}, {'name': 'external-service-port-4546', 'port': 4546, 'targetPort': 'ext-svc-4546'}, {'name': 'external-service-port-4547', 'port': 4547, 'targetPort': 'ext-svc-4547'}, {'name': 'external-service-port-4548', 'port': 4548, 'targetPort': 'ext-svc-4548'}, {'name': 'external-service-port-4549', 'port': 4549, 'targetPort': 'ext-svc-4549'}, {'name': 'external-service-port-4550', 'port': 4550, 'targetPort': 'ext-svc-4550'}, {'name': 'external-service-port-4551', 'port': 4551, 'targetPort': 'ext-svc-4551'}, {'name': 'external-service-port-4552', 'port': 4552, 'targetPort': 'ext-svc-4552'}, {'name': 'external-service-port-4553', 'port': 4553, 'targetPort': 'ext-svc-4553'}, {'name': 'external-service-port-4554', 'port': 4554, 'targetPort': 'ext-svc-4554'}, {'name': 'external-service-port-4555', 'port': 4555, 'targetPort': 'ext-svc-4555'}, {'name': 'external-service-port-4556', 'port': 4556, 'targetPort': 'ext-svc-4556'}, {'name': 'external-service-port-4557', 'port': 4557, 'targetPort': 'ext-svc-4557'}, {'name': 'external-service-port-4558', 'port': 4558, 'targetPort': 'ext-svc-4558'}, {'name': 'external-service-port-4559', 'port': 4559, 'targetPort': 'ext-svc-4559'}], 'selector': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}}, {'apiVersion': 'apps/v1', 'kind': 'Deployment', 'metadata': {'name': 'localstack'}, 'spec': {'replicas': 1, 'strategy': {'type': 'RollingUpdate'}, 'selector': {'matchLabels': {'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'template': {'metadata': {'labels': {'app': 'localstack-{{ spawner.user.name }}', 'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': 'localstack'}}, 'spec': {'serviceAccountName': 'localstack', 'securityContext': {}, 'containers': [{'name': 'localstack', 'securityContext': {}, 'image': 'localstack/localstack:latest', 'imagePullPolicy': 'IfNotPresent', 'ports': [{'name': 'edge', 'containerPort': 4566, 'protocol': 'TCP'}, {'name': 'ext-svc-4510', 'containerPort': 4510, 'protocol': 'TCP'}, {'name': 'ext-svc-4511', 'containerPort': 4511, 'protocol': 'TCP'}, {'name': 'ext-svc-4512', 'containerPort': 4512, 'protocol': 'TCP'}, {'name': 'ext-svc-4513', 'containerPort': 4513, 'protocol': 'TCP'}, {'name': 'ext-svc-4514', 'containerPort': 4514, 'protocol': 'TCP'}, {'name': 'ext-svc-4515', 'containerPort': 4515, 'protocol': 'TCP'}, {'name': 'ext-svc-4516', 'containerPort': 4516, 'protocol': 'TCP'}, {'name': 'ext-svc-4517', 'containerPort': 4517, 'protocol': 'TCP'}, {'name': 'ext-svc-4518', 'containerPort': 4518, 'protocol': 'TCP'}, {'name': 'ext-svc-4519', 'containerPort': 4519, 'protocol': 'TCP'}, {'name': 'ext-svc-4520', 'containerPort': 4520, 'protocol': 'TCP'}, {'name': 'ext-svc-4521', 'containerPort': 4521, 'protocol': 'TCP'}, {'name': 'ext-svc-4522', 'containerPort': 4522, 'protocol': 'TCP'}, {'name': 'ext-svc-4523', 'containerPort': 4523, 'protocol': 'TCP'}, {'name': 'ext-svc-4524', 'containerPort': 4524, 'protocol': 'TCP'}, {'name': 'ext-svc-4525', 'containerPort': 4525, 'protocol': 'TCP'}, {'name': 'ext-svc-4526', 'containerPort': 4526, 'protocol': 'TCP'}, {'name': 'ext-svc-4527', 'containerPort': 4527, 'protocol': 'TCP'}, {'name': 'ext-svc-4528', 'containerPort': 4528, 'protocol': 'TCP'}, {'name': 'ext-svc-4529', 'containerPort': 4529, 'protocol': 'TCP'}, {'name': 'ext-svc-4530', 'containerPort': 4530, 'protocol': 'TCP'}, {'name': 'ext-svc-4531', 'containerPort': 4531, 'protocol': 'TCP'}, {'name': 'ext-svc-4532', 'containerPort': 4532, 'protocol': 'TCP'}, {'name': 'ext-svc-4533', 'containerPort': 4533, 'protocol': 'TCP'}, {'name': 'ext-svc-4534', 'containerPort': 4534, 'protocol': 'TCP'}, {'name': 'ext-svc-4535', 'containerPort': 4535, 'protocol': 'TCP'}, {'name': 'ext-svc-4536', 'containerPort': 4536, 'protocol': 'TCP'}, {'name': 'ext-svc-4537', 'containerPort': 4537, 'protocol': 'TCP'}, {'name': 'ext-svc-4538', 'containerPort': 4538, 'protocol': 'TCP'}, {'name': 'ext-svc-4539', 'containerPort': 4539, 'protocol': 'TCP'}, {'name': 'ext-svc-4540', 'containerPort': 4540, 'protocol': 'TCP'}, {'name': 'ext-svc-4541', 'containerPort': 4541, 'protocol': 'TCP'}, {'name': 'ext-svc-4542', 'containerPort': 4542, 'protocol': 'TCP'}, {'name': 'ext-svc-4543', 'containerPort': 4543, 'protocol': 'TCP'}, {'name': 'ext-svc-4544', 'containerPort': 4544, 'protocol': 'TCP'}, {'name': 'ext-svc-4545', 'containerPort': 4545, 'protocol': 'TCP'}, {'name': 'ext-svc-4546', 'containerPort': 4546, 'protocol': 'TCP'}, {'name': 'ext-svc-4547', 'containerPort': 4547, 'protocol': 'TCP'}, {'name': 'ext-svc-4548', 'containerPort': 4548, 'protocol': 'TCP'}, {'name': 'ext-svc-4549', 'containerPort': 4549, 'protocol': 'TCP'}, {'name': 'ext-svc-4550', 'containerPort': 4550, 'protocol': 'TCP'}, {'name': 'ext-svc-4551', 'containerPort': 4551, 'protocol': 'TCP'}, {'name': 'ext-svc-4552', 'containerPort': 4552, 'protocol': 'TCP'}, {'name': 'ext-svc-4553', 'containerPort': 4553, 'protocol': 'TCP'}, {'name': 'ext-svc-4554', 'containerPort': 4554, 'protocol': 'TCP'}, {'name': 'ext-svc-4555', 'containerPort': 4555, 'protocol': 'TCP'}, {'name': 'ext-svc-4556', 'containerPort': 4556, 'protocol': 'TCP'}, {'name': 'ext-svc-4557', 'containerPort': 4557, 'protocol': 'TCP'}, {'name': 'ext-svc-4558', 'containerPort': 4558, 'protocol': 'TCP'}, {'name': 'ext-svc-4559', 'containerPort': 4559, 'protocol': 'TCP'}], 'livenessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'readinessProbe': {'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, 'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': '/_localstack/health', 'port': 'edge'}}, 'resources': {}, 'env': [{'name': 'DEBUG', 'value': '0'}, {'name': 'EXTERNAL_SERVICE_PORTS_START', 'value': '4510'}, {'name': 'EXTERNAL_SERVICE_PORTS_END', 'value': '4560'}, {'name': 'LOCALSTACK_K8S_SERVICE_NAME', 'value': 'localstack'}, {'name': 'LOCALSTACK_K8S_NAMESPACE', 'valueFrom': {'fieldRef': {'fieldPath': 'metadata.namespace'}}}, {'name': 'LAMBDA_RUNTIME_EXECUTOR', 'value': 'docker'}, {'name': 'LAMBDA_K8S_IMAGE_PREFIX', 'value': 'localstack/lambda-'}, {'name': 'LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT', 'value': '60'}, {'name': 'OVERRIDE_IN_DOCKER', 'value': '1'}]}], 'volumes': []}}}}, {'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': 'my-config'}, 'data': {'ENV_VAR1': 'value1', 'ENV_VAR2': 'value2'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'my-secret'}, 'type': 'Opaque', 'data': {'SECRET_KEY1': 'dmFsdWUx', 'SECRET_KEY2': 'dmFsdWUy'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'aws-credentials-{{ spawner.user.name }}'}, 'type': 'Opaque', 'data': {'credentials': 'W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg='}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'data-by-name', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'data-by-name', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': 'secret-value', 'remoteRef': {'key': 'secret-one', 'property': 'the-key'}}]}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', 'metadata': {'name': 'eoepca-plus-secret-ro', 'namespace': 'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': {'name': 'eoepca-plus-secret-ro', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': '.dockerconfigjson', 'remoteRef': {'key': 'eoepca-plus-secret-ro', 'property': '.dockerconfigjson'}}]}}], persist=False)], env_from_config_maps=None, env_from_secrets=None, secret_mounts=None),\n", - " Profile(id='profile_studio_desktop_qgis', groups=['group-a', 'group-b'], definition=ProfileDefinition(display_name='QGIS on a Remote Desktop', description='Spatial visualization and decision-making tools for everyone', slug='eoepca_desktop_qgis', default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, mem_limit='2G', mem_guarantee=None, image='eoepca/iga-remote-desktop-qgis:1.1.3', extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, readonly=True, content='alias ll=\"ls -l\"\\nalias calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir /calrissian/\"\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\n. /home/jovyan/.bashrc\\n\\n#alias aws=\"aws --endpoint-url=http://localstack:4566\"\\n\\n# >>> conda initialize >>>\\n# !! Contents within this block are managed by \\'conda init\\' !!\\n__conda_setup=\"$(\\'/opt/conda/bin/conda\\' \\'shell.bash\\' \\'hook\\' 2> /dev/null)\"\\nif [ $? -eq 0 ]; then\\n eval \"$__conda_setup\"\\nelse\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\n else\\n export PATH=\"/srv/conda/bin:$PATH\"\\n fi\\nfi\\nunset __conda_setup\\n\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\n . \"/opt/conda/etc/profile.d/mamba.sh\"\\nfi\\n# <<< conda initialize <<<\\n\\na={{spawner.user.name}}\\n\\nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"', persist=False), ConfigMap(name='bash-login', key='bash-login', mount_path='/workspace/.bash_login', default_mode=None, readonly=True, content='source /workspace/.bashrc\\n', persist=False)], volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', size='50Gi', storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), persist=True)], pod_env_vars={'HOME': '/workspace'}, default_url='desktop', node_selector={}, role_bindings=None, image_pull_secrets=[], init_containers=[], manifests=None, env_from_config_maps=None, env_from_secrets=None, secret_mounts=None)]\n", - "(\"config: profiles=[Profile(id='profile_studio_coder1', groups=['group-a', \"\n", - " \"'group-b'], definition=ProfileDefinition(display_name='Code Server Small', \"\n", - " \"description=None, slug='ellip_studio_coder_slug_s', default=False, \"\n", - " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, '\n", - " \"mem_limit='8G', mem_guarantee=None, image='eoepca/pde-code-server:develop', \"\n", - " 'extra_resource_limits={}, extra_resource_guarantees={})), '\n", - " \"config_maps=[ConfigMap(name='bash-rc', key='bash-rc', \"\n", - " \"mount_path='/workspace/.bashrc', default_mode=None, readonly=True, \"\n", - " 'content=\\'alias ll=\"ls -l\"\\\\nalias calrissian=\"/opt/conda/bin/calrissian '\n", - " '--pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout '\n", - " '/calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix '\n", - " '/calrissian/tmp/ --outdir /calrissian/\"\\\\nalias '\n", - " 'cwltool=\"/opt/conda/bin/cwltool --podman\"\\\\n. '\n", - " '/home/jovyan/.bashrc\\\\n\\\\n#alias aws=\"aws '\n", - " '--endpoint-url=http://localstack:4566\"\\\\n\\\\n# >>> conda initialize >>>\\\\n# '\n", - " \"!! Contents within this block are managed by \\\\'conda init\\\\' \"\n", - " '!!\\\\n__conda_setup=\"$(\\\\\\'/opt/conda/bin/conda\\\\\\' \\\\\\'shell.bash\\\\\\' '\n", - " '\\\\\\'hook\\\\\\' 2> /dev/null)\"\\\\nif [ $? -eq 0 ]; then\\\\n eval '\n", - " '\"$__conda_setup\"\\\\nelse\\\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; '\n", - " 'then\\\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\\\n else\\\\n '\n", - " 'export PATH=\"/srv/conda/bin:$PATH\"\\\\n fi\\\\nfi\\\\nunset '\n", - " '__conda_setup\\\\n\\\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\\\n '\n", - " '. \"/opt/conda/etc/profile.d/mamba.sh\"\\\\nfi\\\\n# <<< conda initialize '\n", - " '<<<\\\\n\\\\na={{spawner.user.name}}\\\\n\\\\nalias aws=\"aws '\n", - " '--endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"\\', '\n", - " \"persist=False)], volumes=[Volume(name='calrissian-volume', \"\n", - " \"claim_name='calrissian-claim', size='50Gi', \"\n", - " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], \"\n", - " \"volume_mount=VolumeMount(name='calrissian-volume', \"\n", - " \"mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', \"\n", - " \"claim_name='workspace-claim', size='50Gi', \"\n", - " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], \"\n", - " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", - " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': \"\n", - " \"'/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, \"\n", - " 'image_pull_secrets=[], init_containers=[], manifests=None, '\n", - " 'env_from_config_maps=None, env_from_secrets=None, secret_mounts=None), '\n", - " \"Profile(id='profile_studio_coder2', groups=['group-a', 'group-b'], \"\n", - " \"definition=ProfileDefinition(display_name='Code Server Medium', \"\n", - " \"description=None, slug='ellip_studio_coder_slug_m', default=False, \"\n", - " 'kubespawner_override=KubespawnerOverride(cpu_limit=4, cpu_guarantee=None, '\n", - " \"mem_limit='12G', mem_guarantee=None, image='eoepca/pde-code-server:develop', \"\n", - " 'extra_resource_limits={}, extra_resource_guarantees={})), '\n", - " \"config_maps=[ConfigMap(name='bash-rc', key='bash-rc', \"\n", - " \"mount_path='/workspace/.bashrc', default_mode=None, readonly=True, \"\n", - " 'content=\\'alias ll=\"ls -l\"\\\\nalias calrissian=\"/opt/conda/bin/calrissian '\n", - " '--pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout '\n", - " '/calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix '\n", - " '/calrissian/tmp/ --outdir /calrissian/\"\\\\nalias '\n", - " 'cwltool=\"/opt/conda/bin/cwltool --podman\"\\\\n. '\n", - " '/home/jovyan/.bashrc\\\\n\\\\n#alias aws=\"aws '\n", - " '--endpoint-url=http://localstack:4566\"\\\\n\\\\n# >>> conda initialize >>>\\\\n# '\n", - " \"!! Contents within this block are managed by \\\\'conda init\\\\' \"\n", - " '!!\\\\n__conda_setup=\"$(\\\\\\'/opt/conda/bin/conda\\\\\\' \\\\\\'shell.bash\\\\\\' '\n", - " '\\\\\\'hook\\\\\\' 2> /dev/null)\"\\\\nif [ $? -eq 0 ]; then\\\\n eval '\n", - " '\"$__conda_setup\"\\\\nelse\\\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; '\n", - " 'then\\\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\\\n else\\\\n '\n", - " 'export PATH=\"/srv/conda/bin:$PATH\"\\\\n fi\\\\nfi\\\\nunset '\n", - " '__conda_setup\\\\n\\\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\\\n '\n", - " '. \"/opt/conda/etc/profile.d/mamba.sh\"\\\\nfi\\\\n# <<< conda initialize '\n", - " '<<<\\\\n\\\\na={{spawner.user.name}}\\\\n\\\\nalias aws=\"aws '\n", - " '--endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"\\', '\n", - " \"persist=False)], volumes=[Volume(name='calrissian-volume', \"\n", - " \"claim_name='calrissian-claim', size='50Gi', \"\n", - " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteMany'], \"\n", - " \"volume_mount=VolumeMount(name='calrissian-volume', \"\n", - " \"mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', \"\n", - " \"claim_name='workspace-claim', size='50Gi', \"\n", - " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], \"\n", - " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", - " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': \"\n", - " \"'/workspace/.envs'}, default_url=None, node_selector={}, role_bindings=None, \"\n", - " 'image_pull_secrets=[], init_containers=[], manifests=None, '\n", - " 'env_from_config_maps=None, env_from_secrets=None, secret_mounts=None), '\n", - " \"Profile(id='profile_demo_init_script', groups=['group-a', 'group-b'], \"\n", - " \"definition=ProfileDefinition(display_name='Coder demo init script', \"\n", - " \"description='This profile is used to demonstrate the use of an init script', \"\n", - " \"slug='eoepca_demo_init_script', default=False, \"\n", - " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, '\n", - " \"mem_limit='6G', mem_guarantee='4G', image='eoepca/pde-code-server:develop', \"\n", - " 'extra_resource_limits={}, extra_resource_guarantees={})), '\n", - " \"config_maps=[ConfigMap(name='init', key='init', \"\n", - " \"mount_path='/opt/init/.init.sh', default_mode='0660', readonly=True, \"\n", - " 'content=\"set -x\\\\n\\\\ncd /workspace\\\\n\\\\ngit clone '\n", - " \"'https://github.com/eoap/mastering-app-package.git'\\\\n\\\\ncode-server \"\n", - " '--install-extension ms-python.python\\\\ncode-server --install-extension '\n", - " 'redhat.vscode-yaml\\\\ncode-server --install-extension '\n", - " 'sbg-rabix.benten-cwl\\\\ncode-server --install-extension '\n", - " 'ms-toolsai.jupyter\\\\n\\\\nln -s /workspace/.local/share/code-server/extensions '\n", - " '/workspace/extensions\\\\n\\\\nexit 0\\\\n\", persist=False)], '\n", - " \"volumes=[Volume(name='calrissian-volume', claim_name='calrissian-claim', \"\n", - " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", - " \"access_modes=['ReadWriteMany'], \"\n", - " \"volume_mount=VolumeMount(name='calrissian-volume', \"\n", - " \"mount_path='/calrissian'), persist=False), Volume(name='workspace-volume', \"\n", - " \"claim_name='workspace-claim', size='50Gi', \"\n", - " \"storage_class='managed-nfs-storage', access_modes=['ReadWriteOnce'], \"\n", - " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", - " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': \"\n", - " \"'/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': \"\n", - " \"'/workspace/.local', 'CODE_SERVER_WS': '/workspace/mastering-app-package'}, \"\n", - " 'default_url=None, node_selector={}, role_bindings=None, '\n", - " 'image_pull_secrets=[], '\n", - " \"init_containers=[InitContainer(name='init-file-on-volume', \"\n", - " \"image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh \"\n", - " \"/opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', \"\n", - " \"mount_path='/workspace'), InitContainerVolumeMount(name='init', \"\n", - " \"mount_path='/opt/init/.init.sh', sub_path='init')])], manifests=None, \"\n", - " 'env_from_config_maps=None, env_from_secrets=None, secret_mounts=None), '\n", - " \"Profile(id='profile_jupyter_lab', groups=['group-c'], \"\n", - " \"definition=ProfileDefinition(display_name='Jupyter Lab', \"\n", - " \"description='Jupyter Lab with Python 3.11', slug='eoepca_jupyter_lab', \"\n", - " 'default=False, kubespawner_override=KubespawnerOverride(cpu_limit=2, '\n", - " \"cpu_guarantee=1, mem_limit='6G', mem_guarantee='4G', \"\n", - " \"image='jupyter/scipy-notebook', extra_resource_limits={}, \"\n", - " 'extra_resource_guarantees={})), config_maps=[], '\n", - " \"volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', \"\n", - " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", - " \"access_modes=['ReadWriteOnce'], \"\n", - " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", - " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': \"\n", - " \"'/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, \"\n", - " 'default_url=None, node_selector={}, role_bindings=None, '\n", - " 'image_pull_secrets=[], init_containers=[], manifests=None, '\n", - " 'env_from_config_maps=None, env_from_secrets=None, secret_mounts=None), '\n", - " \"Profile(id='profile_jupyter_lab_2', groups=['group-c'], \"\n", - " \"definition=ProfileDefinition(display_name='Jupyter Lab - profile 2', \"\n", - " \"description='Jupyter Lab with Python 3.11 private image - demoes the use of \"\n", - " \"an image pull secret', slug='eoepca_jupyter_lab_2', default=False, \"\n", - " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, '\n", - " \"mem_limit='6G', mem_guarantee='4G', image='eoepca/iat-jupyterlab:develop', \"\n", - " 'extra_resource_limits={}, extra_resource_guarantees={})), config_maps=[], '\n", - " \"volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', \"\n", - " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", - " \"access_modes=['ReadWriteOnce'], \"\n", - " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", - " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'XDG_RUNTIME_DIR': \"\n", - " \"'/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.config'}, \"\n", - " 'default_url=None, node_selector={}, role_bindings=None, '\n", - " \"image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, \"\n", - " \"data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], \"\n", - " 'init_containers=[], manifests=None, env_from_config_maps=None, '\n", - " 'env_from_secrets=None, secret_mounts=None), '\n", - " \"Profile(id='profile_studio_coder_stac', groups=['group-a', 'group-b'], \"\n", - " \"definition=ProfileDefinition(display_name='Understanding STAC for \"\n", - " \"input/output data modelling', description='Understand the role of STAC in \"\n", - " \"input/output data manifests in EO data processing workflows', \"\n", - " \"slug='eoepca_coder_slug_stac', default=False, \"\n", - " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=1, '\n", - " \"mem_limit='6G', mem_guarantee='4G', \"\n", - " \"image='docker.io/eoepca/pde-code-server@sha256:f57a3d5eabcae667e0db6e84a57b0c07c692c88f0fb5c8f6900ab8d5e38fcd40', \"\n", - " 'extra_resource_limits={}, extra_resource_guarantees={})), '\n", - " \"config_maps=[ConfigMap(name='init', key='init', \"\n", - " \"mount_path='/opt/init/.init.sh', default_mode=None, readonly=True, \"\n", - " \"content='set -x \\\\n\\\\ncd /workspace\\\\n\\\\ngit clone \"\n", - " \"\\\\'https://github.com/eoap/stac-eoap.git\\\\'\\\\n\\\\ncode-server \"\n", - " '--install-extension ms-python.python \\\\ncode-server --install-extension '\n", - " 'redhat.vscode-yaml\\\\ncode-server --install-extension '\n", - " 'sbg-rabix.benten-cwl\\\\ncode-server --install-extension '\n", - " 'ms-toolsai.jupyter\\\\n\\\\nln -s /workspace/.local/share/code-server/extensions '\n", - " '/workspace/extensions\\\\n\\\\nmkdir -p /workspace/User/\\\\n\\\\necho '\n", - " '\\\\\\'{\"workbench.colorTheme\": \"Visual Studio Dark\"}\\\\\\' > '\n", - " '/workspace/User/settings.json\\\\n\\\\npython -m venv /workspace/.venv\\\\nsource '\n", - " '/workspace/.venv/bin/activate\\\\n/workspace/.venv/bin/python -m pip install '\n", - " '--no-cache-dir stactools rasterio requests stac-asset click-logging tabulate '\n", - " 'tqdm pystac-client ipykernel loguru scikit-image rio_stac '\n", - " 'boto3==1.35.23\\\\n\\\\n/workspace/.venv/bin/python -m pip install --index-url '\n", - " 'https://test.pypi.org/simple cwl-wrapper\\\\n\\\\n/workspace/.venv/bin/python -m '\n", - " 'ipykernel install --user --name stac_env --display-name \"Python '\n", - " '(STAC)\"\\\\n\\\\nexport AWS_DEFAULT_REGION=\"us-east-1\"\\\\nexport '\n", - " 'AWS_ACCESS_KEY_ID=\"test\"\\\\nexport AWS_SECRET_ACCESS_KEY=\"test\"\\\\naws s3 mb '\n", - " \"s3://results --endpoint-url=http://localstack:4566\\\\n\\\\nexit 0', \"\n", - " \"persist=False), ConfigMap(name='bash-rc', key='bash-rc', \"\n", - " \"mount_path='/workspace/.bashrc', default_mode=None, readonly=True, \"\n", - " 'content=\\'alias ll=\"ls -l\"\\\\nalias calrissian=\"/opt/conda/bin/calrissian '\n", - " '--pod-nodeselectors /etc/calrissian/pod-node-selector.yml --stdout '\n", - " '/calrissian/results.json --max-ram 16G --max-cores \"8\" --tmp-outdir-prefix '\n", - " '/calrissian/tmp/ --outdir /calrissian/\"\\\\nalias '\n", - " 'cwltool=\"/opt/conda/bin/cwltool --podman\"\\\\n. '\n", - " '/home/jovyan/.bashrc\\\\n\\\\n#alias aws=\"aws '\n", - " '--endpoint-url=http://localstack:4566\"\\\\n\\\\n# >>> conda initialize >>>\\\\n# '\n", - " \"!! Contents within this block are managed by \\\\'conda init\\\\' \"\n", - " '!!\\\\n__conda_setup=\"$(\\\\\\'/opt/conda/bin/conda\\\\\\' \\\\\\'shell.bash\\\\\\' '\n", - " '\\\\\\'hook\\\\\\' 2> /dev/null)\"\\\\nif [ $? -eq 0 ]; then\\\\n eval '\n", - " '\"$__conda_setup\"\\\\nelse\\\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; '\n", - " 'then\\\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\\\n else\\\\n '\n", - " 'export PATH=\"/srv/conda/bin:$PATH\"\\\\n fi\\\\nfi\\\\nunset '\n", - " '__conda_setup\\\\n\\\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\\\n '\n", - " '. \"/opt/conda/etc/profile.d/mamba.sh\"\\\\nfi\\\\n# <<< conda initialize '\n", - " '<<<\\\\n\\\\na={{spawner.user.name}}\\\\n\\\\nalias aws=\"aws '\n", - " '--endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"\\', '\n", - " \"persist=False), ConfigMap(name='bash-login', key='bash-login', \"\n", - " \"mount_path='/workspace/.bash_login', default_mode=None, readonly=True, \"\n", - " \"content='source /workspace/.bashrc\\\\n', persist=False)], \"\n", - " \"volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', \"\n", - " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", - " \"access_modes=['ReadWriteOnce'], \"\n", - " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", - " \"persist=True)], pod_env_vars={'HOME': '/workspace', 'CONDA_ENVS_PATH': \"\n", - " \"'/workspace/.envs', 'CONDARC': '/workspace/.condarc', 'XDG_RUNTIME_DIR': \"\n", - " \"'/workspace/.local', 'XDG_CONFIG_HOME': '/workspace/.local', \"\n", - " \"'XDG_DATA_HOME': '/workspace/.local/share/', 'CWLTOOL_OPTIONS': '--podman', \"\n", - " \"'CODE_SERVER_WS': '/workspace/stac-eoap', 'AWS_DEFAULT_REGION': 'us-east-1', \"\n", - " \"'AWS_ACCESS_KEY_ID': 'test', 'AWS_SECRET_ACCESS_KEY': 'test'}, \"\n", - " 'default_url=None, node_selector={}, role_bindings=[], '\n", - " \"image_pull_secrets=[ImagePullSecret(name='cr-config', persist=False, \"\n", - " \"data='ewogICAgImF1dGhzIjogewogICAgICAgICJjci50ZXJyYWR1ZS5jb20iOiB7CiAgICAgICAgICAgICJ1c2VybmFtZSI6ICJyb2JvdCRlb2VwY2EtcGx1cy1ybyIsCiAgICAgICAgICAgICJwYXNzd29yZCI6ICJQMlE4TnkyZ0lHODhkZkxveXlLN05QVUZVbHJOekFZSiIsCiAgICAgICAgICAgICJlbWFpbCI6ICJlb2VwY2EtcGx1c0B0ZXJyYWR1ZS5jb20iLAogICAgICAgICAgICAiYXV0aCI6ICJjbTlpYjNRa1pXOWxjR05oTFhCc2RYTXRjbTg2VURKUk9FNTVNbWRKUnpnNFpHWk1iM2w1U3pkT1VGVkdWV3h5VG5wQldVbz0iCiAgICAgICAgfQogICAgfQp9')], \"\n", - " \"init_containers=[InitContainer(name='init-file-on-volume', \"\n", - " \"image='eoepca/pde-code-server:develop', command=['sh', '-c', 'sh \"\n", - " \"/opt/init/.init.sh'], volume_mounts=[VolumeMount(name='workspace-volume', \"\n", - " \"mount_path='/workspace'), InitContainerVolumeMount(name='init', \"\n", - " \"mount_path='/opt/init/.init.sh', sub_path='init')])], \"\n", - " \"manifests=[Manifest(name='manifests', key='manifests', \"\n", - " \"content=[{'apiVersion': 'v1', 'kind': 'ServiceAccount', 'metadata': {'name': \"\n", - " \"'localstack'}}, {'apiVersion': 'rbac.authorization.k8s.io/v1', 'kind': \"\n", - " \"'Role', 'metadata': {'name': 'localstack'}, 'rules': [{'apiGroups': [''], \"\n", - " \"'resources': ['pods'], 'verbs': ['*']}, {'apiGroups': [''], 'resources': \"\n", - " \"['pods/log'], 'verbs': ['get']}, {'apiGroups': [''], 'resources': \"\n", - " \"['pods/exec'], 'verbs': ['get', 'create']}, {'apiGroups': [''], 'resources': \"\n", - " \"['services'], 'verbs': ['get', 'list']}]}, {'apiVersion': \"\n", - " \"'rbac.authorization.k8s.io/v1', 'kind': 'RoleBinding', 'metadata': {'name': \"\n", - " \"'localstack'}, 'subjects': [{'kind': 'ServiceAccount', 'name': \"\n", - " \"'localstack'}], 'roleRef': {'kind': 'Role', 'name': 'localstack', \"\n", - " \"'apiGroup': 'rbac.authorization.k8s.io'}}, {'apiVersion': 'v1', 'kind': \"\n", - " \"'Service', 'metadata': {'name': 'localstack'}, 'spec': {'type': 'ClusterIP', \"\n", - " \"'ports': [{'name': 'edge', 'port': 4566, 'targetPort': 4566}, {'name': \"\n", - " \"'external-service-port-4510', 'port': 4510, 'targetPort': 'ext-svc-4510'}, \"\n", - " \"{'name': 'external-service-port-4511', 'port': 4511, 'targetPort': \"\n", - " \"'ext-svc-4511'}, {'name': 'external-service-port-4512', 'port': 4512, \"\n", - " \"'targetPort': 'ext-svc-4512'}, {'name': 'external-service-port-4513', \"\n", - " \"'port': 4513, 'targetPort': 'ext-svc-4513'}, {'name': \"\n", - " \"'external-service-port-4514', 'port': 4514, 'targetPort': 'ext-svc-4514'}, \"\n", - " \"{'name': 'external-service-port-4515', 'port': 4515, 'targetPort': \"\n", - " \"'ext-svc-4515'}, {'name': 'external-service-port-4516', 'port': 4516, \"\n", - " \"'targetPort': 'ext-svc-4516'}, {'name': 'external-service-port-4517', \"\n", - " \"'port': 4517, 'targetPort': 'ext-svc-4517'}, {'name': \"\n", - " \"'external-service-port-4518', 'port': 4518, 'targetPort': 'ext-svc-4518'}, \"\n", - " \"{'name': 'external-service-port-4519', 'port': 4519, 'targetPort': \"\n", - " \"'ext-svc-4519'}, {'name': 'external-service-port-4520', 'port': 4520, \"\n", - " \"'targetPort': 'ext-svc-4520'}, {'name': 'external-service-port-4521', \"\n", - " \"'port': 4521, 'targetPort': 'ext-svc-4521'}, {'name': \"\n", - " \"'external-service-port-4522', 'port': 4522, 'targetPort': 'ext-svc-4522'}, \"\n", - " \"{'name': 'external-service-port-4523', 'port': 4523, 'targetPort': \"\n", - " \"'ext-svc-4523'}, {'name': 'external-service-port-4524', 'port': 4524, \"\n", - " \"'targetPort': 'ext-svc-4524'}, {'name': 'external-service-port-4525', \"\n", - " \"'port': 4525, 'targetPort': 'ext-svc-4525'}, {'name': \"\n", - " \"'external-service-port-4526', 'port': 4526, 'targetPort': 'ext-svc-4526'}, \"\n", - " \"{'name': 'external-service-port-4527', 'port': 4527, 'targetPort': \"\n", - " \"'ext-svc-4527'}, {'name': 'external-service-port-4528', 'port': 4528, \"\n", - " \"'targetPort': 'ext-svc-4528'}, {'name': 'external-service-port-4529', \"\n", - " \"'port': 4529, 'targetPort': 'ext-svc-4529'}, {'name': \"\n", - " \"'external-service-port-4530', 'port': 4530, 'targetPort': 'ext-svc-4530'}, \"\n", - " \"{'name': 'external-service-port-4531', 'port': 4531, 'targetPort': \"\n", - " \"'ext-svc-4531'}, {'name': 'external-service-port-4532', 'port': 4532, \"\n", - " \"'targetPort': 'ext-svc-4532'}, {'name': 'external-service-port-4533', \"\n", - " \"'port': 4533, 'targetPort': 'ext-svc-4533'}, {'name': \"\n", - " \"'external-service-port-4534', 'port': 4534, 'targetPort': 'ext-svc-4534'}, \"\n", - " \"{'name': 'external-service-port-4535', 'port': 4535, 'targetPort': \"\n", - " \"'ext-svc-4535'}, {'name': 'external-service-port-4536', 'port': 4536, \"\n", - " \"'targetPort': 'ext-svc-4536'}, {'name': 'external-service-port-4537', \"\n", - " \"'port': 4537, 'targetPort': 'ext-svc-4537'}, {'name': \"\n", - " \"'external-service-port-4538', 'port': 4538, 'targetPort': 'ext-svc-4538'}, \"\n", - " \"{'name': 'external-service-port-4539', 'port': 4539, 'targetPort': \"\n", - " \"'ext-svc-4539'}, {'name': 'external-service-port-4540', 'port': 4540, \"\n", - " \"'targetPort': 'ext-svc-4540'}, {'name': 'external-service-port-4541', \"\n", - " \"'port': 4541, 'targetPort': 'ext-svc-4541'}, {'name': \"\n", - " \"'external-service-port-4542', 'port': 4542, 'targetPort': 'ext-svc-4542'}, \"\n", - " \"{'name': 'external-service-port-4543', 'port': 4543, 'targetPort': \"\n", - " \"'ext-svc-4543'}, {'name': 'external-service-port-4544', 'port': 4544, \"\n", - " \"'targetPort': 'ext-svc-4544'}, {'name': 'external-service-port-4545', \"\n", - " \"'port': 4545, 'targetPort': 'ext-svc-4545'}, {'name': \"\n", - " \"'external-service-port-4546', 'port': 4546, 'targetPort': 'ext-svc-4546'}, \"\n", - " \"{'name': 'external-service-port-4547', 'port': 4547, 'targetPort': \"\n", - " \"'ext-svc-4547'}, {'name': 'external-service-port-4548', 'port': 4548, \"\n", - " \"'targetPort': 'ext-svc-4548'}, {'name': 'external-service-port-4549', \"\n", - " \"'port': 4549, 'targetPort': 'ext-svc-4549'}, {'name': \"\n", - " \"'external-service-port-4550', 'port': 4550, 'targetPort': 'ext-svc-4550'}, \"\n", - " \"{'name': 'external-service-port-4551', 'port': 4551, 'targetPort': \"\n", - " \"'ext-svc-4551'}, {'name': 'external-service-port-4552', 'port': 4552, \"\n", - " \"'targetPort': 'ext-svc-4552'}, {'name': 'external-service-port-4553', \"\n", - " \"'port': 4553, 'targetPort': 'ext-svc-4553'}, {'name': \"\n", - " \"'external-service-port-4554', 'port': 4554, 'targetPort': 'ext-svc-4554'}, \"\n", - " \"{'name': 'external-service-port-4555', 'port': 4555, 'targetPort': \"\n", - " \"'ext-svc-4555'}, {'name': 'external-service-port-4556', 'port': 4556, \"\n", - " \"'targetPort': 'ext-svc-4556'}, {'name': 'external-service-port-4557', \"\n", - " \"'port': 4557, 'targetPort': 'ext-svc-4557'}, {'name': \"\n", - " \"'external-service-port-4558', 'port': 4558, 'targetPort': 'ext-svc-4558'}, \"\n", - " \"{'name': 'external-service-port-4559', 'port': 4559, 'targetPort': \"\n", - " \"'ext-svc-4559'}], 'selector': {'app.kubernetes.io/name': 'localstack', \"\n", - " \"'app.kubernetes.io/instance': 'localstack'}}}, {'apiVersion': 'apps/v1', \"\n", - " \"'kind': 'Deployment', 'metadata': {'name': 'localstack'}, 'spec': \"\n", - " \"{'replicas': 1, 'strategy': {'type': 'RollingUpdate'}, 'selector': \"\n", - " \"{'matchLabels': {'app.kubernetes.io/name': 'localstack', \"\n", - " \"'app.kubernetes.io/instance': 'localstack'}}, 'template': {'metadata': \"\n", - " \"{'labels': {'app': 'localstack-{{ spawner.user.name }}', \"\n", - " \"'app.kubernetes.io/name': 'localstack', 'app.kubernetes.io/instance': \"\n", - " \"'localstack'}}, 'spec': {'serviceAccountName': 'localstack', \"\n", - " \"'securityContext': {}, 'containers': [{'name': 'localstack', \"\n", - " \"'securityContext': {}, 'image': 'localstack/localstack:latest', \"\n", - " \"'imagePullPolicy': 'IfNotPresent', 'ports': [{'name': 'edge', \"\n", - " \"'containerPort': 4566, 'protocol': 'TCP'}, {'name': 'ext-svc-4510', \"\n", - " \"'containerPort': 4510, 'protocol': 'TCP'}, {'name': 'ext-svc-4511', \"\n", - " \"'containerPort': 4511, 'protocol': 'TCP'}, {'name': 'ext-svc-4512', \"\n", - " \"'containerPort': 4512, 'protocol': 'TCP'}, {'name': 'ext-svc-4513', \"\n", - " \"'containerPort': 4513, 'protocol': 'TCP'}, {'name': 'ext-svc-4514', \"\n", - " \"'containerPort': 4514, 'protocol': 'TCP'}, {'name': 'ext-svc-4515', \"\n", - " \"'containerPort': 4515, 'protocol': 'TCP'}, {'name': 'ext-svc-4516', \"\n", - " \"'containerPort': 4516, 'protocol': 'TCP'}, {'name': 'ext-svc-4517', \"\n", - " \"'containerPort': 4517, 'protocol': 'TCP'}, {'name': 'ext-svc-4518', \"\n", - " \"'containerPort': 4518, 'protocol': 'TCP'}, {'name': 'ext-svc-4519', \"\n", - " \"'containerPort': 4519, 'protocol': 'TCP'}, {'name': 'ext-svc-4520', \"\n", - " \"'containerPort': 4520, 'protocol': 'TCP'}, {'name': 'ext-svc-4521', \"\n", - " \"'containerPort': 4521, 'protocol': 'TCP'}, {'name': 'ext-svc-4522', \"\n", - " \"'containerPort': 4522, 'protocol': 'TCP'}, {'name': 'ext-svc-4523', \"\n", - " \"'containerPort': 4523, 'protocol': 'TCP'}, {'name': 'ext-svc-4524', \"\n", - " \"'containerPort': 4524, 'protocol': 'TCP'}, {'name': 'ext-svc-4525', \"\n", - " \"'containerPort': 4525, 'protocol': 'TCP'}, {'name': 'ext-svc-4526', \"\n", - " \"'containerPort': 4526, 'protocol': 'TCP'}, {'name': 'ext-svc-4527', \"\n", - " \"'containerPort': 4527, 'protocol': 'TCP'}, {'name': 'ext-svc-4528', \"\n", - " \"'containerPort': 4528, 'protocol': 'TCP'}, {'name': 'ext-svc-4529', \"\n", - " \"'containerPort': 4529, 'protocol': 'TCP'}, {'name': 'ext-svc-4530', \"\n", - " \"'containerPort': 4530, 'protocol': 'TCP'}, {'name': 'ext-svc-4531', \"\n", - " \"'containerPort': 4531, 'protocol': 'TCP'}, {'name': 'ext-svc-4532', \"\n", - " \"'containerPort': 4532, 'protocol': 'TCP'}, {'name': 'ext-svc-4533', \"\n", - " \"'containerPort': 4533, 'protocol': 'TCP'}, {'name': 'ext-svc-4534', \"\n", - " \"'containerPort': 4534, 'protocol': 'TCP'}, {'name': 'ext-svc-4535', \"\n", - " \"'containerPort': 4535, 'protocol': 'TCP'}, {'name': 'ext-svc-4536', \"\n", - " \"'containerPort': 4536, 'protocol': 'TCP'}, {'name': 'ext-svc-4537', \"\n", - " \"'containerPort': 4537, 'protocol': 'TCP'}, {'name': 'ext-svc-4538', \"\n", - " \"'containerPort': 4538, 'protocol': 'TCP'}, {'name': 'ext-svc-4539', \"\n", - " \"'containerPort': 4539, 'protocol': 'TCP'}, {'name': 'ext-svc-4540', \"\n", - " \"'containerPort': 4540, 'protocol': 'TCP'}, {'name': 'ext-svc-4541', \"\n", - " \"'containerPort': 4541, 'protocol': 'TCP'}, {'name': 'ext-svc-4542', \"\n", - " \"'containerPort': 4542, 'protocol': 'TCP'}, {'name': 'ext-svc-4543', \"\n", - " \"'containerPort': 4543, 'protocol': 'TCP'}, {'name': 'ext-svc-4544', \"\n", - " \"'containerPort': 4544, 'protocol': 'TCP'}, {'name': 'ext-svc-4545', \"\n", - " \"'containerPort': 4545, 'protocol': 'TCP'}, {'name': 'ext-svc-4546', \"\n", - " \"'containerPort': 4546, 'protocol': 'TCP'}, {'name': 'ext-svc-4547', \"\n", - " \"'containerPort': 4547, 'protocol': 'TCP'}, {'name': 'ext-svc-4548', \"\n", - " \"'containerPort': 4548, 'protocol': 'TCP'}, {'name': 'ext-svc-4549', \"\n", - " \"'containerPort': 4549, 'protocol': 'TCP'}, {'name': 'ext-svc-4550', \"\n", - " \"'containerPort': 4550, 'protocol': 'TCP'}, {'name': 'ext-svc-4551', \"\n", - " \"'containerPort': 4551, 'protocol': 'TCP'}, {'name': 'ext-svc-4552', \"\n", - " \"'containerPort': 4552, 'protocol': 'TCP'}, {'name': 'ext-svc-4553', \"\n", - " \"'containerPort': 4553, 'protocol': 'TCP'}, {'name': 'ext-svc-4554', \"\n", - " \"'containerPort': 4554, 'protocol': 'TCP'}, {'name': 'ext-svc-4555', \"\n", - " \"'containerPort': 4555, 'protocol': 'TCP'}, {'name': 'ext-svc-4556', \"\n", - " \"'containerPort': 4556, 'protocol': 'TCP'}, {'name': 'ext-svc-4557', \"\n", - " \"'containerPort': 4557, 'protocol': 'TCP'}, {'name': 'ext-svc-4558', \"\n", - " \"'containerPort': 4558, 'protocol': 'TCP'}, {'name': 'ext-svc-4559', \"\n", - " \"'containerPort': 4559, 'protocol': 'TCP'}], 'livenessProbe': \"\n", - " \"{'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, \"\n", - " \"'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': \"\n", - " \"'/_localstack/health', 'port': 'edge'}}, 'readinessProbe': \"\n", - " \"{'failureThreshold': 3, 'initialDelaySeconds': 0, 'periodSeconds': 10, \"\n", - " \"'successThreshold': 1, 'timeoutSeconds': 1, 'httpGet': {'path': \"\n", - " \"'/_localstack/health', 'port': 'edge'}}, 'resources': {}, 'env': [{'name': \"\n", - " \"'DEBUG', 'value': '0'}, {'name': 'EXTERNAL_SERVICE_PORTS_START', 'value': \"\n", - " \"'4510'}, {'name': 'EXTERNAL_SERVICE_PORTS_END', 'value': '4560'}, {'name': \"\n", - " \"'LOCALSTACK_K8S_SERVICE_NAME', 'value': 'localstack'}, {'name': \"\n", - " \"'LOCALSTACK_K8S_NAMESPACE', 'valueFrom': {'fieldRef': {'fieldPath': \"\n", - " \"'metadata.namespace'}}}, {'name': 'LAMBDA_RUNTIME_EXECUTOR', 'value': \"\n", - " \"'docker'}, {'name': 'LAMBDA_K8S_IMAGE_PREFIX', 'value': \"\n", - " \"'localstack/lambda-'}, {'name': 'LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT', \"\n", - " \"'value': '60'}, {'name': 'OVERRIDE_IN_DOCKER', 'value': '1'}]}], 'volumes': \"\n", - " \"[]}}}}, {'apiVersion': 'v1', 'kind': 'ConfigMap', 'metadata': {'name': \"\n", - " \"'my-config'}, 'data': {'ENV_VAR1': 'value1', 'ENV_VAR2': 'value2'}}, \"\n", - " \"{'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': 'my-secret'}, \"\n", - " \"'type': 'Opaque', 'data': {'SECRET_KEY1': 'dmFsdWUx', 'SECRET_KEY2': \"\n", - " \"'dmFsdWUy'}}, {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'name': \"\n", - " \"'aws-credentials-{{ spawner.user.name }}'}, 'type': 'Opaque', 'data': \"\n", - " \"{'credentials': \"\n", - " \"'W2RlZmF1bHRdCmF3c19hY2Nlc3Nfa2V5X2lkPUFTSUFJT1NGT0ROTjdFWEFNUExFCmF3c19zZWNyZXRfYWNjZXNzX2tleT13SmFsclhVdG5GRU1JL0s3TURFTkcvYlB4UmZpQ1lFWEFNUExFS0VZCmF3c19zZXNzaW9uX3Rva2VuPUlRb0piM2pySmdCV05FTE5Hb2xHSkxFT3RTVEFOR1k0TFlPNUk0SzVOUlZFS1pPTkNTTk1HRlNUS1FNSUxXUjJPUzAwRklDRTExSlg='}}, \"\n", - " \"{'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ExternalSecret', \"\n", - " \"'metadata': {'name': 'data-by-name', 'namespace': 'jupyter-{{ \"\n", - " \"spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', 'secretStoreRef': \"\n", - " \"{'kind': 'ClusterSecretStore', 'name': 'k8s-secret-store'}, 'target': \"\n", - " \"{'name': 'data-by-name', 'creationPolicy': 'Owner'}, 'data': [{'secretKey': \"\n", - " \"'secret-value', 'remoteRef': {'key': 'secret-one', 'property': \"\n", - " \"'the-key'}}]}}, {'apiVersion': 'external-secrets.io/v1beta1', 'kind': \"\n", - " \"'ExternalSecret', 'metadata': {'name': 'eoepca-plus-secret-ro', 'namespace': \"\n", - " \"'jupyter-{{ spawner.user.name }}'}, 'spec': {'refreshInterval': '15s', \"\n", - " \"'secretStoreRef': {'kind': 'ClusterSecretStore', 'name': \"\n", - " \"'k8s-secret-store'}, 'target': {'name': 'eoepca-plus-secret-ro', \"\n", - " \"'creationPolicy': 'Owner'}, 'data': [{'secretKey': '.dockerconfigjson', \"\n", - " \"'remoteRef': {'key': 'eoepca-plus-secret-ro', 'property': \"\n", - " \"'.dockerconfigjson'}}]}}], persist=False)], env_from_config_maps=None, \"\n", - " 'env_from_secrets=None, secret_mounts=None), '\n", - " \"Profile(id='profile_studio_desktop_qgis', groups=['group-a', 'group-b'], \"\n", - " \"definition=ProfileDefinition(display_name='QGIS on a Remote Desktop', \"\n", - " \"description='Spatial visualization and decision-making tools for everyone', \"\n", - " \"slug='eoepca_desktop_qgis', default=False, \"\n", - " 'kubespawner_override=KubespawnerOverride(cpu_limit=2, cpu_guarantee=None, '\n", - " \"mem_limit='2G', mem_guarantee=None, \"\n", - " \"image='eoepca/iga-remote-desktop-qgis:1.1.3', extra_resource_limits={}, \"\n", - " \"extra_resource_guarantees={})), config_maps=[ConfigMap(name='bash-rc', \"\n", - " \"key='bash-rc', mount_path='/workspace/.bashrc', default_mode=None, \"\n", - " 'readonly=True, content=\\'alias ll=\"ls -l\"\\\\nalias '\n", - " 'calrissian=\"/opt/conda/bin/calrissian --pod-nodeselectors '\n", - " '/etc/calrissian/pod-node-selector.yml --stdout /calrissian/results.json '\n", - " '--max-ram 16G --max-cores \"8\" --tmp-outdir-prefix /calrissian/tmp/ --outdir '\n", - " '/calrissian/\"\\\\nalias cwltool=\"/opt/conda/bin/cwltool --podman\"\\\\n. '\n", - " '/home/jovyan/.bashrc\\\\n\\\\n#alias aws=\"aws '\n", - " '--endpoint-url=http://localstack:4566\"\\\\n\\\\n# >>> conda initialize >>>\\\\n# '\n", - " \"!! Contents within this block are managed by \\\\'conda init\\\\' \"\n", - " '!!\\\\n__conda_setup=\"$(\\\\\\'/opt/conda/bin/conda\\\\\\' \\\\\\'shell.bash\\\\\\' '\n", - " '\\\\\\'hook\\\\\\' 2> /dev/null)\"\\\\nif [ $? -eq 0 ]; then\\\\n eval '\n", - " '\"$__conda_setup\"\\\\nelse\\\\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; '\n", - " 'then\\\\n . \"/opt/conda/etc/profile.d/conda.sh\"\\\\n else\\\\n '\n", - " 'export PATH=\"/srv/conda/bin:$PATH\"\\\\n fi\\\\nfi\\\\nunset '\n", - " '__conda_setup\\\\n\\\\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\\\\n '\n", - " '. \"/opt/conda/etc/profile.d/mamba.sh\"\\\\nfi\\\\n# <<< conda initialize '\n", - " '<<<\\\\n\\\\na={{spawner.user.name}}\\\\n\\\\nalias aws=\"aws '\n", - " '--endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"\\', '\n", - " \"persist=False), ConfigMap(name='bash-login', key='bash-login', \"\n", - " \"mount_path='/workspace/.bash_login', default_mode=None, readonly=True, \"\n", - " \"content='source /workspace/.bashrc\\\\n', persist=False)], \"\n", - " \"volumes=[Volume(name='workspace-volume', claim_name='workspace-claim', \"\n", - " \"size='50Gi', storage_class='managed-nfs-storage', \"\n", - " \"access_modes=['ReadWriteOnce'], \"\n", - " \"volume_mount=VolumeMount(name='workspace-volume', mount_path='/workspace'), \"\n", - " \"persist=True)], pod_env_vars={'HOME': '/workspace'}, default_url='desktop', \"\n", - " 'node_selector={}, role_bindings=None, image_pull_secrets=[], '\n", - " 'init_containers=[], manifests=None, env_from_config_maps=None, '\n", - " 'env_from_secrets=None, secret_mounts=None)]')\n" - ] - } - ], - "source": [ - "pprint(profiles)\n", - "config = Config(profiles=profiles) # type: ignore\n", - "pprint(f'config: {config}')\n", - "eoepca_demo_config_path = str(Path(current_dir).parent.parent / 'eoepca-demo' / 'config.yml')\n", - "with open(eoepca_demo_config_path, \"w\") as file:\n", - " yaml.dump(config.model_dump(), file)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "base", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.5" - }, - "orig_nbformat": 4 - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/examples/generate-config.sh b/docs/examples/generate-config.sh deleted file mode 100755 index 0238b9d..0000000 --- a/docs/examples/generate-config.sh +++ /dev/null @@ -1,4 +0,0 @@ -#exit 0 -export PYTHONPATH=$PWD/`dirname $0`/. - -.env-apphub-configurator/bin/python3 -m generate_config \ No newline at end of file diff --git a/docs/examples/generate_config.py b/docs/examples/generate_config.py deleted file mode 100644 index a09a990..0000000 --- a/docs/examples/generate_config.py +++ /dev/null @@ -1,168 +0,0 @@ -from apphub_configurator.models import * -import yaml -from pathlib import Path -import os -from loguru import logger -from apphub_configurator.helpers import ( - load_config_map, - load_manifests, - create_init_container, - load_init_script, -) -import click - -logger.info("Generating config file...") - -storage_class_rwo = "standard" -storage_class_rwx = "standard" -profiles = [] -workspace_volume_size = "50Gi" -calrissian_volume_size = "50Gi" -image = "eoepca/pde-code-server:develop" -node_selector = {} - -# get the current directory -current_dir = Path(os.path.dirname(os.path.realpath(__file__))) -parent_dir = current_dir.parent - -# load the manifests -localstack_manifest_path = os.path.join(parent_dir, "manifests/manifest.yaml") -# localstack_manifest -localstack_manifest = load_manifests( - name="localstack", - key="localstack", - file_path=localstack_manifest_path, -) - -dask_gateway_manifest_path = os.path.join(parent_dir, "manifests/dask-gateway.yaml") -# Dask Gateway manifest -dask_gateway_manifest = load_manifests( - name="dask-gateway", - key="dask-gateway", - file_path=dask_gateway_manifest_path, -) -kaniko_manifest_path = os.path.join(parent_dir, "manifests/kaniko.yaml") -kaniko_manifest = load_manifests( - name="kaniko", - key="kaniko", - file_path=kaniko_manifest_path, -) -# volumes -workspace_volume = Volume( - name="workspace-volume", - size=workspace_volume_size, - claim_name="workspace-claim", - mount_path="/workspace", - storage_class=storage_class_rwo, - access_modes=["ReadWriteOnce"], - volume_mount=VolumeMount(name="workspace-volume", mount_path="/workspace"), - persist=True, -) - -calrissian_volume = Volume( - name="calrissian-volume", - claim_name="calrissian-claim", - size=calrissian_volume_size, - storage_class=storage_class_rwx, - access_modes=["ReadWriteMany"], - volume_mount=VolumeMount(name="calrissian-volume", mount_path="/calrissian"), - persist=False, -) - -bash_login_file_path = os.path.join(parent_dir, "config-maps/bash-login") -bash_login_cm = load_config_map( - name="bash-login", - key="bash-login", - file_name=bash_login_file_path, - mount_path="/etc/profile.d/bash-login.sh", -) - -bash_rc_cm_file_path = os.path.join(parent_dir, "config-maps/bash-rc") -bash_rc_cm = load_config_map( - name="bash-rc", - key="bash-rc", - file_name=bash_rc_cm_file_path, - mount_path="/workspace/.bashrc", -) -init_cm_file_path = os.path.join(parent_dir, "config-maps/init.sh") -init_cm = load_init_script(init_cm_file_path) - -init_container = create_init_container( - image=image, - volume=workspace_volume, - mount_path="/calrissian", -) - -profile_1 = Profile( - id=f"profile_1", - groups=["group-a", "group-b"], - definition=ProfileDefinition( - display_name="Coder demo init script", - description="This profile is used to demonstrate the use of an init script", - slug="profile_1", - default=False, - kubespawner_override=KubespawnerOverride( - cpu_guarantee=1, - cpu_limit=2, - mem_guarantee="4G", - mem_limit="6G", - image=image, - ), - ), - node_selector=node_selector, - volumes=[calrissian_volume, workspace_volume], - config_maps=[ - init_cm, - ConfigMap( - name="dask-gateway-config", - key="gateway", - mount_path="/etc/dask/gateway.yaml", - readonly=True, - ), - ConfigMap( - name="dask-gateway-config-ws", - key="gateway", - mount_path="/workspace/.config/dask/gateway.yaml", - readonly=True, - ), - bash_login_cm, - bash_rc_cm, - ], - pod_env_vars={ - "HOME": "/workspace", - "CONDA_ENVS_PATH": "/workspace/.envs", - "CONDARC": "/workspace/.condarc", - "XDG_RUNTIME_DIR": "/workspace/.local", - "CODE_SERVER_WS": "/workspace/mastering-app-package", - "DASK_GATEWAY": "http://traefik-dask-gw-jupyter-{{ spawner.user.name }}-dask-gateway.jupyter-{{ spawner.user.name }}.svc.cluster.local:80", - }, - init_containers=[init_container], - manifests=[localstack_manifest, dask_gateway_manifest, kaniko_manifest], - env_from_config_maps=["my-config"], - env_from_secrets=["my-secret", "data-by-name"], - secret_mounts=[ - SecretMount( - name="aws-credentials-{{ spawner.user.name }}", mount_path="/workspace/.aws" - ), - SecretMount(name="data-by-name", mount_path="/workspace/.data-by-name"), - SecretMount( - name="eoepca-plus-secret-ro", - mount_path="/workspace/.docker/config.json", - sub_path=".dockerconfigjson", - ), - ], - image_pull_secrets=[ImagePullSecret(name="eoepca-plus-secret-ro")], -) - -profiles.append(profile_1) - -config = Config(profiles=profiles) -config_file_path = str(Path(current_dir).parent.parent / 'files' / 'hub' / 'config.yml') - - -with open(config_file_path, "w") as file: - yaml.dump(config.model_dump(), file, width=200) - -logger.success( - f"Config file generated successfully at {config_file_path}" -) \ No newline at end of file diff --git a/docs/hands-on.md b/docs/hands-on.md index 4af4559..0af261a 100644 --- a/docs/hands-on.md +++ b/docs/hands-on.md @@ -1,6 +1,6 @@ # Hands-on - Config generator: -Open the an example [notebook](./examples/config-generator.ipynb) to generate a `config.yaml` with your desired profile configuration +Open the example [notebook](./config-generator.ipynb) to generate a `config.yaml` with your desired profile configuration - Add `eoepca/application-hub` from helm repositories. - Deploy the ApplicationHub on a remote cluster using skaffold. \ No newline at end of file From 60f57e436ae0bf29defc88d11a27f1ae1bd172fc Mon Sep 17 00:00:00 2001 From: stripodi Date: Wed, 23 Apr 2025 17:51:45 +0200 Subject: [PATCH 66/86] feat: #377 initial implementation --- .gitignore | 6 +- Taskfile.yaml | 36 ++++ files/hub/config.yml | 28 +-- schemas/config.schema.yaml | 339 +++++++++++++++++++++++++++++++++++++ 4 files changed, 394 insertions(+), 15 deletions(-) create mode 100644 Taskfile.yaml create mode 100644 schemas/config.schema.yaml diff --git a/.gitignore b/.gitignore index f2a524e..ed2736c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,8 @@ build _README.md dist .env-apphub-configurator -hatch \ No newline at end of file +hatch + +.project +.settings +.pydevproject diff --git a/Taskfile.yaml b/Taskfile.yaml new file mode 100644 index 0000000..525e686 --- /dev/null +++ b/Taskfile.yaml @@ -0,0 +1,36 @@ +version: '3' + +tasks: + + # schema + + conditional-pip-install: + internal: true + status: + - command -v {{.COMMAND}} + cmds: + - pip install {{.COMMAND}} + + check-schema: + desc: Executes a strict JSON schema check + deps: + - task: conditional-pip-install + vars: + COMMAND: check-jsonschema + cmds: + - for: ["./files/hub/config.yml"] + cmd: check-jsonschema --schemafile ./schemas/config.schema.yaml {{.ITEM}} + + create-models: + desc: Generates the Pydantic models with field aliases + deps: + - task: conditional-pip-install + vars: + COMMAND: datamodel-codegen + cmds: + - | + datamodel-codegen --input ./schemas/config.schema.yaml \ + --input-file-type jsonschema \ + --output-model-type pydantic_v2.BaseModel \ + --base-class pydantic.BaseModel \ + --output ./application_hub_context/models.py diff --git a/files/hub/config.yml b/files/hub/config.yml index 9d2021b..6b495b6 100644 --- a/files/hub/config.yml +++ b/files/hub/config.yml @@ -30,15 +30,15 @@ profiles: name: init persist: false readonly: true - - content: null - default_mode: null + - # content: null + # default_mode: null key: gateway mount_path: /etc/dask/gateway.yaml name: dask-gateway-config persist: true readonly: true - - content: null - default_mode: null + - # content: null + # default_mode: null key: gateway mount_path: /workspace/.config/dask/gateway.yaml name: dask-gateway-config-ws @@ -47,7 +47,7 @@ profiles: - content: 'source /workspace/.bashrc ' - default_mode: null + # default_mode: null key: bash-login mount_path: /etc/profile.d/bash-login.sh name: bash-login @@ -59,13 +59,13 @@ profiles: \ eval \"$__conda_setup\"\nelse\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\n . \"/opt/conda/etc/profile.d/conda.sh\"\n else\n export PATH=\"/srv/conda/bin:$PATH\"\ \n fi\nfi\nunset __conda_setup\n\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\n . \"/opt/conda/etc/profile.d/mamba.sh\"\nfi\n# <<< conda initialize <<<\n\na={{spawner.user.name}}\n\ \nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"" - default_mode: null + # default_mode: null key: bash-rc mount_path: /workspace/.bashrc name: bash-rc persist: true readonly: true - default_url: null + # default_url: null definition: default: false description: This profile is used to demonstrate the use of an init script @@ -89,7 +89,7 @@ profiles: - group-b id: profile_1 image_pull_secrets: - - data: null + - # data: null name: eoepca-plus-secret-ro persist: true init_containers: @@ -699,10 +699,10 @@ profiles: - sleep infinity image: gcr.io/kaniko-project/executor:debug name: kaniko - volumeMounts: - - mountPath: /calrissian + volume_mounts: + - mount_path: /calrissian name: build-context - - mountPath: /kaniko/.docker + - mount_path: /kaniko/.docker name: kaniko-secret restartPolicy: Never volumes: @@ -753,14 +753,14 @@ profiles: DASK_GATEWAY: http://traefik-dask-gw-jupyter-{{ spawner.user.name }}-dask-gateway.jupyter-{{ spawner.user.name }}.svc.cluster.local:80 HOME: /workspace XDG_RUNTIME_DIR: /workspace/.local - role_bindings: null + # role_bindings: null secret_mounts: - mount_path: /workspace/.aws name: aws-credentials-{{ spawner.user.name }} - sub_path: null + # sub_path: null - mount_path: /workspace/.data-by-name name: data-by-name - sub_path: null + # sub_path: null - mount_path: /workspace/.docker/config.json name: eoepca-plus-secret-ro sub_path: .dockerconfigjson diff --git a/schemas/config.schema.yaml b/schemas/config.schema.yaml new file mode 100644 index 0000000..d5f0ce3 --- /dev/null +++ b/schemas/config.schema.yaml @@ -0,0 +1,339 @@ +$schema: "https:/json-schema.org/draft/2020-12/schema" +$id: "https:/www.terradue.com/eoap/v1/project.yaml" +title: Config +type: object +properties: + profiles: + type: array + items: + $ref: "#/$defs/Profile" +required: +- profiles + +$defs: + + ConfigMap: + type: object + properties: + name: + type: string + key: + type: string + mount_path: + type: string + default_mode: + type: string + readonly: + type: boolean + content: + type: string + persist: + type: boolean + default: true + required: + - name + - key + - readonly + + ConfigMapEnvVarReference: + type: object + properties: + from_config_map: + $ref: "#/$defs/ConfigMapKeyRef" + required: + - from_config_map + + ConfigMapKeyRef: + type: object + properties: + name: + type: string + key: + type: string + required: + - name + - key + + ImagePullSecret: + type: object + properties: + name: + type: string + persist: + default: true + type: boolean + data: + type: string + required: + - name + + InitContainer: + type: object + properties: + name: + type: string + image: + type: string + command: + type: array + items: + type: string + volume_mounts: + type: array + items: + $ref: "#/$defs/VolumeMount" + required: + - name + - image + - command + - volume_mounts + + KubespawnerOverride: + type: object + properties: + cpu_limit: + type: integer + cpu_guarantee: + type: integer + mem_limit: + type: string + mem_guarantee: + type: string + image: + type: string + extra_resource_limits: + type: object + additionalProperties: true + extra_resource_guarantees: + type: object + additionalProperties: true + required: + - cpu_limit + - mem_limit + - image + + Manifest: + type: object + properties: + name: + type: string + key: + type: string + content: + type: array + items: + type: object + additionalProperties: true + persist: + type: boolean + default: true + required: + - name + - key + + Profile: + type: object + properties: + id: + type: string + groups: + type: array + items: + type: string + definition: + $ref: "#/$defs/ProfileDefinition" + config_maps: + type: array + items: + $ref: "#/$defs/ConfigMap" + volumes: + type: array + items: + $ref: "#/$defs/Volume" + pod_env_vars: + type: object + additionalProperties: + anyOf: + - type: string + - $ref: "#/$defs/ConfigMapEnvVarReference" + default_url: + type: string + node_selector: + type: object + additionalProperties: true + role_bindings: + type: array + items: + $ref: "#/$defs/RoleBinding" + image_pull_secrets: + type: array + items: + $ref: "#/$defs/ImagePullSecret" + init_containers: + type: array + items: + $ref: "#/$defs/InitContainer" + manifests: + type: array + items: + $ref: "#/$defs/Manifest" + env_from_config_maps: + type: array + items: + type: string + env_from_secrets: + type: array + items: + type: string + secret_mounts: + type: array + items: + $ref: "#/$defs/SecretMount" + required: + - id + - groups + - definition + - node_selector + + ProfileDefinition: + type: object + properties: + display_name: + type: string + description: + type: string + slug: + type: string + default: + type: boolean + kubespawner_override: + $ref: "#/$defs/KubespawnerOverride" + required: + - display_name + - slug + - default + - kubespawner_override + + Role: + type: object + properties: + name: + type: string + resources: + type: array + items: + type: string + verbs: + type: array + items: + $ref: "#/$defs/Verb" + api_groups: + type: array + items: + type: string + required: + - name + - resources + - verbs + + RoleBinding: + type: object + properties: + name: + type: string + subjects: + items: + $ref: "#/$defs/Subject" + type: array + role: + $ref: "#/$defs/Role" + persist: + type: boolean + default: true + required: + - name + - subjects + - role + + SecretMount: + type: object + properties: + name: + type: string + mount_path: + type: string + sub_path: + type: string + required: + - name + - mount_path + + Subject: + type: object + properties: + name: + type: string + kind: + $ref: "#/$defs/SubjectKind" + required: + - name + - kind + + SubjectKind: + type: string + enum: + - ServiceAccount + - User + + Verb: + type: string + enum: + - get + - list + - watch + - create + - update + - patch + - delete + - deletecollection + + Volume: + type: object + properties: + name: + type: string + claimName: + type: string + size: + type: string + storage_class: + type: string + access_modes: + items: + type: string + type: array + volume_mount: + $ref: "#/$defs/VolumeMount" + persist: + type: boolean + required: + - name + - claim_name + - size + - storage_class + - access_modes + - volume_mount + - persist + + VolumeMount: + type: object + properties: + name: + type: string + mount_path: + type: string + sub_path: + type: string + required: + - name + - mount_path From 1b8af1216fe3a1f4d930fb87b858749bb93ccde4 Mon Sep 17 00:00:00 2001 From: ldonnini Date: Fri, 9 May 2025 14:32:42 +0200 Subject: [PATCH 67/86] COPLACSER-445: fix: remove hardcoded namespace in dynamically generated ExternalSecret --- application_hub_context/app_hub_context.py | 2 +- build.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application_hub_context/app_hub_context.py b/application_hub_context/app_hub_context.py index 946a02a..0d3d243 100644 --- a/application_hub_context/app_hub_context.py +++ b/application_hub_context/app_hub_context.py @@ -587,7 +587,7 @@ def apply_manifest(self, manifest): # Define the ExternalSecret details group = "external-secrets.io" version = "v1beta1" - namespace = f"jupyter-{self.spawner.user.name}" + namespace = self.namespace plural = "externalsecrets" self.custom_objects_api.create_namespaced_custom_object( diff --git a/build.yml b/build.yml index 5e78c85..46b213f 100644 --- a/build.yml +++ b/build.yml @@ -1,2 +1,2 @@ docker_image_name: eoepca/application-hub -docker_image_version: 1.4.0 +docker_image_version: 1.4.1 From 07c63d810cec264141e1a38bde1f47406bcceb9e Mon Sep 17 00:00:00 2001 From: stripodi Date: Wed, 14 May 2025 16:13:09 +0200 Subject: [PATCH 68/86] feat: #377 schema update according to the expected cases + improved task to check multiple/single instance --- Taskfile.yaml | 27 ++++++++++++++++++++++----- schemas/config.schema.yaml | 8 +++++--- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/Taskfile.yaml b/Taskfile.yaml index 525e686..63234ac 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -11,17 +11,33 @@ tasks: cmds: - pip install {{.COMMAND}} - check-schema: - desc: Executes a strict JSON schema check + check_schema: + desc: Executes a strict check of a configuration over the JSON schema deps: - task: conditional-pip-install vars: COMMAND: check-jsonschema + ignore_error: true cmds: - - for: ["./files/hub/config.yml"] - cmd: check-jsonschema --schemafile ./schemas/config.schema.yaml {{.ITEM}} + - check-jsonschema --schemafile ./schemas/config.schema.yaml {{.CONFIG}} - create-models: + check_schemas: + desc: Executes a strict check of a configurations set over the JSON schema + vars: + CONFIGS: + - './eoepca-demo/config.yml' + - './files/hub/config.yml' + cmds: + - for: + var: CONFIGS + as: CONFIG + task: check_schema + vars: + CONFIG: "{{.CONFIG}}" + + # Python models + + create_models: desc: Generates the Pydantic models with field aliases deps: - task: conditional-pip-install @@ -33,4 +49,5 @@ tasks: --input-file-type jsonschema \ --output-model-type pydantic_v2.BaseModel \ --base-class pydantic.BaseModel \ + --snake-case-field \ --output ./application_hub_context/models.py diff --git a/schemas/config.schema.yaml b/schemas/config.schema.yaml index d5f0ce3..b855913 100644 --- a/schemas/config.schema.yaml +++ b/schemas/config.schema.yaml @@ -104,6 +104,7 @@ $defs: extra_resource_limits: type: object additionalProperties: true + default: {} extra_resource_guarantees: type: object additionalProperties: true @@ -231,6 +232,7 @@ $defs: type: array items: type: string + default: [""] required: - name - resources @@ -302,7 +304,7 @@ $defs: properties: name: type: string - claimName: + claim_name: type: string size: type: string @@ -330,9 +332,9 @@ $defs: properties: name: type: string - mount_path: + mountPath: type: string - sub_path: + subPath: type: string required: - name From c23481ce329fc5f737ffc711272c75f869be0846 Mon Sep 17 00:00:00 2001 From: ldonnini Date: Wed, 21 May 2025 14:34:20 +0200 Subject: [PATCH 69/86] improvemt to manage namespace in dynamic way --- .github/workflows/.github-ci.yaml | 15 ++--- .../examples/generate_config.py | 2 +- .../manifests/dask-gateway.yaml | 14 ++--- apphub-configurator/manifests/kaniko.yaml | 8 +-- apphub-configurator/manifests/manifest.yaml | 10 ++-- application_hub_context/app_hub_context.py | 5 +- application_hub_context/parser.py | 5 +- build.yml | 2 +- files/hub/config.yml | 55 +++++++++---------- 9 files changed, 58 insertions(+), 58 deletions(-) diff --git a/.github/workflows/.github-ci.yaml b/.github/workflows/.github-ci.yaml index da5c940..93b23db 100644 --- a/.github/workflows/.github-ci.yaml +++ b/.github/workflows/.github-ci.yaml @@ -155,17 +155,18 @@ jobs: docker push "${{ secrets.CR_REGISTRY }}"/"${{ secrets.CR_REPO }}"/"${tag}" # Step 10: Login Docker Hub - - name: Login to Docker Hub + - name: Login to GHCR uses: docker/login-action@v2 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} # Step 11: Push to Docker Hub - name: push to Docker Hub run: | tag="${docker_image_application}:${docker_image_version}" - docker tag "${tag}" "docker.io/${{ env.docker_tag }}" - docker tag "${tag}" "docker.io/${{ env.docker_tag_latest }}" - docker push "docker.io/${{ env.docker_tag }}" - docker push "docker.io/${{ env.docker_tag_latest }}" + docker tag "${tag}" "ghcr.io/EOEPCA/${{ env.docker_tag }}" + docker tag "${tag}" "ghcr.io/EOEPCA/${{ env.docker_tag_latest }}" + docker push "ghcr.io/EOEPCA/${{ env.docker_tag }}" + docker push "ghcr.io/EOEPCA/${{ env.docker_tag_latest }}" diff --git a/apphub-configurator/examples/generate_config.py b/apphub-configurator/examples/generate_config.py index a09a990..2473fa4 100644 --- a/apphub-configurator/examples/generate_config.py +++ b/apphub-configurator/examples/generate_config.py @@ -90,7 +90,7 @@ init_container = create_init_container( image=image, volume=workspace_volume, - mount_path="/calrissian", + mount_path="/workspace", ) profile_1 = Profile( diff --git a/apphub-configurator/manifests/dask-gateway.yaml b/apphub-configurator/manifests/dask-gateway.yaml index c760862..53c8697 100644 --- a/apphub-configurator/manifests/dask-gateway.yaml +++ b/apphub-configurator/manifests/dask-gateway.yaml @@ -2,14 +2,14 @@ apiVersion: helm.crossplane.io/v1beta1 kind: Release metadata: name: dask-gw-jupyter-{{ spawner.user.name }} - namespace: jupyter-{{ spawner.user.name }} + namespace: "{{ namespace }}" spec: forProvider: chart: name: dask-gateway version: "2024.1.0" repository: https://helm.dask.org - namespace: jupyter-{{ spawner.user.name }} + namespace: "{{ namespace }}" values: gateway: backend: @@ -39,7 +39,7 @@ metadata: data: gateway: | gateway: - address: http://traefik-dask-gw-jupyter-{{ spawner.user.name }}-dask-gateway.jupyter-{{ spawner.user.name }}.svc.cluster.local:80 + address: http://traefik-dask-gw-jupyter-{{ spawner.user.name }}-dask-gateway.{namespace}-{{ spawner.user.name }}.svc.cluster.local:80 cluster: options: @@ -55,7 +55,7 @@ metadata: data: gateway: | gateway: - address: http://traefik-dask-gw-jupyter-{{ spawner.user.name }}-dask-gateway.jupyter-{{ spawner.user.name }}.svc.cluster.local:80 + address: http://traefik-dask-gw-jupyter-{{ spawner.user.name }}-dask-gateway.{namespace}-{{ spawner.user.name }}.svc.cluster.local:80 cluster: options: @@ -68,7 +68,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: access-services - namespace: jupyter-{{ spawner.user.name }} + #namespace: jupyter-{{ spawner.user.name }} rules: - verbs: - get @@ -98,11 +98,11 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: bind-default-to-services - namespace: jupyter-{{ spawner.user.name }} + #namespace: jupyter-{{ spawner.user.name }} subjects: - kind: ServiceAccount name: default - namespace: jupyter-{{ spawner.user.name }} + #namespace: jupyter-{{ spawner.user.name }} roleRef: apiGroup: rbac.authorization.k8s.io kind: Role diff --git a/apphub-configurator/manifests/kaniko.yaml b/apphub-configurator/manifests/kaniko.yaml index e7280b9..f86df21 100644 --- a/apphub-configurator/manifests/kaniko.yaml +++ b/apphub-configurator/manifests/kaniko.yaml @@ -2,7 +2,7 @@ kind: Pod metadata: name: kaniko-build - namespace: jupyter-{{ spawner.user.name }} + namespace: "{{ namespace }}" spec: containers: - name: kaniko @@ -32,7 +32,7 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: pod-exec - namespace: jupyter-{{ spawner.user.name }} + namespace: "{{ namespace }}" rules: - verbs: - get @@ -47,11 +47,11 @@ apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: bind-default-to-opd-exec - namespace: jupyter-{{ spawner.user.name }} + namespace: "{{ namespace }}" subjects: - kind: ServiceAccount name: default - namespace: jupyter-{{ spawner.user.name }} + namespace: "{{ namespace }}" roleRef: apiGroup: rbac.authorization.k8s.io kind: Role diff --git a/apphub-configurator/manifests/manifest.yaml b/apphub-configurator/manifests/manifest.yaml index 829b1f7..0e1b171 100644 --- a/apphub-configurator/manifests/manifest.yaml +++ b/apphub-configurator/manifests/manifest.yaml @@ -449,15 +449,15 @@ data: # apiVersion: helm.crossplane.io/v1beta1 # kind: Release # metadata: -# name: wordpress-jupyter-{{ spawner.user.name }} -# namespace: jupyter-{{ spawner.user.name }} +# name: wordpress-{namespace}-{{ spawner.user.name }} +# namespace: {namespace}-{{ spawner.user.name }} # spec: # forProvider: # chart: # name: wordpress # version: "24.1.4" # repository: oci://registry-1.docker.io/bitnamicharts/ -# namespace: jupyter-{{ spawner.user.name }} +# namespace: {namespace}-{{ spawner.user.name }} # values: # service: # type: ClusterIP @@ -468,7 +468,7 @@ apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: data-by-name - namespace: jupyter-{{ spawner.user.name }} + namespace: "{{ namespace }}" spec: refreshInterval: 15s secretStoreRef: @@ -488,7 +488,7 @@ apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: eoepca-plus-secret-ro - namespace: jupyter-{{ spawner.user.name }} + namespace: "{{ namespace }}" spec: refreshInterval: 15s secretStoreRef: diff --git a/application_hub_context/app_hub_context.py b/application_hub_context/app_hub_context.py index 0d3d243..dc9ee4a 100644 --- a/application_hub_context/app_hub_context.py +++ b/application_hub_context/app_hub_context.py @@ -50,7 +50,7 @@ def __init__( # loads config self.config_parser = ConfigParser.read_file( - config_path=config_path, user_groups=self.user_groups, spawner=self.spawner + config_path=config_path, user_groups=self.user_groups, spawner=self.spawner, namespace=self.namespace ) # update class dict with kwargs @@ -546,7 +546,8 @@ def patch_service_account(self, secret_name: str): def apply_manifest(self, manifest): template = Template(yaml.dump(manifest)) - rendered_manifest = template.render(spawner=self.spawner) + rendered_manifest = template.render(spawner=self.spawner, + namespace=self.namespace) self.spawner.log.info(f"Applying manifest name: {yaml.safe_load(rendered_manifest).get('metadata').get('name')}") diff --git a/application_hub_context/parser.py b/application_hub_context/parser.py index 679c3b1..64e37c9 100644 --- a/application_hub_context/parser.py +++ b/application_hub_context/parser.py @@ -12,7 +12,7 @@ def __init__(self, config_data, user_groups): self.user_groups = user_groups @classmethod - def read_file(cls, config_path, user_groups, spawner): + def read_file(cls, config_path, user_groups, spawner, namespace): """reads a config file encoded in YAML""" with open(config_path, "r") as stream: try: @@ -21,7 +21,8 @@ def read_file(cls, config_path, user_groups, spawner): # Render the content as a Jinja2 template template = Template(raw_content) - rendered_content = template.render(spawner=spawner) + rendered_content = template.render(spawner=spawner, + namespace=namespace) # Parse the rendered content as YAML config_data = yaml.safe_load(rendered_content) diff --git a/build.yml b/build.yml index 46b213f..9b1d84c 100644 --- a/build.yml +++ b/build.yml @@ -1,2 +1,2 @@ docker_image_name: eoepca/application-hub -docker_image_version: 1.4.1 +docker_image_version: 1.4.2 diff --git a/files/hub/config.yml b/files/hub/config.yml index 6b495b6..4488ac1 100644 --- a/files/hub/config.yml +++ b/files/hub/config.yml @@ -30,15 +30,15 @@ profiles: name: init persist: false readonly: true - - # content: null - # default_mode: null + - content: null + default_mode: null key: gateway mount_path: /etc/dask/gateway.yaml name: dask-gateway-config persist: true readonly: true - - # content: null - # default_mode: null + - content: null + default_mode: null key: gateway mount_path: /workspace/.config/dask/gateway.yaml name: dask-gateway-config-ws @@ -47,7 +47,7 @@ profiles: - content: 'source /workspace/.bashrc ' - # default_mode: null + default_mode: null key: bash-login mount_path: /etc/profile.d/bash-login.sh name: bash-login @@ -58,14 +58,14 @@ profiles: \n\n# >>> conda initialize >>>\n# !! Contents within this block are managed by 'conda init' !!\n__conda_setup=\"$('/opt/conda/bin/conda' 'shell.bash' 'hook' 2> /dev/null)\"\nif [ $? -eq 0 ]; then\n\ \ eval \"$__conda_setup\"\nelse\n if [ -f \"/opt/conda/etc/profile.d/conda.sh\" ]; then\n . \"/opt/conda/etc/profile.d/conda.sh\"\n else\n export PATH=\"/srv/conda/bin:$PATH\"\ \n fi\nfi\nunset __conda_setup\n\nif [ -f \"/opt/conda/etc/profile.d/mamba.sh\" ]; then\n . \"/opt/conda/etc/profile.d/mamba.sh\"\nfi\n# <<< conda initialize <<<\n\na={{spawner.user.name}}\n\ - \nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"" - # default_mode: null + \nalias aws=\"aws --endpoint-url=http://localstack-jupyter-{{spawner.user.name}}:4566\"\n\nexport PATH=/workspace/.local/bin:$PATH" + default_mode: null key: bash-rc mount_path: /workspace/.bashrc name: bash-rc persist: true readonly: true - # default_url: null + default_url: null definition: default: false description: This profile is used to demonstrate the use of an init script @@ -89,7 +89,7 @@ profiles: - group-b id: profile_1 image_pull_secrets: - - # data: null + - data: null name: eoepca-plus-secret-ro persist: true init_containers: @@ -100,7 +100,7 @@ profiles: image: eoepca/pde-code-server:develop name: init-file-on-volume volume_mounts: - - mount_path: /calrissian + - mount_path: /workspace name: workspace-volume - mount_path: /opt/init/.init.sh name: init @@ -563,7 +563,7 @@ profiles: kind: ExternalSecret metadata: name: data-by-name - namespace: jupyter-{{ spawner.user.name }} + namespace: '{namespace}-{{ spawner.user.name }}' spec: data: - remoteRef: @@ -581,7 +581,7 @@ profiles: kind: ExternalSecret metadata: name: eoepca-plus-secret-ro - namespace: jupyter-{{ spawner.user.name }} + namespace: '{namespace}-{{ spawner.user.name }}' spec: data: - remoteRef: @@ -603,14 +603,14 @@ profiles: kind: Release metadata: name: dask-gw-jupyter-{{ spawner.user.name }} - namespace: jupyter-{{ spawner.user.name }} + namespace: '{namespace}-{{ spawner.user.name }}' spec: forProvider: chart: name: dask-gateway repository: https://helm.dask.org version: 2024.1.0 - namespace: jupyter-{{ spawner.user.name }} + namespace: '{namespace}-{{ spawner.user.name }}' values: gateway: backend: @@ -628,14 +628,14 @@ profiles: name: helm-provider - apiVersion: v1 data: - gateway: "gateway:\n address: http://traefik-dask-gw-jupyter-{{ spawner.user.name }}-dask-gateway.jupyter-{{ spawner.user.name }}.svc.cluster.local:80\n\n cluster:\n options: \n image:\ + gateway: "gateway:\n address: http://traefik-dask-gw-jupyter-{{ spawner.user.name }}-dask-gateway.{namespace}-{{ spawner.user.name }}.svc.cluster.local:80\n\n cluster:\n options: \n image:\ \ \"ghcr.io/fabricebrito/dev-platform-dask-gateway/worker:1.0.0\"\n worker_cores: 0.5\n worker_cores_limit: 1\n worker_memory: \"4 G\"\n" kind: ConfigMap metadata: name: dask-gateway-config - apiVersion: v1 data: - gateway: "gateway:\n address: http://traefik-dask-gw-jupyter-{{ spawner.user.name }}-dask-gateway.jupyter-{{ spawner.user.name }}.svc.cluster.local:80\n\n cluster:\n options: \n image:\ + gateway: "gateway:\n address: http://traefik-dask-gw-jupyter-{{ spawner.user.name }}-dask-gateway.{namespace}-{{ spawner.user.name }}.svc.cluster.local:80\n\n cluster:\n options: \n image:\ \ \"ghcr.io/fabricebrito/dev-platform-dask-gateway/worker:1.0.0\"\n worker_cores: 0.5\n worker_cores_limit: 1\n worker_memory: \"4 G\"\n" kind: ConfigMap metadata: @@ -644,7 +644,6 @@ profiles: kind: Role metadata: name: access-services - namespace: jupyter-{{ spawner.user.name }} rules: - apiGroups: - '' @@ -673,7 +672,6 @@ profiles: kind: RoleBinding metadata: name: bind-default-to-services - namespace: jupyter-{{ spawner.user.name }} roleRef: apiGroup: rbac.authorization.k8s.io kind: Role @@ -681,7 +679,6 @@ profiles: subjects: - kind: ServiceAccount name: default - namespace: jupyter-{{ spawner.user.name }} key: dask-gateway name: dask-gateway persist: false @@ -690,7 +687,7 @@ profiles: kind: Pod metadata: name: kaniko-build - namespace: jupyter-{{ spawner.user.name }} + namespace: '{namespace}-{{ spawner.user.name }}' spec: containers: - command: @@ -699,10 +696,10 @@ profiles: - sleep infinity image: gcr.io/kaniko-project/executor:debug name: kaniko - volume_mounts: - - mount_path: /calrissian + volumeMounts: + - mountPath: /calrissian name: build-context - - mount_path: /kaniko/.docker + - mountPath: /kaniko/.docker name: kaniko-secret restartPolicy: Never volumes: @@ -719,7 +716,7 @@ profiles: kind: Role metadata: name: pod-exec - namespace: jupyter-{{ spawner.user.name }} + namespace: '{namespace}-{{ spawner.user.name }}' rules: - apiGroups: - '' @@ -733,7 +730,7 @@ profiles: kind: RoleBinding metadata: name: bind-default-to-opd-exec - namespace: jupyter-{{ spawner.user.name }} + namespace: '{namespace}-{{ spawner.user.name }}' roleRef: apiGroup: rbac.authorization.k8s.io kind: Role @@ -741,7 +738,7 @@ profiles: subjects: - kind: ServiceAccount name: default - namespace: jupyter-{{ spawner.user.name }} + namespace: '{namespace}-{{ spawner.user.name }}' key: kaniko name: kaniko persist: false @@ -753,14 +750,14 @@ profiles: DASK_GATEWAY: http://traefik-dask-gw-jupyter-{{ spawner.user.name }}-dask-gateway.jupyter-{{ spawner.user.name }}.svc.cluster.local:80 HOME: /workspace XDG_RUNTIME_DIR: /workspace/.local - # role_bindings: null + role_bindings: null secret_mounts: - mount_path: /workspace/.aws name: aws-credentials-{{ spawner.user.name }} - # sub_path: null + sub_path: null - mount_path: /workspace/.data-by-name name: data-by-name - # sub_path: null + sub_path: null - mount_path: /workspace/.docker/config.json name: eoepca-plus-secret-ro sub_path: .dockerconfigjson From c86ec955ebd2c916a1376061bb5b619eaad42b1c Mon Sep 17 00:00:00 2001 From: ldonnini Date: Wed, 21 May 2025 15:13:42 +0200 Subject: [PATCH 70/86] updated files to fix the issue with the gitlab-ci: https://github.com/EOEPCA/application-hub-context/actions/runs/15162303194/job/42631094503 --- apphub-configurator/pyproject.toml | 2 +- apphub-configurator/src/apphub_configurator/__about__.py | 2 +- setup.cfg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apphub-configurator/pyproject.toml b/apphub-configurator/pyproject.toml index d47dd4a..ab3c84d 100644 --- a/apphub-configurator/pyproject.toml +++ b/apphub-configurator/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "apphub-configurator" dynamic = ["version"] description = "apphub-configurator" -readme = "../docs/app-hub-configurator.md" +readme = "../docs/configuration.md" requires-python = ">=3.10" license = "Apache-2.0" keywords = ["kubernetes", "configuration", "EOEPCA"] diff --git a/apphub-configurator/src/apphub_configurator/__about__.py b/apphub-configurator/src/apphub_configurator/__about__.py index 14ed908..7e37c70 100644 --- a/apphub-configurator/src/apphub_configurator/__about__.py +++ b/apphub-configurator/src/apphub_configurator/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2025-present pmembari # # SPDX-License-Identifier: MIT -__version__ = "0.0.7" +__version__ = "0.0.8" diff --git a/setup.cfg b/setup.cfg index 3f7496a..397bfd7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,4 @@ [metadata] name = application-context-hub -version = 1.3.1 +version = 1.3.2 From 5fdd7f23bcfe84b5b3fe63fa1f4ba768ddb64969 Mon Sep 17 00:00:00 2001 From: ldonnini Date: Wed, 21 May 2025 15:37:23 +0200 Subject: [PATCH 71/86] fixed pipeline issue --- Dockerfile | 4 +- apphub-configurator/pyproject.toml | 2 +- docs/README.md | 122 +++++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 docs/README.md diff --git a/Dockerfile b/Dockerfile index 851c12a..ab34dd0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,7 +41,9 @@ RUN pip3 install --upgrade --no-cache-dir setuptools pip # Specific Python dependencies installation RUN PYCURL_SSL_LIBRARY=openssl \ - pip install --no-cache-dir -r /tmp/requirements.txt + pip install --no-cache-dir -r /tmp/requirements.txt &&\ + pip install --no-cache-dir "tornado==6.5.0" + # Check and correct requirejs version RUN sed -i 's/"version": "[^"]*"/"version": "2.3.7"/' /usr/local/share/jupyterhub/static/components/requirejs/package.json diff --git a/apphub-configurator/pyproject.toml b/apphub-configurator/pyproject.toml index ab3c84d..e6d1082 100644 --- a/apphub-configurator/pyproject.toml +++ b/apphub-configurator/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "apphub-configurator" dynamic = ["version"] description = "apphub-configurator" -readme = "../docs/configuration.md" +readme = "../docs/README.md" requires-python = ">=3.10" license = "Apache-2.0" keywords = ["kubernetes", "configuration", "EOEPCA"] diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..3214d58 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,122 @@ +# apphub-configurator + +[![PyPI - Version](https://img.shields.io/pypi/v/apphub-configurator.svg)](https://pypi.org/project/apphub-configurator) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/apphub-configurator.svg)](https://pypi.org/project/apphub-configurator) + +----- + +## Table of Contents + +- [Installation](#installation) +- [Overview](#overview) +- [Examples](#Examples) +- [License](#license) + +## Installation + +```console +pip install apphub-configurator +``` +## Overview +This package contains a notebook and the python modules to support the generation of ApplicationHub configurations for a minikube cluster. For more information about ApplicationHub please check this [link](https://github.com/EOEPCA/application-hub-context) + +## Examples: + +Find more examples if you need from this [link](https://github.com/EOEPCA/application-hub-context/tree/ESAEOEPCA-236/config-generator/apphub-configurator/examples) + +### Step 1: Setup the environment + +To begin using the `apphub-configurator` package, import the required functions from the package: + +```python +from apphub_configurator.helpers import load_config_map, load_manifests, create_init_container, load_init_script +``` + +### Step 2: Example of configuration generation + +Here is an overview of the functions and how to use them to generate configurations: + +1. **Loading Kubernetes Manifests:** + Use `load_manifests()` to load the required Kubernetes manifests. This function takes the following parameters: + - `name`: The name of the manifest. + - `key`: The key used to reference the manifest. + - `file_path`: The file path to the manifest YAML file. + + Example usage: + ```python + load_manifests(name="example-name", key="example-key", file_path="path/to/manifest.yaml") + ``` + +2. **Creating Volumes:** + You can create a Kubernetes `Volume` by specifying the following: + - `name`: The name of the volume. + - `size`: The size of the volume (e.g., `"50Gi"`). + - `claim_name`: The claim name for the volume. + - `mount_path`: The path where the volume should be mounted. + + Example usage: + ```python + Volume( + name="workspace-volume", + size="50Gi", + claim_name="workspace-claim", + mount_path="/workspace" + ) + ``` + +3. **Loading ConfigMaps:** + Use `load_config_map()` to load configuration maps. It requires the following parameters: + - `name`: The name of the config map. + - `key`: The key for the configuration map. + - `file_name`: The file path to the configuration file. + - `mount_path`: The path where the config map should be mounted. + + Example usage: + ```python + load_config_map(name="bash-login", key="bash-login", file_name="path/to/bash-login", mount_path="/etc/profile.d/bash-login.sh") + ``` + +4. **Creating Init Containers:** + The `create_init_container()` function allows you to define init containers with the following parameters: + - `image`: The container image to use. + - `volume`: The volume associated with the container. + - `mount_path`: The path where the volume will be mounted inside the container. + + Example usage: + ```python + create_init_container(image="example-image", volume=your_volume, mount_path="/calrissian") + ``` + +5. **Creating Profiles:** + You can create a `Profile` by defining its parameters such as `id`, `groups`, `definition`, and others. The profile can include volumes, config maps, init containers, and manifests. + + Example usage: + ```python + Profile( + id="profile_1", + definition=ProfileDefinition( + display_name="Example Profile", + description="This profile configures an example service", + default=True + ), + volumes=[your_volume], + config_maps=[your_config_map], + init_containers=[your_init_container], + manifests=[your_manifest] + ) + ``` + +6. **Generating the Configuration:** + After defining your profiles and configurations, you can use the `Config` class to generate the final configuration. This configuration can be saved to a YAML file. + + Example usage: + ```python + config = Config(profiles=[your_profile]) + with open("generated_config.yml", "w") as file: + yaml.dump(config.dict(), file) + ``` + + +## License + +`apphub-configurator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. \ No newline at end of file From a9f2a0d0156530002d29af64f017005a53cc435e Mon Sep 17 00:00:00 2001 From: ldonnini Date: Wed, 21 May 2025 15:44:28 +0200 Subject: [PATCH 72/86] fixed push on ghcr --- .github/workflows/.github-ci.yaml | 10 +++++----- .../src/apphub_configurator/__about__.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/.github-ci.yaml b/.github/workflows/.github-ci.yaml index 93b23db..8f32851 100644 --- a/.github/workflows/.github-ci.yaml +++ b/.github/workflows/.github-ci.yaml @@ -154,7 +154,7 @@ jobs: docker tag "${tag}" "${{ secrets.CR_REGISTRY }}"/"${{ secrets.CR_REPO }}"/"${tag}" docker push "${{ secrets.CR_REGISTRY }}"/"${{ secrets.CR_REPO }}"/"${tag}" - # Step 10: Login Docker Hub + # Step 10: Login GHCR - name: Login to GHCR uses: docker/login-action@v2 with: @@ -162,11 +162,11 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - # Step 11: Push to Docker Hub - - name: push to Docker Hub + # Step 11: Push to GHCR + - name: push to GHCR run: | tag="${docker_image_application}:${docker_image_version}" - docker tag "${tag}" "ghcr.io/EOEPCA/${{ env.docker_tag }}" - docker tag "${tag}" "ghcr.io/EOEPCA/${{ env.docker_tag_latest }}" + docker tag "${tag}" "ghcr.io/${{ env.docker_tag }}" + docker tag "${tag}" "ghcr.io/${{ env.docker_tag_latest }}" docker push "ghcr.io/EOEPCA/${{ env.docker_tag }}" docker push "ghcr.io/EOEPCA/${{ env.docker_tag_latest }}" diff --git a/apphub-configurator/src/apphub_configurator/__about__.py b/apphub-configurator/src/apphub_configurator/__about__.py index 7e37c70..9d35080 100644 --- a/apphub-configurator/src/apphub_configurator/__about__.py +++ b/apphub-configurator/src/apphub_configurator/__about__.py @@ -1,4 +1,4 @@ # SPDX-FileCopyrightText: 2025-present pmembari # # SPDX-License-Identifier: MIT -__version__ = "0.0.8" +__version__ = "0.1.0" From 45b76db189befaef5611e939aa780478dd7972c0 Mon Sep 17 00:00:00 2001 From: ldonnini Date: Wed, 21 May 2025 15:49:33 +0200 Subject: [PATCH 73/86] fixed push on ghcr --- .github/workflows/.github-ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/.github-ci.yaml b/.github/workflows/.github-ci.yaml index 8f32851..83c33ac 100644 --- a/.github/workflows/.github-ci.yaml +++ b/.github/workflows/.github-ci.yaml @@ -168,5 +168,5 @@ jobs: tag="${docker_image_application}:${docker_image_version}" docker tag "${tag}" "ghcr.io/${{ env.docker_tag }}" docker tag "${tag}" "ghcr.io/${{ env.docker_tag_latest }}" - docker push "ghcr.io/EOEPCA/${{ env.docker_tag }}" - docker push "ghcr.io/EOEPCA/${{ env.docker_tag_latest }}" + docker push "ghcr.io/${{ env.docker_tag }}" + docker push "ghcr.io/${{ env.docker_tag_latest }}" From ca768e44c3e031b55a5a37b8ec6a8514c94a5356 Mon Sep 17 00:00:00 2001 From: ldonnini Date: Wed, 21 May 2025 16:51:24 +0200 Subject: [PATCH 74/86] fixed version for configurable-http-proxy --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ab34dd0..3424d13 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ RUN microdnf update -y && \ && microdnf clean all # Installation of configurable-http-proxy via npm -RUN npm install -g configurable-http-proxy +RUN npm install -g configurable-http-proxy@4.5.3 # User creation RUN adduser \ From abe1fc088b7fb39ed4d1e72d084126596f8ae953 Mon Sep 17 00:00:00 2001 From: stripodi Date: Fri, 11 Jul 2025 11:05:59 +0200 Subject: [PATCH 75/86] feat: updated ignore list --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ed2736c..df987ba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.pyc +.pytest_cache __pycache__ values.yaml *.egg-info From 823636c5fcd0b02e6cc58c813c3b98216282ffd3 Mon Sep 17 00:00:00 2001 From: Leonardo Mizzoni Date: Mon, 14 Jul 2025 09:52:30 +0200 Subject: [PATCH 76/86] Add support for pvc annotations - bump to version 1.3.3 (#6) --- apphub-configurator/src/apphub_configurator/models.py | 2 +- application_hub_context/app_hub_context.py | 6 ++++-- application_hub_context/models.py | 1 + setup.cfg | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apphub-configurator/src/apphub_configurator/models.py b/apphub-configurator/src/apphub_configurator/models.py index 4715396..7f7aa8f 100644 --- a/apphub-configurator/src/apphub_configurator/models.py +++ b/apphub-configurator/src/apphub_configurator/models.py @@ -69,7 +69,7 @@ class Volume(BaseModel): access_modes: List[str] volume_mount: VolumeMount persist: bool - + annotations: Optional[Dict[str, str]] = None class Manifest(BaseModel): name: str diff --git a/application_hub_context/app_hub_context.py b/application_hub_context/app_hub_context.py index dc9ee4a..e60c92e 100644 --- a/application_hub_context/app_hub_context.py +++ b/application_hub_context/app_hub_context.py @@ -3,7 +3,7 @@ import yaml from abc import ABC from http import HTTPStatus -from typing import Dict, TextIO +from typing import Dict, Optional, TextIO from jinja2 import Template from kubernetes import client, config @@ -304,13 +304,14 @@ def create_pvc( access_modes, size, storage_class, + annotations: Optional[Dict[str, str]] = None ): if self.is_pvc_created(name=name): return self.core_v1_api.read_namespaced_persistent_volume_claim( name=name, namespace=self.namespace ) - metadata = client.V1ObjectMeta(name=name, namespace=self.namespace) + metadata = client.V1ObjectMeta(name=name, namespace=self.namespace, annotations=annotations) spec = client.V1PersistentVolumeClaimSpec( access_modes=access_modes, @@ -871,6 +872,7 @@ def initialise(self): access_modes=volume.access_modes, size=volume.size, storage_class=volume.storage_class, + annotations=volume.annotations ) self.spawner.log.info( f"Mounting volume {volume.name} (claim {volume.claim_name}))" diff --git a/application_hub_context/models.py b/application_hub_context/models.py index 86b52c8..e18903c 100644 --- a/application_hub_context/models.py +++ b/application_hub_context/models.py @@ -35,6 +35,7 @@ class defining a volume object: access_modes: List[str] volume_mount: VolumeMount persist: bool + annotations: Optional[Dict[str, str]] = None class Manifest(BaseModel): diff --git a/setup.cfg b/setup.cfg index 397bfd7..135c20e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,4 +1,4 @@ [metadata] name = application-context-hub -version = 1.3.2 +version = 1.3.3 From d93164bfb41044c9e7cc7b09eaa779996a152fd2 Mon Sep 17 00:00:00 2001 From: Fabrice Brito Date: Wed, 21 Jan 2026 14:07:17 +0100 Subject: [PATCH 77/86] initial update --- .github/workflows/.github-ci.yaml | 172 ------------------------------ .github/workflows/build-image.yml | 142 ++++++++++++++++++++++++ .github/workflows/package.yaml | 17 +-- .gitignore | 1 + build.yml | 2 - release.yaml | 4 + 6 files changed, 151 insertions(+), 187 deletions(-) delete mode 100644 .github/workflows/.github-ci.yaml create mode 100644 .github/workflows/build-image.yml delete mode 100644 build.yml create mode 100644 release.yaml diff --git a/.github/workflows/.github-ci.yaml b/.github/workflows/.github-ci.yaml deleted file mode 100644 index 83c33ac..0000000 --- a/.github/workflows/.github-ci.yaml +++ /dev/null @@ -1,172 +0,0 @@ -name: Build, Test, and Deploy Docker Image - -on: - push: - branches: - - develop - -jobs: - build: - runs-on: ubuntu-latest - steps: - # Step 1: Checkout repository - - uses: actions/checkout@v4 - - # Step 2: Install Trivy - - name: Install Trivy - run: | - sudo apt-get update -y - sudo apt-get install -y wget apt-transport-https gnupg lsb-release - wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | sudo apt-key add - - echo deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main | sudo tee -a /etc/apt/sources.list.d/trivy.list - sudo apt-get update -y - sudo apt-get install -y trivy - - # Step 3: Read image name - - name: Read docker name - id: yaml-docker-name - uses: jbutcher5/read-yaml@main - with: - file: 'build.yml' - key-path: '["docker_image_name"]' - - # Step 4: Read image version - - name: Read docker version - id: yaml-docker-version - uses: jbutcher5/read-yaml@main - with: - file: 'build.yml' - key-path: '["docker_image_version"]' - - # Step 5: Generate Docker tag - - name: Generate docker tag - env: - GITHUB_BRANCH: ${{ github.ref }} - docker_image_name: ${{ steps.yaml-docker-name.outputs.data }} - docker_image_version: ${{ steps.yaml-docker-version.outputs.data }} - run: | - branch_name=${GITHUB_BRANCH#refs/heads/} - echo "branch_name=${GITHUB_BRANCH#refs/heads/}" >> $GITHUB_ENV - if [[ "$branch_name" = "main" ]] - then - mType="" - else - mType="dev" - fi - echo "docker_tag=$docker_image_name:$docker_image_version" >> $GITHUB_ENV - echo "docker_tag_latest=$docker_image_name:latest" >> $GITHUB_ENV - docker_image_application=(${docker_image_name#*/}) - echo "docker_image_application=$docker_image_application" >> $GITHUB_ENV - echo "docker_image_version=$docker_image_version" >> $GITHUB_ENV - - # Step 6: Build Docker image to inspect it with Trivy - - name: Build Docker image - run: | - tag="${docker_image_application}:${docker_image_version}" - echo "${{ secrets.CR_PASSWORD }}" | docker login -u "${{ secrets.CR_USERNAME }}" --password-stdin "${{ secrets.CR_REGISTRY }}" - docker build -t "${tag}" --file Dockerfile . - - # Step 7: Save Docker image as tar.gz - - name: Save Docker Image as tar.gz - run: | - tag="${docker_image_application}:${docker_image_version}" - docker save "${tag}" -o "${docker_image_application}_${docker_image_version}.tar" - tar -czf "${docker_image_application}_${docker_image_version}.tar.gz" "${docker_image_application}_${docker_image_version}.tar" - - # Step 8: Upload Docker Image tar.gz as an artifact - - name: Upload Docker Image Artifact - uses: actions/upload-artifact@v4.6.2 - with: - name: docker-image-tar - path: ${{ env.docker_image_application }}_${{ env.docker_image_version }}.tar.gz - - - # Step 9: Scan Docker Image with Trivy - - name: Scan Docker Image with Trivy - run: | - tag="${docker_image_application}:${docker_image_version}" - trivy image --no-progress --exit-code 1 --severity HIGH,CRITICAL,UNKNOWN --format table "${tag}" - - - deploy: - needs: build - runs-on: ubuntu-latest - steps: - # Step 1: Checkout repository - - uses: actions/checkout@v4 - - # Step 2: Read image name - - name: Read docker name - id: yaml-docker-name - uses: jbutcher5/read-yaml@main - with: - file: 'build.yml' - key-path: '["docker_image_name"]' - - # Step 3: Read image version - - name: Read docker version - id: yaml-docker-version - uses: jbutcher5/read-yaml@main - with: - file: 'build.yml' - key-path: '["docker_image_version"]' - - # Step 4: Generate Docker tag - - name: Generate docker tag - env: - GITHUB_BRANCH: ${{ github.ref }} - docker_image_name: ${{ steps.yaml-docker-name.outputs.data }} - docker_image_version: ${{ steps.yaml-docker-version.outputs.data }} - run: | - branch_name=${GITHUB_BRANCH#refs/heads/} - echo "branch_name=${GITHUB_BRANCH#refs/heads/}" >> $GITHUB_ENV - echo "docker_tag=$docker_image_name:$docker_image_version" >> $GITHUB_ENV - echo "docker_tag_latest=$docker_image_name:latest" >> $GITHUB_ENV - docker_image_application=(${docker_image_name#*/}) - echo "docker_image_application=$docker_image_application" >> $GITHUB_ENV - echo "docker_image_version=$docker_image_version" >> $GITHUB_ENV - - # Step 5: Download Docker Image tar.gz Artifact - - name: Download Docker Image Artifact - uses: actions/download-artifact@v4.2.1 - with: - name: docker-image-tar - - # Step 6: Extract the Docker Image tar.gz - - name: Extract Docker Image tar.gz - run: | - tar -xzf "${docker_image_application}_${docker_image_version}.tar.gz" - - # Step 7: Load Docker Image - - name: Load Docker Image - run: | - docker load -i "${docker_image_application}_${docker_image_version}.tar" - - # Step 8: Log in to Docker Registry (use GitHub secrets for security) - - name: Login to Docker Registry - run: | - echo "${{ secrets.CR_PASSWORD }}" | docker login -u "${{ secrets.CR_USERNAME }}" --password-stdin "${{ secrets.CR_REGISTRY }}" - - # Step 9: Push Docker Image to Registry - - name: Push Docker Image to Registry - run: | - tag="${docker_image_application}:${docker_image_version}" - docker tag "${tag}" "${{ secrets.CR_REGISTRY }}"/"${{ secrets.CR_REPO }}"/"${tag}" - docker push "${{ secrets.CR_REGISTRY }}"/"${{ secrets.CR_REPO }}"/"${tag}" - - # Step 10: Login GHCR - - name: Login to GHCR - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - # Step 11: Push to GHCR - - name: push to GHCR - run: | - tag="${docker_image_application}:${docker_image_version}" - docker tag "${tag}" "ghcr.io/${{ env.docker_tag }}" - docker tag "${tag}" "ghcr.io/${{ env.docker_tag_latest }}" - docker push "ghcr.io/${{ env.docker_tag }}" - docker push "ghcr.io/${{ env.docker_tag_latest }}" diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml new file mode 100644 index 0000000..bb9a2e2 --- /dev/null +++ b/.github/workflows/build-image.yml @@ -0,0 +1,142 @@ +name: build + +on: + push: + branches: [develop, main] + tags: + - 'v*' + +jobs: + # ------------------------------------------------------------ + # Resolve release tag from ref + # ------------------------------------------------------------ + release-meta: + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.meta.outputs.tag }} + steps: + - id: meta + run: | + set -euo pipefail + if [[ "${GITHUB_REF}" == "refs/heads/develop" ]]; then + TAG="latest-dev" + elif [[ "${GITHUB_REF}" == "refs/heads/main" ]]; then + TAG="latest" + elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then + TAG="${GITHUB_REF#refs/tags/v}" + + # -------------------------------------------------- + # Validate against release.yaml + # -------------------------------------------------- + RELEASE_VERSION=$(yq -r '.image_version' release.yaml) + + if [[ -z "${RELEASE_VERSION}" || "${RELEASE_VERSION}" == "null" ]]; then + echo "image_version not found in release.yaml" + exit 1 + fi + + if [[ "${TAG}" != "${RELEASE_VERSION}" ]]; then + echo "Git tag v${TAG} does not match release.yaml version ${RELEASE_VERSION}" + exit 1 + fi + else + echo "Unsupported ref ${GITHUB_REF}" + exit 1 + fi + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + + # ------------------------------------------------------------ + # Build, scan, SBOM, push + # ------------------------------------------------------------ + build-scan-push: + needs: release-meta + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install tooling + run: | + sudo apt-get update + sudo apt-get install -y jq skopeo + curl -sL https://github.com/oras-project/oras/releases/download/v1.2.2/oras_1.2.2_linux_amd64.tar.gz | tar -xz oras + sudo mv oras /usr/local/bin/oras + + - name: Configure Docker Hub auth for Kaniko + run: | + if [[ -n "${{ secrets.dockerhub-username }}" ]]; then + mkdir -p $HOME/.docker + cat > $HOME/.docker/config.json < + --context . + --dockerfile Dockerfile + --no-push + --tar-path image.tar + + # -------------------------------------------------------- + # Trivy scan (relaxed for now) + # -------------------------------------------------------- + - name: Trivy scan (debug mode) + uses: docker://aquasec/trivy:0.50.2 + env: + TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db + with: + args: > + image + --input image.tar + --severity HIGH,CRITICAL + --exit-code 0 + --no-progress + + - name: Generate SBOM (SPDX JSON) + uses: docker://aquasec/trivy:0.50.2 + env: + TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db + with: + args: > + image + --input image.tar + --format spdx-json + --output sbom.spdx.json + + # -------------------------------------------------------- + # Push image and attach SBOM + # -------------------------------------------------------- + - name: Push image with Skopeo + id: push + run: | + set -euo pipefail + + TAG=${{ needs.release-meta.outputs.tag }} + REGISTRY=$(yq -r '.image_registry' release.yaml) + IMAGE_PREFIX=$(yq -r '.image_prefix' release.yaml) + IMAGE=$(yq -r '.image_name' release.yaml) + + IMAGE_REF="${REGISTRY}/${IMAGE_PREFIX}/${IMAGE}:${TAG}" + + skopeo copy \ + --dest-creds="${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}" \ + docker-archive:image.tar \ + docker://${IMAGE_REF} + + DIGEST=$(skopeo inspect \ + --creds="${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}" \ + docker://${IMAGE_REF} \ + | jq -r .Digest) + + echo "${REGISTRY}/${IMAGE_PREFIX}/${IMAGE}@${DIGEST}" > image.digest diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index 065bdad..a5add16 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -1,19 +1,10 @@ -name: Publish to PyPI +name: publish-to-pypi on: push: - branches: [develop, main, "patch-1" ] - paths: - - 'apphub-configurator/**' - - '.github/workflows/package.yaml' - - release: - types: [published] - pull_request: - branches: [develop, main] - paths: - - 'apphub-configurator/**' - + tags: + - 'v*' + permissions: contents: read diff --git a/.gitignore b/.gitignore index df987ba..6e1132e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ hatch .project .settings .pydevproject +.env* \ No newline at end of file diff --git a/build.yml b/build.yml deleted file mode 100644 index 9b1d84c..0000000 --- a/build.yml +++ /dev/null @@ -1,2 +0,0 @@ -docker_image_name: eoepca/application-hub -docker_image_version: 1.4.2 diff --git a/release.yaml b/release.yaml new file mode 100644 index 0000000..468e86e --- /dev/null +++ b/release.yaml @@ -0,0 +1,4 @@ +image_name: application-hub +image_prefix: eoepca +image_version: 1.4.2 +image_registry: ghcr.io \ No newline at end of file From fb3f7cbf8b2521a9b77989b8a83017a802acfade Mon Sep 17 00:00:00 2001 From: Fabrice Brito Date: Wed, 21 Jan 2026 15:04:46 +0100 Subject: [PATCH 78/86] minor fix to allow building the container --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3424d13..2c09abe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,7 +37,7 @@ RUN usermod -aG wheel jovyan && \ # Python packages installation from requirements.txt COPY requirements.txt /tmp/requirements.txt -RUN pip3 install --upgrade --no-cache-dir setuptools pip +# RUN pip3 install --upgrade --no-cache-dir setuptools pip # Specific Python dependencies installation RUN PYCURL_SSL_LIBRARY=openssl \ From 67e360f0105e2a3bcbdeed23898d8757f1053713 Mon Sep 17 00:00:00 2001 From: Fabrice Brito Date: Wed, 21 Jan 2026 15:09:21 +0100 Subject: [PATCH 79/86] updates README with container strategy --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 39cb890..077d8ce 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,29 @@ The Application pod contextualization takes as input a 'profile' and is handled - **pre-spawn and post-stop hooks** +## Container Image Strategy & Availability + +This project publishes container images to GitHub Container Registry (GHCR) following a clear and deterministic tagging strategy aligned with the Git branching and release model. + +### Image Registry + +Images are published to: + +``` +ghcr.io//application-hub +``` + +The registry owner corresponds to the GitHub repository owner (user or organization). + +Images are built using Kaniko and pushed using OCI-compliant tooling. + +### Tagging Strategy + +The image tag is derived automatically from the Git reference that triggered the build: + + +| Git reference | Image tag | Purpose | +| ---------------- | ------------ | ---------------------------------- | +| `develop` branch | `latest-dev` | Development and integration builds | +| `main` branch | `latest` | Stable branch builds | +| Git tag `vX.Y.Z` | `X.Y.Z` | Immutable release builds | \ No newline at end of file From 6c952136f0f339a58dd5581570e6747e0a6ca354 Mon Sep 17 00:00:00 2001 From: Fabrice Brito Date: Wed, 21 Jan 2026 15:22:43 +0100 Subject: [PATCH 80/86] Update Dockerfile Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2c09abe..748e650 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,7 +37,6 @@ RUN usermod -aG wheel jovyan && \ # Python packages installation from requirements.txt COPY requirements.txt /tmp/requirements.txt -# RUN pip3 install --upgrade --no-cache-dir setuptools pip # Specific Python dependencies installation RUN PYCURL_SSL_LIBRARY=openssl \ From 877466b05bb0e9e030dd4ef5e1abf7cbe1ca3bc1 Mon Sep 17 00:00:00 2001 From: Fabrice Brito Date: Wed, 21 Jan 2026 15:23:07 +0100 Subject: [PATCH 81/86] Update Dockerfile Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 748e650..df26697 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,10 @@ RUN microdnf update -y && \ postgresql-devel \ && microdnf clean all -# Installation of configurable-http-proxy via npm +# Installation of configurable-http-proxy via npm. +# NOTE: configurable-http-proxy is pinned to 4.5.3 to ensure compatibility and reproducible builds +# with the current JupyterHub/base image setup. Review and test carefully before changing this +# version, as upgrades may affect proxy behavior, compatibility, or security posture. RUN npm install -g configurable-http-proxy@4.5.3 # User creation From 26bc85ed331c2efcc1f62cc79112b979a1aaab47 Mon Sep 17 00:00:00 2001 From: Fabrice Brito Date: Wed, 21 Jan 2026 15:24:12 +0100 Subject: [PATCH 82/86] Update .github/workflows/package.yaml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/package.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index a5add16..19b512a 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -39,12 +39,12 @@ jobs: ls -la - name: Publish package distributions to PyPI (main) - if: github.ref == 'refs/heads/main' + if: startsWith(github.ref, 'refs/tags/v') uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://upload.pypi.org/legacy/ - name: Publish package distributions to PyPI (develop) - if: github.ref != 'refs/heads/main' + if: !startsWith(github.ref, 'refs/tags/v') uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://test.pypi.org/legacy/ \ No newline at end of file From a246a37ba5c868b3cd9ab6f21373b1bf9642b7c0 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:29:56 +0100 Subject: [PATCH 83/86] Fix malformed URLs in JSON schema file (#9) * Initial plan * Fix malformed URLs in config.schema.yaml Co-authored-by: fabricebrito <1178901+fabricebrito@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: fabricebrito <1178901+fabricebrito@users.noreply.github.com> --- schemas/config.schema.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/schemas/config.schema.yaml b/schemas/config.schema.yaml index b855913..f10a1cc 100644 --- a/schemas/config.schema.yaml +++ b/schemas/config.schema.yaml @@ -1,5 +1,5 @@ -$schema: "https:/json-schema.org/draft/2020-12/schema" -$id: "https:/www.terradue.com/eoap/v1/project.yaml" +$schema: "https://json-schema.org/draft/2020-12/schema" +$id: "https://www.terradue.com/eoap/v1/project.yaml" title: Config type: object properties: From c0bc6f48f75a9fbd8b77cb9a693647b46bf90933 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:31:09 +0100 Subject: [PATCH 84/86] Fix malformed URLs in JSON schema (#8) * Initial plan * Fix malformed URLs in config schema - add missing colon Co-authored-by: fabricebrito <1178901+fabricebrito@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: fabricebrito <1178901+fabricebrito@users.noreply.github.com> From 5c26e098fd848dcd7125e2c778abda01cc6810c5 Mon Sep 17 00:00:00 2001 From: Fabrice Brito Date: Wed, 21 Jan 2026 15:56:02 +0100 Subject: [PATCH 85/86] WIP on minilube configuration --- .../examples/generate-config.sh | 4 +-- .../examples/generate_config.py | 18 ++++++------ apphub-configurator/pyproject.toml | 2 +- files/hub/config.yml | 29 ++++++++----------- 4 files changed, 24 insertions(+), 29 deletions(-) diff --git a/apphub-configurator/examples/generate-config.sh b/apphub-configurator/examples/generate-config.sh index 0238b9d..ecc0894 100755 --- a/apphub-configurator/examples/generate-config.sh +++ b/apphub-configurator/examples/generate-config.sh @@ -1,4 +1,4 @@ -#exit 0 +set -x export PYTHONPATH=$PWD/`dirname $0`/. -.env-apphub-configurator/bin/python3 -m generate_config \ No newline at end of file +python3 -m generate_config \ No newline at end of file diff --git a/apphub-configurator/examples/generate_config.py b/apphub-configurator/examples/generate_config.py index 2473fa4..73086fd 100644 --- a/apphub-configurator/examples/generate_config.py +++ b/apphub-configurator/examples/generate_config.py @@ -18,7 +18,7 @@ profiles = [] workspace_volume_size = "50Gi" calrissian_volume_size = "50Gi" -image = "eoepca/pde-code-server:develop" +image = "ghcr.io/eoepca/pde-code-server:latest-dev" node_selector = {} # get the current directory @@ -139,17 +139,17 @@ init_containers=[init_container], manifests=[localstack_manifest, dask_gateway_manifest, kaniko_manifest], env_from_config_maps=["my-config"], - env_from_secrets=["my-secret", "data-by-name"], + env_from_secrets=["my-secret"], #, "data-by-name"], secret_mounts=[ SecretMount( name="aws-credentials-{{ spawner.user.name }}", mount_path="/workspace/.aws" ), - SecretMount(name="data-by-name", mount_path="/workspace/.data-by-name"), - SecretMount( - name="eoepca-plus-secret-ro", - mount_path="/workspace/.docker/config.json", - sub_path=".dockerconfigjson", - ), + # SecretMount(name="data-by-name", mount_path="/workspace/.data-by-name"), + # SecretMount( + # name="eoepca-plus-secret-ro", + # mount_path="/workspace/.docker/config.json", + # sub_path=".dockerconfigjson", + # ), ], image_pull_secrets=[ImagePullSecret(name="eoepca-plus-secret-ro")], ) @@ -161,7 +161,7 @@ with open(config_file_path, "w") as file: - yaml.dump(config.model_dump(), file, width=200) + yaml.dump(config.dict(), file, width=200) logger.success( f"Config file generated successfully at {config_file_path}" diff --git a/apphub-configurator/pyproject.toml b/apphub-configurator/pyproject.toml index e6d1082..123a89f 100644 --- a/apphub-configurator/pyproject.toml +++ b/apphub-configurator/pyproject.toml @@ -46,7 +46,7 @@ path = "hatch/envs/apphub_configurator" path = "hatch/envs/apphub_configurator" dependencies = ["pytest", "pytest-cov", - "pydantic", + "pydantic<2.0.0", "pyyaml", "ipykernel", "jupyter", diff --git a/files/hub/config.yml b/files/hub/config.yml index 4488ac1..a1ab5de 100644 --- a/files/hub/config.yml +++ b/files/hub/config.yml @@ -75,7 +75,7 @@ profiles: cpu_limit: 2 extra_resource_guarantees: {} extra_resource_limits: {} - image: eoepca/pde-code-server:develop + image: ghcr.io/eoepca/pde-code-server:latest-dev mem_guarantee: 4G mem_limit: 6G slug: profile_1 @@ -83,7 +83,6 @@ profiles: - my-config env_from_secrets: - my-secret - - data-by-name groups: - group-a - group-b @@ -97,7 +96,7 @@ profiles: - sh - -c - sh /opt/init/.init.sh - image: eoepca/pde-code-server:develop + image: ghcr.io/eoepca/pde-code-server:latest-dev name: init-file-on-volume volume_mounts: - mount_path: /workspace @@ -563,7 +562,7 @@ profiles: kind: ExternalSecret metadata: name: data-by-name - namespace: '{namespace}-{{ spawner.user.name }}' + namespace: '{{ namespace }}' spec: data: - remoteRef: @@ -581,7 +580,7 @@ profiles: kind: ExternalSecret metadata: name: eoepca-plus-secret-ro - namespace: '{namespace}-{{ spawner.user.name }}' + namespace: '{{ namespace }}' spec: data: - remoteRef: @@ -603,14 +602,14 @@ profiles: kind: Release metadata: name: dask-gw-jupyter-{{ spawner.user.name }} - namespace: '{namespace}-{{ spawner.user.name }}' + namespace: '{{ namespace }}' spec: forProvider: chart: name: dask-gateway repository: https://helm.dask.org version: 2024.1.0 - namespace: '{namespace}-{{ spawner.user.name }}' + namespace: '{{ namespace }}' values: gateway: backend: @@ -687,7 +686,7 @@ profiles: kind: Pod metadata: name: kaniko-build - namespace: '{namespace}-{{ spawner.user.name }}' + namespace: '{{ namespace }}' spec: containers: - command: @@ -716,7 +715,7 @@ profiles: kind: Role metadata: name: pod-exec - namespace: '{namespace}-{{ spawner.user.name }}' + namespace: '{{ namespace }}' rules: - apiGroups: - '' @@ -730,7 +729,7 @@ profiles: kind: RoleBinding metadata: name: bind-default-to-opd-exec - namespace: '{namespace}-{{ spawner.user.name }}' + namespace: '{{ namespace }}' roleRef: apiGroup: rbac.authorization.k8s.io kind: Role @@ -738,7 +737,7 @@ profiles: subjects: - kind: ServiceAccount name: default - namespace: '{namespace}-{{ spawner.user.name }}' + namespace: '{{ namespace }}' key: kaniko name: kaniko persist: false @@ -755,15 +754,10 @@ profiles: - mount_path: /workspace/.aws name: aws-credentials-{{ spawner.user.name }} sub_path: null - - mount_path: /workspace/.data-by-name - name: data-by-name - sub_path: null - - mount_path: /workspace/.docker/config.json - name: eoepca-plus-secret-ro - sub_path: .dockerconfigjson volumes: - access_modes: - ReadWriteMany + annotations: null claim_name: calrissian-claim name: calrissian-volume persist: false @@ -774,6 +768,7 @@ profiles: name: calrissian-volume - access_modes: - ReadWriteOnce + annotations: null claim_name: workspace-claim name: workspace-volume persist: true From df9b441620743df92da65c63dc51e9730aea3b07 Mon Sep 17 00:00:00 2001 From: Fabrice Brito Date: Wed, 21 Jan 2026 16:54:28 +0100 Subject: [PATCH 86/86] removes dev container, updates skaffold profiles --- .devcontainer/Dockerfile | 37 -------------- .devcontainer/devcontainer.json | 20 -------- .devcontainer/environment.yml | 22 -------- .github/workflows/package.yaml | 9 +--- .vscode/settings.json | 15 ------ skaffold.yaml | 89 ++++++++++++++++++++------------- 6 files changed, 56 insertions(+), 136 deletions(-) delete mode 100644 .devcontainer/Dockerfile delete mode 100644 .devcontainer/devcontainer.json delete mode 100644 .devcontainer/environment.yml delete mode 100644 .vscode/settings.json diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 69d9ed7..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -FROM mambaorg/micromamba - -USER root - -ENV USERNAME=mambauser - -ENV PATH=/opt/conda/envs/env_context/bin:$PATH - -RUN apt-get update -y \ - && apt-get upgrade -y \ - && apt-get install -y --no-install-recommends \ - ca-certificates \ - curl sudo git nodejs wget curl git-flow vim gpg - -RUN echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ - && chmod 0440 /etc/sudoers.d/$USERNAME - -RUN chown -R mambauser:1000 /opt/conda/ - -RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" && \ - install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl - - -RUN curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null && \ - apt-get install apt-transport-https --yes - -RUN echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list && \ - apt-get update && \ - apt-get install helm - -USER mambauser - -ADD .devcontainer/environment.yml /tmp/environment.yml - -RUN micromamba create -f /tmp/environment.yml - -WORKDIR /home/mambauser diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 731bb8d..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "application-hub-context", - - "build": { - "context": "..", - "dockerfile": "./Dockerfile"}, - "settings": { - "python.pythonPath": "/opt/conda/envs/env_context/bin/python"}, - "extensions": [ - "ms-python.python", - "ms-kubernetes-tools.vscode-kubernetes-tools", - "redhat.vscode-yaml" - ], - "remoteEnv": { - "PATH": "${containerEnv:PATH}:/opt/conda/envs/env_context/bin"}, - "postCreateCommand": "/opt/conda/envs/env_context/bin/pre-commit install", - "mounts": [ - "source=${localEnv:HOME}/.kube,target=/home/mambauser/.kube,type=bind" - ] -} diff --git a/.devcontainer/environment.yml b/.devcontainer/environment.yml deleted file mode 100644 index e08e580..0000000 --- a/.devcontainer/environment.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: env_context -channels: - - terradue - - conda-forge -dependencies: - - click - - black - - pre-commit - - isort - - flake8 - - autoflake - - pylint - - nose2 - - cwltool - - pyyaml - - pip: - - kubernetes - - loguru - - munch - - addict - - pydantic>=2 - - ipykernel diff --git a/.github/workflows/package.yaml b/.github/workflows/package.yaml index 19b512a..d5bc319 100644 --- a/.github/workflows/package.yaml +++ b/.github/workflows/package.yaml @@ -33,18 +33,11 @@ jobs: rm -rf setup* cd apphub-configurator/ hatch build - ls -la mv -f dist/ ../ cd - - ls -la - + - name: Publish package distributions to PyPI (main) if: startsWith(github.ref, 'refs/tags/v') uses: pypa/gh-action-pypi-publish@release/v1 with: repository-url: https://upload.pypi.org/legacy/ - - name: Publish package distributions to PyPI (develop) - if: !startsWith(github.ref, 'refs/tags/v') - uses: pypa/gh-action-pypi-publish@release/v1 - with: - repository-url: https://test.pypi.org/legacy/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 72c63ca..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "python.testing.pytestArgs": [ - "tests" - ], - "python.testing.unittestEnabled": true, - "python.testing.pytestEnabled": false, - "python.testing.autoTestDiscoverOnSaveEnabled": true, - "python.testing.unittestArgs": [ - "-v", - "-s", - "./tests", - "-p", - "test*.py" - ], -} diff --git a/skaffold.yaml b/skaffold.yaml index 13cf1ba..a48ccc3 100644 --- a/skaffold.yaml +++ b/skaffold.yaml @@ -4,40 +4,39 @@ build: artifacts: - image: hubimage -profiles: - - name: minikube - deploy: - helm: - releases: - - name: jupyterhub - remoteChart: eoepca/application-hub - namespace: jupyter - version: 4.0.1 - createNamespace: true - valuesFiles: [] - setValueTemplates: - jupyterhub.hub.image.name: "{{.IMAGE_REPO_hubimage}}" - jupyterhub.hub.image.tag: "{{.IMAGE_TAG_hubimage}}@{{.IMAGE_DIGEST_hubimage}}" - setValues: - jupyterhub.hub.image.pullSecrets: [] - jupyterhub.hub.db.pvc.storageClassName: standard - jupyterhub.hub.extraEnv.STORAGE_CLASS: standard - jupyterhub.proxy.secretToken: "032d5bfe141a7eab86d57587879b33c5d168617cacb339823d7f47fe2933f880" - jupyterhub.hub.extraEnv.APP_HUB_NAMESPACE: jupyter - jupyterhub.hub.networkPolicy.enabled: false - setFiles: - configYml: ./files/hub/config.yml - jupyterConfig: ./files/hub/jupyterhub_config.py - hooks: - before: - - host: - command: ["sh", "-c", "apphub-configurator/examples/generate-config.sh"] - os: [darwin, linux] - manifests: - rawYaml: - - sk-k8s/cluster-role-binding.yaml - - sk-k8s/script.yaml - - sk-k8s/job.yaml + +deploy: + helm: + releases: + - name: jupyterhub + remoteChart: eoepca/application-hub + namespace: jupyter + version: 4.0.1 + createNamespace: true + valuesFiles: [] + setValueTemplates: + jupyterhub.hub.image.name: "{{.IMAGE_REPO_hubimage}}" + jupyterhub.hub.image.tag: "{{.IMAGE_TAG_hubimage}}@{{.IMAGE_DIGEST_hubimage}}" + setValues: + jupyterhub.hub.image.pullSecrets: [] + jupyterhub.hub.db.pvc.storageClassName: standard + jupyterhub.hub.extraEnv.STORAGE_CLASS: standard + jupyterhub.proxy.secretToken: "032d5bfe141a7eab86d57587879b33c5d168617cacb339823d7f47fe2933f880" + jupyterhub.hub.extraEnv.APP_HUB_NAMESPACE: jupyter + jupyterhub.hub.networkPolicy.enabled: false + setFiles: + configYml: ./files/hub/config.yml + jupyterConfig: ./files/hub/jupyterhub_config.py + hooks: + before: + - host: + command: ["sh", "-c", "apphub-configurator/examples/generate-config.sh"] + os: [darwin, linux] +manifests: + rawYaml: + - sk-k8s/cluster-role-binding.yaml + - sk-k8s/script.yaml + - sk-k8s/job.yaml portForward: - resourceType: service @@ -45,3 +44,25 @@ portForward: namespace: jupyter # Optional, if you are using a specific namespace port: 80 # Target port on the pod localPort: 8000 # Local port on your machine + +profiles: + + - name: develop + + patches: + - op: add + path: /deploy/helm/releases/0/setValueTemplates/jupyterhub.hub.image.name + value: ghcr.io/eoepca/application-hub + - op: add + path: /deploy/helm/releases/0/setValueTemplates/jupyterhub.hub.image.tag + value: latest-dev + + - name: main + + patches: + - op: add + path: /deploy/helm/releases/0/setValueTemplates/jupyterhub.hub.image.name + value: ghcr.io/eoepca/application-hub + - op: add + path: /deploy/helm/releases/0/setValueTemplates/jupyterhub.hub.image.tag + value: latest \ No newline at end of file