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/.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/.github-ci.yaml b/.github/workflows/.github-ci.yaml deleted file mode 100644 index 81eb52a..0000000 --- a/.github/workflows/.github-ci.yaml +++ /dev/null @@ -1,171 +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@v3 - 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@v3 - 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 Docker Hub - - name: Login to Docker Hub - uses: docker/login-action@v2 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - # 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 }}" \ No newline at end of file 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/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/.github/workflows/package.yaml b/.github/workflows/package.yaml new file mode 100644 index 0000000..d5bc319 --- /dev/null +++ b/.github/workflows/package.yaml @@ -0,0 +1,43 @@ +name: publish-to-pypi + +on: + push: + tags: + - 'v*' + + +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 + mv -f dist/ ../ + cd - + + - 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/ diff --git a/.gitignore b/.gitignore index ec0db41..6e1132e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,15 @@ *.pyc +.pytest_cache __pycache__ values.yaml *.egg-info build _README.md dist -.env-config-generator \ No newline at end of file +.env-apphub-configurator +hatch + +.project +.settings +.pydevproject +.env* \ 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/Dockerfile b/Dockerfile index 598c405..df26697 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,10 +17,14 @@ RUN microdnf update -y && \ gcc \ libcurl-devel \ openssl-devel \ + postgresql-devel \ && microdnf clean all -# Installation of configurable-http-proxy via npm -RUN npm install -g configurable-http-proxy +# 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 RUN adduser \ @@ -36,11 +40,12 @@ 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 \ - 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 @@ -55,4 +60,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"] diff --git a/README.md b/README.md index b826c57..077d8ce 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,53 @@ # ApplicationHub Context -[![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) +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** + + +## 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 diff --git a/Taskfile.yaml b/Taskfile.yaml new file mode 100644 index 0000000..63234ac --- /dev/null +++ b/Taskfile.yaml @@ -0,0 +1,53 @@ +version: '3' + +tasks: + + # schema + + conditional-pip-install: + internal: true + status: + - command -v {{.COMMAND}} + cmds: + - pip install {{.COMMAND}} + + 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: + - check-jsonschema --schemafile ./schemas/config.schema.yaml {{.CONFIG}} + + 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 + 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 \ + --snake-case-field \ + --output ./application_hub_context/models.py diff --git a/config-generator/apphub-configurator/LICENSE.txt b/apphub-configurator/LICENSE.txt similarity index 100% rename from config-generator/apphub-configurator/LICENSE.txt rename to apphub-configurator/LICENSE.txt 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 94% rename from config-generator/config-maps/bash-rc rename to apphub-configurator/config-maps/bash-rc index 3f643a6..5098e90 100644 --- a/config-generator/config-maps/bash-rc +++ b/apphub-configurator/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 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..cd1db94 --- /dev/null +++ b/apphub-configurator/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/apphub-configurator/examples/generate-config.sh b/apphub-configurator/examples/generate-config.sh new file mode 100755 index 0000000..ecc0894 --- /dev/null +++ b/apphub-configurator/examples/generate-config.sh @@ -0,0 +1,4 @@ +set -x +export PYTHONPATH=$PWD/`dirname $0`/. + +python3 -m generate_config \ No newline at end of file diff --git a/config-generator/apphub-configurator/examples/generate_config.py b/apphub-configurator/examples/generate_config.py similarity index 71% rename from config-generator/apphub-configurator/examples/generate_config.py rename to apphub-configurator/examples/generate_config.py index 9987455..73086fd 100644 --- a/config-generator/apphub-configurator/examples/generate_config.py +++ b/apphub-configurator/examples/generate_config.py @@ -1,5 +1,6 @@ from apphub_configurator.models import * import yaml +from pathlib import Path import os from loguru import logger from apphub_configurator.helpers import ( @@ -10,40 +11,43 @@ ) 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" +image = "ghcr.io/eoepca/pde-code-server:latest-dev" node_selector = {} # get the current directory -current_dir = os.path.dirname(os.path.realpath(__file__)) +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=os.path.join(current_dir, "manifests/manifest.yaml"), + 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=os.path.join(current_dir, "manifests/dask-gateway.yaml"), + 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=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,27 +69,28 @@ 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=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(parent_dir, "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(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", + mount_path="/workspace", ) profile_1 = Profile( @@ -134,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")], ) @@ -152,6 +157,12 @@ 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: + +with open(config_file_path, "w") as file: yaml.dump(config.dict(), 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 86% rename from config-generator/manifests/dask-gateway.yaml rename to apphub-configurator/manifests/dask-gateway.yaml index c760862..53c8697 100644 --- a/config-generator/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/config-generator/manifests/kaniko.yaml b/apphub-configurator/manifests/kaniko.yaml similarity index 86% rename from config-generator/manifests/kaniko.yaml rename to apphub-configurator/manifests/kaniko.yaml index e7280b9..f86df21 100644 --- a/config-generator/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/config-generator/manifests/manifest.yaml b/apphub-configurator/manifests/manifest.yaml similarity index 98% rename from config-generator/manifests/manifest.yaml rename to apphub-configurator/manifests/manifest.yaml index 829b1f7..0e1b171 100644 --- a/config-generator/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/config-generator/apphub-configurator/pyproject.toml b/apphub-configurator/pyproject.toml similarity index 62% rename from config-generator/apphub-configurator/pyproject.toml rename to apphub-configurator/pyproject.toml index 3cbea85..123a89f 100644 --- a/config-generator/apphub-configurator/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/README.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", @@ -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/apphub-configurator/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/apphub-configurator/apphub-configurator" +Examples = "https://github.com/EOEPCA/application-hub-context/apphub-configurator/examples" [tool.hatch.version] path = "src/apphub_configurator/__about__.py" @@ -37,9 +37,25 @@ allow-direct-references = true [tool.hatch.build] packages = ["src/apphub_configurator"] +examples = ["examples"] + [tool.hatch.envs.default] -dependencies = ["pytest", "pytest-cov"] +path = "hatch/envs/apphub_configurator" +[tool.hatch.envs.prod] +path = "hatch/envs/apphub_configurator" +dependencies = ["pytest", + "pytest-cov", + "pydantic<2.0.0", + "pyyaml", + "ipykernel", + "jupyter", + "loguru", + "click", + ] + +[[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/apphub-configurator/src/apphub_configurator/__about__.py similarity index 83% rename from config-generator/apphub-configurator/src/apphub_configurator/__about__.py rename to apphub-configurator/src/apphub_configurator/__about__.py index 06f57eb..9d35080 100644 --- a/config-generator/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.1.0" diff --git a/config-generator/apphub-configurator/src/apphub_configurator/__init__.py b/apphub-configurator/src/apphub_configurator/__init__.py similarity index 100% rename from config-generator/apphub-configurator/src/apphub_configurator/__init__.py rename to apphub-configurator/src/apphub_configurator/__init__.py diff --git a/config-generator/apphub-configurator/src/apphub_configurator/helpers.py b/apphub-configurator/src/apphub_configurator/helpers.py similarity index 100% rename from config-generator/apphub-configurator/src/apphub_configurator/helpers.py rename to apphub-configurator/src/apphub_configurator/helpers.py diff --git a/config-generator/models.py b/apphub-configurator/src/apphub_configurator/models.py similarity index 98% rename from config-generator/models.py rename to apphub-configurator/src/apphub_configurator/models.py index 4715396..7f7aa8f 100644 --- a/config-generator/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/config-generator/apphub-configurator/tests/__init__.py b/apphub-configurator/tests/__init__.py similarity index 100% rename from config-generator/apphub-configurator/tests/__init__.py rename to apphub-configurator/tests/__init__.py diff --git a/application_hub_context/app_hub_context.py b/application_hub_context/app_hub_context.py index 946a02a..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 @@ -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 @@ -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, @@ -546,7 +547,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')}") @@ -587,7 +589,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( @@ -870,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/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 deleted file mode 100644 index 5e78c85..0000000 --- a/build.yml +++ /dev/null @@ -1,2 +0,0 @@ -docker_image_name: eoepca/application-hub -docker_image_version: 1.4.0 diff --git a/config-generator/README.md b/config-generator/README.md deleted file mode 100644 index 8a4a3c1..0000000 --- a/config-generator/README.md +++ /dev/null @@ -1,5 +0,0 @@ -## 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 diff --git a/config-generator/apphub-configurator/examples/config-generator-eoepca-demo.ipynb b/config-generator/apphub-configurator/examples/config-generator-eoepca-demo.ipynb deleted file mode 100644 index d68626b..0000000 --- a/config-generator/apphub-configurator/examples/config-generator-eoepca-demo.ipynb +++ /dev/null @@ -1,707 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import yaml\n", - "from apphub_configurator.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\"" - ] - }, - { - "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", - "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": [ - "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={\"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", - "\n", - "with open(\"../eoepca-demo/config.yml\", \"w\") 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/apphub-configurator/examples/config-generator.ipynb b/config-generator/apphub-configurator/examples/config-generator.ipynb deleted file mode 100644 index 095a292..0000000 --- a/config-generator/apphub-configurator/examples/config-generator.ipynb +++ /dev/null @@ -1,589 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!pip install apphub-configurator" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import yaml\n", - "from apphub_configurator.models import *\n", - "import os" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "with open(\"manifests/manifest.yaml\", \"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": null, - "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": null, - "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": null, - "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": null, - "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": null, - "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": null, - "metadata": {}, - "outputs": [], - "source": [ - "profiles = []" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Coder" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "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": null, - "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": null, - "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": null, - "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": null, - "metadata": {}, - "outputs": [], - "source": [ - "image_pull_secret = ImagePullSecret(\n", - " name=\"cr-config\",\n", - " persist=False,\n", - " data=\"\",\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "with open(\"manifests/manifest.yaml\", \"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": null, - "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": null, - "metadata": {}, - "outputs": [], - "source": [ - "config = Config(profiles=profiles)\n", - "\n", - "with open(\"../files/hub/config.yml\", \"w\") as file:\n", - " yaml.dump(config.dict(), file)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "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": "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/config-generator/apphub-configurator/examples/generate-config.sh b/config-generator/apphub-configurator/examples/generate-config.sh deleted file mode 100755 index 279e056..0000000 --- a/config-generator/apphub-configurator/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/config-generator/apphub-configurator/src/apphub_configurator/models.py b/config-generator/apphub-configurator/src/apphub_configurator/models.py deleted file mode 100644 index 4715396..0000000 --- a/config-generator/apphub-configurator/src/apphub_configurator/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/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/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/generate_config.py b/config-generator/generate_config.py deleted file mode 100644 index 4847780..0000000 --- a/config-generator/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/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/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 diff --git a/config-generator/apphub-configurator/README.md b/docs/README.md similarity index 98% rename from config-generator/apphub-configurator/README.md rename to docs/README.md index 5f38ded..3214d58 100644 --- a/config-generator/apphub-configurator/README.md +++ b/docs/README.md @@ -119,4 +119,4 @@ Here is an overview of the functions and how to use them to generate configurati ## License -`apphub-configurator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. +`apphub-configurator` is distributed under the terms of the [MIT](https://spdx.org/licenses/MIT.html) license. \ No newline at end of file diff --git a/docs/configuration.md b/docs/configuration.md index 788cc52..7224018 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,246 +1,101 @@ # 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 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). + +- **`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](./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..0af261a --- /dev/null +++ b/docs/hands-on.md @@ -0,0 +1,6 @@ +# Hands-on + +- Config generator: +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 diff --git a/docs/index.md b/docs/index.md index bbccec4..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 @@ -55,21 +68,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 +111,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() 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/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: 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/files/hub/config.yml b/files/hub/config.yml index 9d2021b..a1ab5de 100644 --- a/files/hub/config.yml +++ b/files/hub/config.yml @@ -58,7 +58,7 @@ 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\"" + \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 @@ -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,10 +96,10 @@ 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: /calrissian + - mount_path: /workspace name: workspace-volume - mount_path: /opt/init/.init.sh name: init @@ -563,7 +562,7 @@ profiles: kind: ExternalSecret metadata: name: data-by-name - namespace: jupyter-{{ spawner.user.name }} + namespace: '{{ namespace }}' spec: data: - remoteRef: @@ -581,7 +580,7 @@ profiles: kind: ExternalSecret metadata: name: eoepca-plus-secret-ro - namespace: jupyter-{{ spawner.user.name }} + namespace: '{{ namespace }}' spec: data: - remoteRef: @@ -603,14 +602,14 @@ profiles: kind: Release metadata: name: dask-gw-jupyter-{{ spawner.user.name }} - namespace: jupyter-{{ spawner.user.name }} + namespace: '{{ namespace }}' spec: forProvider: chart: name: dask-gateway repository: https://helm.dask.org version: 2024.1.0 - namespace: jupyter-{{ spawner.user.name }} + namespace: '{{ namespace }}' values: gateway: backend: @@ -628,14 +627,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 +643,6 @@ profiles: kind: Role metadata: name: access-services - namespace: jupyter-{{ spawner.user.name }} rules: - apiGroups: - '' @@ -673,7 +671,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 +678,6 @@ profiles: subjects: - kind: ServiceAccount name: default - namespace: jupyter-{{ spawner.user.name }} key: dask-gateway name: dask-gateway persist: false @@ -690,7 +686,7 @@ profiles: kind: Pod metadata: name: kaniko-build - namespace: jupyter-{{ spawner.user.name }} + namespace: '{{ namespace }}' spec: containers: - command: @@ -719,7 +715,7 @@ profiles: kind: Role metadata: name: pod-exec - namespace: jupyter-{{ spawner.user.name }} + namespace: '{{ namespace }}' rules: - apiGroups: - '' @@ -733,7 +729,7 @@ profiles: kind: RoleBinding metadata: name: bind-default-to-opd-exec - namespace: jupyter-{{ spawner.user.name }} + namespace: '{{ namespace }}' roleRef: apiGroup: rbac.authorization.k8s.io kind: Role @@ -741,7 +737,7 @@ profiles: subjects: - kind: ServiceAccount name: default - namespace: jupyter-{{ spawner.user.name }} + namespace: '{{ namespace }}' key: kaniko name: kaniko persist: false @@ -758,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 @@ -777,6 +768,7 @@ profiles: name: calrissian-volume - access_modes: - ReadWriteOnce + annotations: null claim_name: workspace-claim name: workspace-volume persist: true diff --git a/mkdocs.yml b/mkdocs.yml index ee86f92..2483cfc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -26,3 +26,4 @@ nav: - Kubernetes: 'k8s.md' - Configuration: 'configuration.md' - JupyterHub API: 'jupyterhub-api.md' + - Hands-on: 'hands-on.md' 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 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 diff --git a/schemas/config.schema.yaml b/schemas/config.schema.yaml new file mode 100644 index 0000000..f10a1cc --- /dev/null +++ b/schemas/config.schema.yaml @@ -0,0 +1,341 @@ +$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 + default: {} + 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 + default: [""] + 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 + claim_name: + 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 + mountPath: + type: string + subPath: + type: string + required: + - name + - mount_path diff --git a/setup.cfg b/setup.cfg index 03d2509..135c20e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,4 @@ [metadata] name = application-context-hub -version = 1.3.0 +version = 1.3.3 + diff --git a/skaffold.yaml b/skaffold.yaml index 54d1aed..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", "config-generator/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