From f0f9d3dec3b07284adec7fd6cb241a5d1f52daa5 Mon Sep 17 00:00:00 2001 From: IsaiahStapleton Date: Mon, 18 May 2026 14:38:36 -0400 Subject: [PATCH] Initial refactoring of repo Refactoring repo to pull in stuff from BU-RHOAI repo. The tooling that is used to actually deploy and run courses in RHOAI. Signed-off-by: IsaiahStapleton --- .github/workflows/Container_Test.yaml | 193 ++- .github/workflows/assign-class-label.yaml | 43 + .github/workflows/precommit.yaml | 8 + .github/workflows/validate-manifests.yaml | 8 + .gitignore | 165 +++ .ope/README.md | 2 +- .pre-commit-config.yaml | 26 + .yamllint.yaml | 13 + README.md | 85 +- books/contributor_guide/Makefile | 2 +- books/contributor_guide/fixlinks.sh | 16 +- books/contributor_guide/ghp-url.sh | 2 +- books/test_book/fixlinks.sh | 16 +- books/test_book/ghp-url.sh | 2 +- books/user_guide/Makefile | 2 +- books/user_guide/fixlinks.sh | 16 +- books/user_guide/ghp-url.sh | 2 +- containers/README.md | 20 + containers/assign-class-label/Dockerfile | 13 + containers/assign-class-label/models.py | 37 + containers/assign-class-label/mutate.py | 165 +++ .../assign-class-label/requirements.txt | 5 + .../assign-class-label/test-requirements.txt | 2 + .../assign-class-label/tests/test_mutate.py | 243 ++++ containers/assign-class-label/wsgi.py | 6 + .../.gitignore | 1 - .../Makefile | 12 +- .../README.md | 0 .../base/Dockerfile | 3 +- .../base/base_image | 1 - .../base/base_registry | 1 - .../default-class-container/base/base_tag | 1 + .../base/checksum | 1 - .../base/customization_name | 1 - .../base/distro_pkgs | 1 - .../base/jupyter_disable_exts | 1 - .../base/jupyter_enable_exts | 1 - .../base/mkversions | 0 .../base/ope_book | 0 .../base/ope_book_registry | 0 .../base/ope_book_user | 0 .../base/ope_gid | 0 .../base/ope_group | 0 .../base/ope_uid | 0 .../default-class-container/base/pip_pkgs | 1 + .../base/python_pkgs | 2 +- .../base/python_prereqs | 2 +- .../base/settings/overrides.json | 1050 ++++++++++++++++ .../base/start-notebook.d/ope-sartup.sh | 80 ++ .../requirements/requirements.txt | 7 +- containers/default/base/base_tag | 1 - containers/default/base/pip_pkgs | 1 - .../default/base/settings/overrides.json | 1050 ---------------- .../base/start-notebook.d/ope-sartup.sh | 80 -- containers/group-sync/Dockerfile | 14 + containers/group-sync/group-sync.py | 54 + containers/group-sync/requirements.txt | 1 + containers/testing/.gitignore | 1 - containers/testing/Makefile | 10 +- containers/testing/README.md | 6 +- containers/testing/base/Dockerfile | 1 - containers/testing/base/base_image | 1 - containers/testing/base/base_registry | 1 - containers/testing/base/base_tag | 2 +- containers/testing/base/checksum | 1 - containers/testing/base/customization_name | 1 - containers/testing/base/distro_pkgs | 2 +- containers/testing/base/jupyter_disable_exts | 1 - containers/testing/base/jupyter_enable_exts | 1 - containers/testing/base/mkversions | 3 +- containers/testing/base/ope_book | 2 +- containers/testing/base/ope_book_registry | 2 +- containers/testing/base/ope_book_user | 2 +- containers/testing/base/ope_gid | 2 +- containers/testing/base/ope_group | 2 +- containers/testing/base/ope_registry_user | 2 +- containers/testing/base/pip_pkgs | 2 +- containers/testing/base/python_pkgs | 2 +- containers/testing/base/python_prereqs | 2 +- .../testing/base/settings/overrides.json | 1092 ++++++++--------- .../base/start-notebook.d/ope-sartup.sh | 88 +- containers/testing/tests/Makefile | 2 - containers/testing/tests/requirements.txt | 2 +- containers/testing/tests/rise_test.py | 2 - containers/testing/tests/screenshots_diff.py | 2 - containers/testing/tests/versions.txt | 2 +- content/README.md | 2 +- content/contributor_guide/rhoai_ta_access.md | 67 + content/contributor_guide_config.yml | 30 +- content/contributor_guide_intro.md | 4 +- content/contributor_guide_toc.yml | 23 +- content/css/ln.css | 18 +- content/examples/README.md | 2 - content/examples/book/README.md | 1 - content/examples/book/config.yml | 30 +- content/examples/book/intro.md | 6 +- content/examples/css/ln.css | 18 +- content/examples/images/src/README.md | 7 +- content/examples/images/src/logo.drawio | 2 +- content/examples/python/common.py | 274 ++--- content/examples/python/ln_preamble.py | 32 +- content/examples/src/README.md | 3 +- content/images/imported_slides/save1.html | 3 +- content/images/src/README.md | 7 +- content/images/src/logo.drawio | 2 +- content/python/common.py | 8 +- content/python/ln_preamble.py | 32 +- content/src/README.md | 3 +- content/test_book_config.yml | 26 +- content/test_book_intro.md | 2 +- content/test_book_toc.yml | 25 +- content/user_guide_config.yml | 30 +- content/user_guide_intro.md | 2 +- content/user_guide_toc.yml | 59 +- deployment/README.md | 291 +++++ .../cronjobs/group-sync/clusterrole.yaml | 15 + .../group-sync/clusterrolebinding.yaml | 12 + deployment/cronjobs/group-sync/cronjob.yaml | 28 + .../cronjobs/group-sync/kustomization.yaml | 10 + .../cronjobs/group-sync/rolebinding.yaml | 11 + .../cronjobs/group-sync/serviceaccount.yaml | 4 + .../multiple-ns-group-sync/clusterrole.yaml | 31 + .../clusterrolebinding.yaml | 11 + .../multiple-ns-group-sync/cronjob.yaml | 109 ++ .../multiple-ns-group-sync/kustomization.yaml | 9 + .../multiple-ns-group-sync/rhods-rb.yaml | 12 + .../serviceaccount.yaml | 4 + .../cronjobs/nb-culler/clusterrole.yaml | 48 + .../nb-culler/clusterrolebinding.yaml | 11 + deployment/cronjobs/nb-culler/cronjob.yaml | 177 +++ .../cronjobs/nb-culler/kustomization.yaml | 9 + .../cronjobs/nb-culler/rolebinding.yaml | 11 + .../cronjobs/nb-culler/serviceaccount.yaml | 4 + deployment/gpu-class/cleanup.sh | 7 + deployment/gpu-class/clusterrole.yaml | 26 + deployment/gpu-class/clusterrolebinding.yaml | 41 + deployment/gpu-class/gpu-class-setup.sh | 91 ++ deployment/gpu-class/localqueue.yaml | 29 + deployment/gpu-class/notebook_resource.yaml | 210 ++++ deployment/gpu-class/rbac_template.yaml | 134 ++ .../assign-class-label/certificate.yaml | 12 + .../assign-class-label/deployment.yaml | 40 + .../webhooks/assign-class-label/issuer.yaml | 6 + .../assign-class-label/kustomization.yaml | 15 + .../webhooks/assign-class-label/role.yaml | 8 + .../assign-class-label/rolebinding.yaml | 12 + .../webhooks/assign-class-label/service.yaml | 10 + .../assign-class-label/serviceaccount.yaml | 5 + .../assign-class-label/webhook-config.yaml | 32 + scripts/get_url.py | 31 + 150 files changed, 4627 insertions(+), 2305 deletions(-) create mode 100644 .github/workflows/assign-class-label.yaml create mode 100644 .github/workflows/precommit.yaml create mode 100644 .github/workflows/validate-manifests.yaml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 .yamllint.yaml create mode 100644 containers/assign-class-label/Dockerfile create mode 100644 containers/assign-class-label/models.py create mode 100644 containers/assign-class-label/mutate.py create mode 100644 containers/assign-class-label/requirements.txt create mode 100644 containers/assign-class-label/test-requirements.txt create mode 100644 containers/assign-class-label/tests/test_mutate.py create mode 100644 containers/assign-class-label/wsgi.py rename containers/{default => default-class-container}/.gitignore (75%) rename containers/{default => default-class-container}/Makefile (97%) rename containers/{default => default-class-container}/README.md (100%) rename containers/{default => default-class-container}/base/Dockerfile (99%) rename containers/{default => default-class-container}/base/base_image (96%) rename containers/{default => default-class-container}/base/base_registry (90%) create mode 100644 containers/default-class-container/base/base_tag rename containers/{default => default-class-container}/base/checksum (98%) rename containers/{default => default-class-container}/base/customization_name (93%) rename containers/{default => default-class-container}/base/distro_pkgs (99%) rename containers/{default => default-class-container}/base/jupyter_disable_exts (99%) rename containers/{default => default-class-container}/base/jupyter_enable_exts (98%) rename containers/{default => default-class-container}/base/mkversions (100%) rename containers/{default => default-class-container}/base/ope_book (100%) rename containers/{default => default-class-container}/base/ope_book_registry (100%) rename containers/{default => default-class-container}/base/ope_book_user (100%) rename containers/{default => default-class-container}/base/ope_gid (100%) rename containers/{default => default-class-container}/base/ope_group (100%) rename containers/{default => default-class-container}/base/ope_uid (100%) create mode 100644 containers/default-class-container/base/pip_pkgs rename containers/{default => default-class-container}/base/python_pkgs (90%) rename containers/{default => default-class-container}/base/python_prereqs (60%) create mode 100644 containers/default-class-container/base/settings/overrides.json create mode 100644 containers/default-class-container/base/start-notebook.d/ope-sartup.sh rename containers/{default => default-class-container}/requirements/requirements.txt (62%) delete mode 100644 containers/default/base/base_tag delete mode 100644 containers/default/base/pip_pkgs delete mode 100644 containers/default/base/settings/overrides.json delete mode 100644 containers/default/base/start-notebook.d/ope-sartup.sh create mode 100644 containers/group-sync/Dockerfile create mode 100644 containers/group-sync/group-sync.py create mode 100644 containers/group-sync/requirements.txt create mode 100644 content/contributor_guide/rhoai_ta_access.md create mode 100644 deployment/README.md create mode 100644 deployment/cronjobs/group-sync/clusterrole.yaml create mode 100644 deployment/cronjobs/group-sync/clusterrolebinding.yaml create mode 100644 deployment/cronjobs/group-sync/cronjob.yaml create mode 100644 deployment/cronjobs/group-sync/kustomization.yaml create mode 100644 deployment/cronjobs/group-sync/rolebinding.yaml create mode 100644 deployment/cronjobs/group-sync/serviceaccount.yaml create mode 100644 deployment/cronjobs/multiple-ns-group-sync/clusterrole.yaml create mode 100644 deployment/cronjobs/multiple-ns-group-sync/clusterrolebinding.yaml create mode 100644 deployment/cronjobs/multiple-ns-group-sync/cronjob.yaml create mode 100644 deployment/cronjobs/multiple-ns-group-sync/kustomization.yaml create mode 100644 deployment/cronjobs/multiple-ns-group-sync/rhods-rb.yaml create mode 100644 deployment/cronjobs/multiple-ns-group-sync/serviceaccount.yaml create mode 100644 deployment/cronjobs/nb-culler/clusterrole.yaml create mode 100644 deployment/cronjobs/nb-culler/clusterrolebinding.yaml create mode 100644 deployment/cronjobs/nb-culler/cronjob.yaml create mode 100644 deployment/cronjobs/nb-culler/kustomization.yaml create mode 100644 deployment/cronjobs/nb-culler/rolebinding.yaml create mode 100644 deployment/cronjobs/nb-culler/serviceaccount.yaml create mode 100755 deployment/gpu-class/cleanup.sh create mode 100644 deployment/gpu-class/clusterrole.yaml create mode 100644 deployment/gpu-class/clusterrolebinding.yaml create mode 100755 deployment/gpu-class/gpu-class-setup.sh create mode 100644 deployment/gpu-class/localqueue.yaml create mode 100644 deployment/gpu-class/notebook_resource.yaml create mode 100644 deployment/gpu-class/rbac_template.yaml create mode 100644 deployment/webhooks/assign-class-label/certificate.yaml create mode 100644 deployment/webhooks/assign-class-label/deployment.yaml create mode 100644 deployment/webhooks/assign-class-label/issuer.yaml create mode 100644 deployment/webhooks/assign-class-label/kustomization.yaml create mode 100644 deployment/webhooks/assign-class-label/role.yaml create mode 100644 deployment/webhooks/assign-class-label/rolebinding.yaml create mode 100644 deployment/webhooks/assign-class-label/service.yaml create mode 100644 deployment/webhooks/assign-class-label/serviceaccount.yaml create mode 100644 deployment/webhooks/assign-class-label/webhook-config.yaml create mode 100755 scripts/get_url.py diff --git a/.github/workflows/Container_Test.yaml b/.github/workflows/Container_Test.yaml index 348651b..95338b1 100644 --- a/.github/workflows/Container_Test.yaml +++ b/.github/workflows/Container_Test.yaml @@ -1,12 +1,10 @@ +--- name: OPE Container Test on: push: - branches: - - 'main' - + branches: [main] env: - REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} - + REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} jobs: Setup-and-Build: runs-on: ubuntu-latest @@ -14,35 +12,31 @@ jobs: run: working-directory: ./containers/testing steps: - - name: Checkout Code #loads repo into action + - name: Checkout Code # loads repo into action uses: actions/checkout@v3 - - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.9.16" - + python-version: 3.9.16 - name: Install Dependencies run: | pip install -r requirements/requirements.txt - - name: Build run: | set -e # Exit immediately if any command fail - start=$(date +%s) #start the timer + start=$(date +%s) # start the timer timeout 50m time make build - end=$(date +%s) #end timer - seconds=$((end-start)) #calc build time in sec + end=$(date +%s) # end timer + seconds=$((end-start)) # calc build time in sec elapsed=$(echo "scale=2; ($end - $start) / 60.0" | bc) #get time in min with dec - min=${elapsed%.*} #extract min part - sec_cut=${elapsed#*.} #get the decimal + min=${elapsed%.*} # extract min part + sec_cut=${elapsed#*.} # get the decimal sec_round="${sec_cut:0:1}.${sec_cut:1}" sec=$(echo "$sec_round * 6" | bc -l) #calc secs echo "${min}.m_${sec}.s" > time.txt #send to file for use in diff run IMAGE=$(docker images --format "{{.Repository}}:{{.Tag}}" | head -n 1) #get built image echo "Image = $IMAGE" echo $IMAGE > image.txt - - name: Push Beta Version run: | IMAGE=$(docker images --format "{{.Repository}}:{{.Tag}}" | head -n 1) @@ -55,22 +49,18 @@ jobs: SIZE=$(docker images --format "{{.Size}}" | head -n 1) make push TIME="$TIME" SIZE="$SIZE" docker stop stable - - name: Upload Image uses: actions/upload-artifact@v3 with: name: image path: | ./containers/testing/image.txt - - name: Upload Time uses: actions/upload-artifact@v3 with: name: time path: | ./containers/testing/time.txt - - Health-Check: needs: Setup-and-Build runs-on: ubuntu-latest @@ -80,16 +70,14 @@ jobs: steps: - name: Checkout Code uses: actions/checkout@v3 - - name: Pull Image run: | make pull-beta - - name: Check Container Health run: | IMAGE=$(docker images --format "{{.Repository}}:{{.Tag}}" | head -n 1) #get built image docker run -d --name stable $IMAGE - sleep 7 #wait to start to check health + sleep 7 # wait to start to check health CONTAINER_NAME="stable" HEALTH=$(docker ps --format "{{.Names}}: {{.Status}}" | grep "$CONTAINER_NAME" | awk '{print $2}') #extract health status echo "" @@ -102,8 +90,6 @@ jobs: fi echo "" docker stop stable - - Image-Version-Check: needs: Setup-and-Build runs-on: ubuntu-latest @@ -111,21 +97,18 @@ jobs: run: working-directory: ./containers/testing steps: - - name: Checkout Code uses: actions/checkout@v3 - - name: Pull Image run: | make pull-beta - - name: Check Image Version run: | IMAGE=$(docker images --format "{{.Repository}}:{{.Tag}}" | head -n 1) #get just built image echo "IMAGE = $IMAGE" docker run -d --name stable $IMAGE ID=$( docker ps -aqf "name=stable" ) #get id of image - docker inspect $IMAGE > image.json #write inspect info to json for parsing + docker inspect $IMAGE > image.json # write inspect info to json for parsing REPO_TAG=$(jq -r '.[0].RepoTags[0]' image.json) #acquire tag echo "the value of REPO_TAG is: $REPO_TAG\n" echo "the value of IMAGE is: $IMAGE\n" @@ -137,8 +120,6 @@ jobs: exit -1 fi docker stop stable - - JupyterNB-Test: needs: Setup-and-Build runs-on: ubuntu-latest @@ -146,39 +127,32 @@ jobs: run: working-directory: ./containers/testing steps: - - name: Checkout Code uses: actions/checkout@v3 - - name: Pull Image run: | make pull-beta - - name: Run Container run: | IMAGE=$(docker images --format "{{.Repository}}:{{.Tag}}" | head -n 1) docker run -d -p 8888:8888 --name stable -v /test.ipynb:/home/jovyan/work $IMAGE container_id=$(docker ps -qf "name=stable" | head -n 1) - docker cp tests/test.ipynb $container_id:/home/jovyan/work #mount test nb into container - + docker cp tests/test.ipynb $container_id:/home/jovyan/work # mount test nb into container - name: Wait for Container Start run: | - until docker exec stable jupyter lab --version; do sleep 1; done #start JupyterLab in notebook and wait until its up - + until docker exec stable jupyter lab --version; do sleep 1; done # start JupyterLab in notebook and wait until its up - name: Execute Notebook run: | container_id=$(docker ps -qf "name=stable" | head -n 1) docker exec $container_id jupyter lab --generate-config docker exec $container_id sh -c "echo 'c.NotebookApp.token = \"\"' >> /home/jovyan/.jupyter/jupyter_notebook_config.py" #create it without a token - docker exec $container_id jupyter labextension install @jupyter-widgets/jupyterlab-manager #install widgets + docker exec $container_id jupyter labextension install @jupyter-widgets/jupyterlab-manager # install widgets docker exec $container_id pip install nbclient docker exec $container_id pip install nbconvert docker exec $container_id jupyter lab build --minimize=False - docker exec $container_id jupyter execute /home/jovyan/work/test.ipynb #if import fails import error will be triggered here + docker exec $container_id jupyter execute /home/jovyan/work/test.ipynb # if import fails import error will be triggered here echo "************SUCCESS***********" docker stop stable - - Package-Version-Test: needs: Setup-and-Build runs-on: ubuntu-latest @@ -186,29 +160,25 @@ jobs: run: working-directory: ./containers/testing steps: - - name: Checkout code uses: actions/checkout@v3 - - name: Pull Image run: | make pull-beta - - name: Checkout Package Versions run: | - cp tests/version_check.py ./version_check.py #add python script to compare vers to this file - cp tests/versions.txt ./versions.txt #add correct version list to compare against - chmod +x ./version_check.py # change perms to execute + cp tests/version_check.py ./version_check.py # add python script to compare vers to this file + cp tests/versions.txt ./versions.txt # add correct version list to compare against + chmod +x ./version_check.py # change perms to execute IMAGE=$(docker images --format "{{.Repository}}:{{.Tag}}" | head -n 1) #get image name docker run -d --name stable $IMAGE - sleep 7 # let properly start + sleep 7 # let properly start ID=$( docker ps -aqf "name=stable" ) - docker exec stable pip list >> ./list.txt #get rid of header for python comparison test + docker exec stable pip list >> ./list.txt # get rid of header for python comparison test tail -n +3 ./list.txt > temp.txt - mv ./temp.txt ./list.txt #rewrite truncated list to file - python3 version_check.py ./list.txt versions.txt #send to python for checking + mv ./temp.txt ./list.txt # rewrite truncated list to file + python3 version_check.py ./list.txt versions.txt # send to python for checking docker stop stable - Checksum: needs: Setup-and-Build runs-on: ubuntu-latest @@ -216,15 +186,11 @@ jobs: run: working-directory: ./containers/testing steps: - - name: Checkout code uses: actions/checkout@v3 - - name: Run Checksum run: | make -C tests checksum-test - - Size-and-Time-Display: needs: Setup-and-Build runs-on: ubuntu-latest @@ -232,21 +198,17 @@ jobs: run: working-directory: ./containers/testing steps: - - name: Checkout Code uses: actions/checkout@v3 - - name: Download Time uses: actions/download-artifact@v3 with: name: time path: | ./containers/testing/ - - name: Pull Image run: | make pull-beta - - name: Extract Size and Display Time and Size run: | IMAGE=$(docker images --format "{{.Repository}}:{{.Tag}}" | head -n 1) @@ -262,15 +224,12 @@ jobs: echo "" echo $SIZE > size.txt docker stop stable - - name: Upload Size uses: actions/upload-artifact@v3 with: name: size path: | ./containers/testing/size.txt - - UI-Test: runs-on: ubuntu-latest needs: Setup-and-Build @@ -278,21 +237,17 @@ jobs: run: working-directory: ./containers/testing steps: - - name: Checkout Code uses: actions/checkout@v3 - - name: Setting up Python uses: actions/setup-python@v4 with: python-version: '3.9' - - name: Install Chromium run: | - #sudo apt-get update + # sudo apt-get update sudo apt-get install -y chromium-browser sudo apt-get upgrade chromium-browser - - name: Install Necessary Python Packages run: | python -m pip install --upgrade pip @@ -300,27 +255,21 @@ jobs: pip install --upgrade selenium pip install --upgrade webdriver_manager chromedriver --version - - name: Pull beta-version run: | make pull-beta - - name: Run JupyterLab Inside Container run: | make run-beta & sleep 3 - - name: Run RISE Extension Test run: | sleep 5 - make -C tests rise-test #this triggers both testing of rise functionality & recording of screenshots - + make -C tests rise-test # this triggers both testing of rise functionality & recording of screenshots - name: Run Screenshots Difference Test run: | sleep 5 - make -C tests screenshots-test #this just runs screenshot difference - - + make -C tests screenshots-test # this just runs screenshot difference gdb-Test: runs-on: ubuntu-latest needs: Setup-and-Build @@ -328,39 +277,41 @@ jobs: run: working-directory: ./containers/testing steps: - - name: Checkout Code uses: actions/checkout@v3 - - name: Install Dependencies run: | pip install -r requirements/requirements.txt - - name: Pull beta-version run: | make pull-beta - - name: Run gdb run: | - IMAGE=$(docker images --format "{{.Repository}}:{{.Tag}}" | head -n 1) - docker run -d --name stable $IMAGE - docker exec stable bash -c 'for ((i=0;i<100; i++)); do gdb -ex starti -ex quit -q --batch /usr/bin/date 2>/dev/null | grep ld; done' | tee out | uniq #run jonatahns gdb test - expected_lines=100 - actual_lines=$(wc -l < out) - echo "got $actual_lines of output and expected $expected_lines of output\n" - if [ "$actual_lines" -lt "$expected_lines" ]; then - echo "******************Test failed****************" #if the output file contians no iterations through the test file, gdb has failed - exit -1 - else - echo "******************Test passed***************" - fi - docker stop stable - - - + IMAGE=$(docker images --format "{{.Repository}}:{{.Tag}}" | head -n 1) + docker run -d --name stable $IMAGE + docker exec stable bash -c 'for ((i=0;i<100; i++)); do gdb -ex starti -ex quit -q --batch /usr/bin/date 2>/dev/null | grep ld; done' | tee out | uniq #run jonatahns gdb test + expected_lines=100 + actual_lines=$(wc -l < out) + echo "got $actual_lines of output and expected $expected_lines of output\n" + if [ "$actual_lines" -lt "$expected_lines" ]; then + echo "******************Test failed****************" #if the output file contians no iterations through the test file, gdb has failed + exit -1 + else + echo "******************Test passed***************" + fi + docker stop stable Approval: runs-on: ubuntu-latest - needs: [Setup-and-Build, Health-Check, Image-Version-Check, JupyterNB-Test, Package-Version-Test, Checksum, Size-and-Time-Display, UI-Test, gdb-Test] + needs: + - Setup-and-Build + - Health-Check + - Image-Version-Check + - JupyterNB-Test + - Package-Version-Test + - Checksum + - Size-and-Time-Display + - UI-Test + - gdb-Test environment: name: approval steps: @@ -378,38 +329,32 @@ jobs: -H "Authorization: token $TOKEN" \ -H "Accept: application/vnd.github.v3+json" \ "https://api.github.com/repos/$OWNER/$REPO/actions/runs/$RUN_ID/approve") - - - Publish: - runs-on: ubuntu-latest #use the latest ubuntu container image - needs: Approval - defaults: + runs-on: ubuntu-latest # use the latest ubuntu container image + needs: Approval + defaults: run: working-directory: ./containers/testing - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Download Time - uses: actions/download-artifact@v3 - with: + steps: + - name: Checkout Code + uses: actions/checkout@v3 + - name: Download Time + uses: actions/download-artifact@v3 + with: name: time path: | ./containers/testing/ - - - name: Download Size - uses: actions/download-artifact@v3 - with: + - name: Download Size + uses: actions/download-artifact@v3 + with: name: size path: | ./containers/testing/ - - - name: Push to Public Version - run: | - REGISTRY=$(cat base/ope_book_registry) - REGISTRY_USER=$(cat base/ope_registry_user) - TIME=$(cat time.txt) - SIZE=$(cat size.txt) - docker login $REGISTRY -u $REGISTRY_USER -p $REGISTRY_PASSWORD - make publish TIME="$TIME" SIZE="$SIZE" \ No newline at end of file + - name: Push to Public Version + run: |- + REGISTRY=$(cat base/ope_book_registry) + REGISTRY_USER=$(cat base/ope_registry_user) + TIME=$(cat time.txt) + SIZE=$(cat size.txt) + docker login $REGISTRY -u $REGISTRY_USER -p $REGISTRY_PASSWORD + make publish TIME="$TIME" SIZE="$SIZE" diff --git a/.github/workflows/assign-class-label.yaml b/.github/workflows/assign-class-label.yaml new file mode 100644 index 0000000..814976a --- /dev/null +++ b/.github/workflows/assign-class-label.yaml @@ -0,0 +1,43 @@ +name: assign-class-label tests and linting/formatting + +on: + push: + paths: + - container-images/assign-class-label/** + pull_request: + paths: + - container-images/assign-class-label/** + +jobs: + lint-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.12' + + - name: Install dependencies + working-directory: ./container-images/assign-class-label/ + run: | + pip install -r requirements.txt + pip install -r test-requirements.txt + + - name: Run ruff format (formatting) + working-directory: ./container-images/assign-class-label/ + run: | + ruff format + + - name: Run ruff check (linting) + working-directory: ./container-images/assign-class-label/ + run: | + ruff check --fix + + - name: Run tests + working-directory: ./container-images/assign-class-label/ + run: | + pytest tests/ diff --git a/.github/workflows/precommit.yaml b/.github/workflows/precommit.yaml new file mode 100644 index 0000000..c5f222e --- /dev/null +++ b/.github/workflows/precommit.yaml @@ -0,0 +1,8 @@ +name: Run pre-commit checks + +on: + pull_request: + +jobs: + run-linters: + uses: ocp-on-nerc/workflows/.github/workflows/precommit.yaml@main diff --git a/.github/workflows/validate-manifests.yaml b/.github/workflows/validate-manifests.yaml new file mode 100644 index 0000000..533e04c --- /dev/null +++ b/.github/workflows/validate-manifests.yaml @@ -0,0 +1,8 @@ +name: Validate Kubernetes manifests + +on: + pull_request: + +jobs: + validate-manifests: + uses: ocp-on-nerc/workflows/.github/workflows/validate-manifests.yaml@main diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..56877ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,165 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + + +webhook.crt +webhook.key +secret.yaml diff --git a/.ope/README.md b/.ope/README.md index 13b6183..f2b47a3 100644 --- a/.ope/README.md +++ b/.ope/README.md @@ -1,3 +1,3 @@ # OPE Project configuration directory -A place to store project wide facts and configuration state \ No newline at end of file +A place to store project wide facts and configuration state diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..81ef041 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +repos: + - repo: https://github.com/Lucas-C/pre-commit-hooks + rev: v1.5.5 + hooks: + - id: remove-tabs + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: check-merge-conflict + - id: end-of-file-fixer + - id: check-added-large-files + exclude: '^cluster-scope/base/openapi/openshift-api-schema.json$' + - id: check-case-conflict + - id: check-json + - id: check-symlinks + - id: detect-private-key + + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.37.1 + hooks: + - id: yamllint + files: \.(yaml|yml)$ + types: [file, yaml] + entry: yamllint --strict diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..fea2692 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,13 @@ +--- +extends: default +rules: + line-length: disable + document-start: disable + indentation: + indent-sequences: whatever + hyphens: + max-spaces-after: 4 + truthy: + check-keys: false + comments: + min-spaces-from-content: 1 diff --git a/README.md b/README.md index ae0e10a..aef46c8 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,42 @@ ## Introduction -This user guide is intended to teach you how to author OPE-style books, as well as setup container images that contain all the software required for following along with the book content. - The OPE project is an initiative towards making education open source, and to achieve highly interactive teaching and presentation material. -## OPE Architecture +This project can be defined in two parts: +- **RHOAI Deployment Tools**: Operational tooling for running courses on OpenShift AI infrastructure +- **OPE SDK**: Tools for authoring interactive educational books and container images + +The first part of the README will cover the tooling for running courses on OpenShift AI. + +The second part is intended to teach you how to author OPE-style books, as well as setup container images that contain all the software required for following along with the book content. + + +## OpenShift AI Deployment Tools + +The OPE project includes comprehensive tooling for deploying and managing courses on OpenShift AI (RHOAI) infrastructure. + +### Components + +- **CronJobs**: Automated tasks for user group synchronization and notebook lifecycle management +- **Webhooks**: Pod mutation webhooks for automatic class label assignment +- **GPU Resource Management**: Tooling for setting up and managing GPU classes across namespaces +- **Utility Scripts**: Helper scripts for retrieving notebook URLs and managing course resources + +### Deployment + +All RHOAI deployment resources are located in the `deployment/` directory. For detailed setup instructions, configuration options, and deployment guides, see [deployment/README.md](deployment/README.md). + +### Quick Start + +To deploy RHOAI components to your OpenShift cluster: + +1. Ensure you have cluster-admin access to an OpenShift cluster with RHOAI installed +2. Review and customize configurations in `deployment/` +3. Deploy using Kustomize: `oc apply -k deployment//` + + +## OPE SDK Architecture ![OPE Architecture](content/images/OPE-Architecture.png) @@ -21,17 +52,17 @@ The OPE project is an initiative towards making education open source, and to ac ## [OPE CLI Tool](https://github.com/OPEFFORT/tools) -Using the official OPE tool, you can easily create, update, manage, and publish OPE projects from the command line. +Using the official OPE tool, you can easily create, update, manage, and publish OPE projects from the command line. ### Commands -- **repo_add:** add a repository to a project -- **new_project:** create a new ope project from the ope templates -- **new_book:** create a new publishable book for your content -- **new_container:** add source for building a new container for your project -- **update:** update and rebase changes from the OPE framework -- **build:** build the book -- **pub:** publish the book +- **repo_add:** add a repository to a project +- **new_project:** create a new ope project from the ope templates +- **new_book:** create a new publishable book for your content +- **new_container:** add source for building a new container for your project +- **update:** update and rebase changes from the OPE framework +- **build:** build the book +- **pub:** publish the book - **new_course:** creates a new ope project seeded with 3 books (textbook, lecture notes, and labmanual) and one container @@ -40,7 +71,7 @@ Using the official OPE tool, you can easily create, update, manage, and publish A project consists of a collection of OPE books and associated container images. -Before you create a project, an empty repository for the project should already be created. +Before you create a project, an empty repository for the project should already be created. Assuming that you are using github or gitlab we recommend you create a new organization for your project. If you will be publishing containers as part of your project we also recommend that you create an docker image registery organization. @@ -65,7 +96,7 @@ This will add the repository to the current project in order to store and track This will create a new directory (with the name of the project) with 3 sub directories: - books: This directory contains the book(s) for the project -- containers: This directory contains the container images +- containers: This directory contains the container images - content: This directory contains the content associated with the book(s) @@ -79,14 +110,14 @@ To create a new book, run the following command within the books directory: ope new_book ``` -This will create a new directory with the name of the book, within this directory there is a sub-directory "content" that will contain the content of the book and is seeded with everything needed to get started authoring said book. A symbol link is created for this content directory to the content directory in the home directory (~/content) to store the source files for all books. +This will create a new directory with the name of the book, within this directory there is a sub-directory "content" that will contain the content of the book and is seeded with everything needed to get started authoring said book. A symbol link is created for this content directory to the content directory in the home directory (~/content) to store the source files for all books. ### Editing Book Configuration - **bookname_config.yml:** This config file is used to change book settings such as: title, author, logo, URL to publish to, and jupyter extensions you would like enabled, etc. -- **bookname_toc.yml:** This file is used to modify the different parts of the book. In the section labeled parts, you can add a new part by specifying it's caption (name of the part), whether it is numbered or not, and then you may specify the different chapters or sections within that part by specifying the files associated with the respective content. +- **bookname_toc.yml:** This file is used to modify the different parts of the book. In the section labeled parts, you can add a new part by specifying it's caption (name of the part), whether it is numbered or not, and then you may specify the different chapters or sections within that part by specifying the files associated with the respective content. - The caption name does not have to be the same name as the directory containing the content for that part (ex. part1 does not have to have the caption: 'part1') - The basic layout for a part is as follows: ``` @@ -94,12 +125,12 @@ This will create a new directory with the name of the book, within this director numbered: true chapters: - file: directory/file - sections: + sections: - file: dummy_part/section ``` - Each chapter may have its own sub-section -- **bookname_intro.md:** This file is used to create the landing page for the textbook. The content within this file will be the first page displayed when accessing the textbook, lecture notes, or lab manual. +- **bookname_intro.md:** This file is used to create the landing page for the textbook. The content within this file will be the first page displayed when accessing the textbook, lecture notes, or lab manual. ### Creating Content @@ -145,25 +176,25 @@ To add source for building a new container for your project, run the following c ope new_container ``` -This will create a new directory with the name specified for the container. This directory contains the Dockerfile along with necessary associated files for building the container. +This will create a new directory with the name specified for the container. This directory contains the Dockerfile along with necessary associated files for building the container. ### Adding Software Packages to the Image -In order to add software packages that are not already included in the base OPE image, you are able to edit the ```requirements.txt``` file within the requirements directory. When building the image, these packages will be present in the container environment. +In order to add software packages that are not already included in the base OPE image, you are able to edit the ```requirements.txt``` file within the requirements directory. When building the image, these packages will be present in the container environment. ### Building and Publishing Container We utilize the make tool to efficiently build, run, tag and publish the container images. You can simply run the command 'make' followed by one of these commands. - **build** - builds the custom container image -- **push** - append timestamp to current image and push to private registry mentioned in private_registry:private_user files under base. +- **push** - append timestamp to current image and push to private registry mentioned in private_registry:private_user files under base. - **publish** - tag current image as ope_book_registry/ope_book_user/ope_book files under base directory along with a timestamp and push it like below. -- **pull** - pull the most recent public image -- **pull-priv** - pulls the most recent private image -- **root** - executes the private image as root user like below. -- **user** - executes the private image as the default user -- **run** - starts published version with jupyter lab interface -- **run-priv** - starts private version with jupyter lab interface +- **pull** - pull the most recent public image +- **pull-priv** - pulls the most recent private image +- **root** - executes the private image as root user like below. +- **user** - executes the private image as the default user +- **run** - starts published version with jupyter lab interface +- **run-priv** - starts private version with jupyter lab interface ## List of OPE Textbooks @@ -175,7 +206,7 @@ The textbooks in the following list are OPE textbooks. Take a look at these for - [Repository](https://github.com/jappavoo/UndertheCovers) - [Textbook](https://jappavoo.github.io/UndertheCovers/textbook/intro_tb.html) - [Lecture Notes](https://jappavoo.github.io/UndertheCovers/lecturenotes/intro_ln.html) -- [Lab Manual](https://jappavoo.github.io/UndertheCovers/labmanual/intro_lm.html) +- [Lab Manual](https://jappavoo.github.io/UndertheCovers/labmanual/intro_lm.html) ### OpenOS diff --git a/books/contributor_guide/Makefile b/books/contributor_guide/Makefile index 9a1fcc7..402ec24 100644 --- a/books/contributor_guide/Makefile +++ b/books/contributor_guide/Makefile @@ -34,7 +34,7 @@ pub: @echo "Published to:" @./ghp-url.sh ${NAME} -clean: +clean: ${RM} -r ${OUTDIR} help: diff --git a/books/contributor_guide/fixlinks.sh b/books/contributor_guide/fixlinks.sh index da6e323..3acf507 100755 --- a/books/contributor_guide/fixlinks.sh +++ b/books/contributor_guide/fixlinks.sh @@ -10,13 +10,13 @@ fi find ${DIR} -type f -name '*.html' | while read file do if grep -q '' $file; do - echo "$file -- broken links??" - # -i is for inplace update -- leaves original in in $file.orig -- not -i'' would stop backups from being created - sed -i.orig -E 's/(' $file; do + echo "$file -- broken links??" + # -i is for inplace update -- leaves original in in $file.orig -- not -i'' would stop backups from being created + sed -i.orig -E 's/(' $file; do - echo "$file -- broken links??" - # -i is for inplace update -- leaves original in in $file.orig -- not -i'' would stop backups from being created - sed -i.orig -E 's/(' $file; do + echo "$file -- broken links??" + # -i is for inplace update -- leaves original in in $file.orig -- not -i'' would stop backups from being created + sed -i.orig -E 's/(' $file; do - echo "$file -- broken links??" - # -i is for inplace update -- leaves original in in $file.orig -- not -i'' would stop backups from being created - sed -i.orig -E 's/(' $file; do + echo "$file -- broken links??" + # -i is for inplace update -- leaves original in in $file.orig -- not -i'' would stop backups from being created + sed -i.orig -E 's/( str: + return pod_user.replace('-40', '@').replace('-2e', '.') + + +def get_client() -> DynamicClient: + try: + config.load_config() + k8s_client = client.ApiClient() + dyn_client = DynamicClient(k8s_client) + return dyn_client + except config.ConfigException as e: + LOG.error('Could not configure Kubernetes client: %s', str(e)) + exit(1) + + +def get_group_resource(dyn_client): + return dyn_client.resources.get(api_version='user.openshift.io/v1', kind='Group') + + +# Get users of a given group +def get_group_members(group_resource: Any, group_name: str) -> List[str]: + group_obj = group_resource.get(name=group_name) + return group_obj.users + + +def assign_class_label( + pod: dict[str, Any], groups: list[str], dyn_client: DynamicClient +) -> str | None: + # Extract pod metadata + try: + pod_metadata = pod.get('metadata', {}) + pod_labels = pod_metadata.get('labels', {}) + pod_user = pod_labels.get('opendatahub.io/user', None) + except AttributeError as e: + LOG.error(f'Error extracting pod information: {e}') + return None + + if pod_user is None: + return None + + pod_user = decode_pod_user(pod_user) + + group_resource = get_group_resource(dyn_client) + + # Iterate through classes + for group in groups: + users = get_group_members(group_resource, group) + + # Check if group has no users + if not users: + LOG.warning(f'Group {group} has no users or users attribute is not a list.') + continue + + # Compare users in the groups (classes) with the pod user + if pod_user in users: + LOG.info(f'Assigning class label: {group} to user {pod_user}') + return group + + return None + + +def create_app(**config: Any) -> Flask: + app = Flask(__name__) + app.config.from_prefixed_env('RHOAI_CLASS') + app.config.update(config) + + if not app.config['GROUPS']: + LOG.error('RHOAI_CLASS_GROUPS environment variables are required.') + exit(1) + + groups = app.config['GROUPS'].split(',') + + dyn_client = get_client() + + @app.route('/mutate', methods=['POST']) + def mutate_pod() -> Response: + # Grab pod for mutation and validate request + try: + admission_review = AdmissionReview(**request.get_json()) + except ValidationError as e: + LOG.error('Validation error: %s', e) + return ( + jsonify( + AdmissionReviewResponse( + response=AdmissionResponse( + uid=request.json.get('request', {}).get('uid', ''), + allowed=False, + status=Status(message=f'Invalid request: {e}'), + ) + ).model_dump() + ), + 400, + {'content-type': 'application/json'}, + ) + + uid = admission_review.request.uid + pod = admission_review.request.object.model_dump() + + # Grab class that the pod user belongs to + try: + class_label = assign_class_label(pod, groups, dyn_client) + except Exception as err: + LOG.error('failed to assign class label: %s', err) + return 'unexpected error encountered', 500, {'content-type': 'text/plain'} + + # If user not in any class, return without modifications + if not class_label: + return ( + jsonify( + AdmissionReviewResponse( + response=AdmissionResponse( + uid=uid, + allowed=True, + status=Status(message='No class label assigned.'), + ) + ).model_dump() + ), + 200, + {'content-type': 'application/json'}, + ) + + # Generate JSON Patch to add class label + patch = [ + { + 'op': 'add', + 'path': '/metadata/labels/nerc.mghpcc.org~1class', + 'value': class_label, + } + ] + + # Encode patch as base64 for response + patch_base64 = base64.b64encode(json.dumps(patch).encode('utf-8')).decode( + 'utf-8' + ) + + # Return webhook response that includes the patch to add class label + return ( + jsonify( + AdmissionReviewResponse( + response=AdmissionResponse( + uid=uid, allowed=True, patchType='JSONPatch', patch=patch_base64 + ) + ).model_dump() + ), + 200, + {'content-type': 'application/json'}, + ) + + return app diff --git a/containers/assign-class-label/requirements.txt b/containers/assign-class-label/requirements.txt new file mode 100644 index 0000000..fde8659 --- /dev/null +++ b/containers/assign-class-label/requirements.txt @@ -0,0 +1,5 @@ +flask +kubernetes +openshift +gunicorn +pydantic diff --git a/containers/assign-class-label/test-requirements.txt b/containers/assign-class-label/test-requirements.txt new file mode 100644 index 0000000..71cc539 --- /dev/null +++ b/containers/assign-class-label/test-requirements.txt @@ -0,0 +1,2 @@ +pytest +ruff diff --git a/containers/assign-class-label/tests/test_mutate.py b/containers/assign-class-label/tests/test_mutate.py new file mode 100644 index 0000000..6386333 --- /dev/null +++ b/containers/assign-class-label/tests/test_mutate.py @@ -0,0 +1,243 @@ +import pytest +import mutate + +from unittest import mock + + +@pytest.fixture() +def app(): + with mock.patch('mutate.get_client') as mock_get_client, mock.patch( + 'mutate.get_group_resource' + ) as mock_get_group_resource, mock.patch( + 'mutate.get_group_members' + ) as mock_group_members: + mock_object = mock.Mock() + mock_get_client.return_value = mock_object + mock_get_group_resource.return_value = mock_object + + def mock_get_group_members(group_resource, group_name): + if group_name == 'class1': + return ['user1'] + elif group_name == 'class2': + return ['user2'] + return [] + + mock_group_members.side_effect = mock_get_group_members + + app = mutate.create_app(GROUPS='class1,class2', LABEL='testlabel') + + app.config.update( + { + 'TESTING': True, + } + ) + + yield app + + +@pytest.fixture() +def client(app): + return app.test_client() + + +# Sending empty json request +def test_mutate_bad_data(client): + res = client.post('/mutate', json={}) + + assert res.status_code == 400 + + res_json = res.get_json() + + assert 'Invalid request' in res_json['response']['status']['message'] + + +# Calling a bad path +def test_bad_path(client): + res = client.get('/fake_path') + assert res.status_code == 404 + + assert 'The requested URL was not found' in res.data.decode('utf-8') + + +# Proper request that contains no metadata +def test_request_no_metadata(client): + res = client.post( + '/mutate', + json={ + 'request': { + 'uid': '1234', + 'object': {'metadata': {}}, + } + }, + ) + + assert res.status_code == 200 + assert res.json == { + 'apiVersion': 'admission.k8s.io/v1', + 'kind': 'AdmissionReview', + 'response': { + 'allowed': True, + 'status': {'message': 'No class label assigned.'}, + 'uid': '1234', + 'patchType': None, + 'patch': None, + }, + } + + +# Exeption response +def test_api_exception(client): + mutate.get_group_members.side_effect = ValueError('this is a test') + res = client.post( + '/mutate', + json={ + 'request': { + 'uid': '1234', + 'object': { + 'metadata': { + 'labels': { + 'opendatahub.io/user': 'testuser1', + } + } + }, + } + }, + ) + assert res.status_code == 500 + assert res.text == 'unexpected error encountered' + + +# If user1 in group1, returns successful json patch +def test_valid_request(client): + res = client.post( + '/mutate', + json={ + 'request': { + 'uid': '1234', + 'object': { + 'metadata': { + 'labels': { + 'opendatahub.io/user': 'user1', + } + } + }, + } + }, + ) + + print(f'Response code: {res.status_code}') + print(f'Response JSON: {res.get_json()}') + assert res.status_code == 200 + assert res.get_json()['response']['patchType'] == 'JSONPatch' + + +# Pod has invalid (empty) UID +def test_invalid_uid(client): + res = client.post( + '/mutate', + json={ + 'request': { + 'uid': '', + 'object': { + 'metadata': { + 'labels': { + 'opendatahub.io/user': 'user1', + } + } + }, + } + }, + ) + + assert res.status_code == 400 + + assert 'Invalid request' in res.get_json()['response']['status']['message'] + + +# Request formatted correctly but contains empty or no user +def test_no_user(client): + res = client.post( + '/mutate', + json={ + 'request': { + 'uid': '1234', + 'object': { + 'metadata': { + 'labels': { + 'opendatahub.io/user': '', + } + } + }, + } + }, + ) + + assert res.status_code == 200 + assert res.json == { + 'apiVersion': 'admission.k8s.io/v1', + 'kind': 'AdmissionReview', + 'response': { + 'allowed': True, + 'status': {'message': 'No class label assigned.'}, + 'uid': '1234', + 'patchType': None, + 'patch': None, + }, + } + + res = client.post( + '/mutate', + json={ + 'request': { + 'uid': '1234', + 'object': {'metadata': {'labels': {}}}, + } + }, + ) + + assert res.status_code == 200 + assert res.json == { + 'apiVersion': 'admission.k8s.io/v1', + 'kind': 'AdmissionReview', + 'response': { + 'allowed': True, + 'status': {'message': 'No class label assigned.'}, + 'uid': '1234', + 'patchType': None, + 'patch': None, + }, + } + + +# Valid request but all groups empty +def test_empty_group_members(client): + with mock.patch('mutate.get_group_members') as mock_group_members: + mock_group_members.return_value = [] + res = client.post( + '/mutate', + json={ + 'request': { + 'uid': '1234', + 'object': { + 'metadata': { + 'labels': { + 'opendatahub.io/user': 'user1', + } + } + }, + } + }, + ) + + assert res.status_code == 200 + assert res.json == { + 'apiVersion': 'admission.k8s.io/v1', + 'kind': 'AdmissionReview', + 'response': { + 'allowed': True, + 'status': {'message': 'No class label assigned.'}, + 'uid': '1234', + 'patchType': None, + 'patch': None, + }, + } diff --git a/containers/assign-class-label/wsgi.py b/containers/assign-class-label/wsgi.py new file mode 100644 index 0000000..03851b3 --- /dev/null +++ b/containers/assign-class-label/wsgi.py @@ -0,0 +1,6 @@ +from mutate import create_app + +webhook = create_app() + +if __name__ == '__main__': + webhook.run() diff --git a/containers/default/.gitignore b/containers/default-class-container/.gitignore similarity index 75% rename from containers/default/.gitignore rename to containers/default-class-container/.gitignore index 5236e1e..b25c15b 100644 --- a/containers/default/.gitignore +++ b/containers/default-class-container/.gitignore @@ -1,2 +1 @@ *~ - diff --git a/containers/default/Makefile b/containers/default-class-container/Makefile similarity index 97% rename from containers/default/Makefile rename to containers/default-class-container/Makefile index 96b68d9..9d5e05f 100644 --- a/containers/default/Makefile +++ b/containers/default-class-container/Makefile @@ -38,7 +38,7 @@ PYTHON_INSTALL_PACKAGES := $(shell cat base/python_pkgs) PIP_INSTALL_PACKAGES := $(shell cat base/pip_pkgs) JUPYTER_ENABLE_EXTENSIONS := $(shell cat base/jupyter_enable_exts) -JUPYTER_DISABLE_EXTENSIONS := $(shell if [[ -a base/jupyter_disable_exts ]]; then cat base/jupyter_disable_exts; fi) +JUPYTER_DISABLE_EXTENSIONS := $(shell if [[ -a base/jupyter_disable_exts ]]; then cat base/jupyter_disable_exts; fi) # expand installation so that the image feels more like a proper UNIX user environment with man pages, etc. UNMIN := yes @@ -52,7 +52,7 @@ MOUNT_DIR := /opt/app-root/src HOST_DIR := ${HOME} # include vars for image tag -TIME ?= +TIME ?= SIZE ?= help: @@ -117,7 +117,7 @@ show-tag: ARGS ?= show-tag: DARGS ?= show-tag: ## tag current private build as beta @-echo $(OPE_BOOK_REG)$(OPE_BOOK_IMAGE)$(OPE_BETA_TAG) - + ### DEBUG TARGETS @@ -133,7 +133,7 @@ user: ## start private version with usershell to poke around ### PUBLIC USAGE TARGETS -pull: +pull: pull: ## pull most recent public version docker pull $(OPE_BOOK_REG)$(OPE_BOOK_IMAGE)$(OPE_PUBLIC_TAG)_$(TIME)_$(SIZE) @@ -142,6 +142,4 @@ run: ARGS ?= run: DARGS ?= -u $(OPE_UID):$(OPE_GID) -v "${HOST_DIR}":"${MOUNT_DIR}" -v "${SSH_AUTH_SOCK}":"${SSH_AUTH_SOCK}" -v "${SSH_AUTH_SOCK}":"${SSH_AUTH_SOCK}" -e SSH_AUTH_SOCK=${SSH_AUTH_SOCK} -p ${SSH_PORT}:22 run: PORT ?= 8888 run: ## start published version with jupyter lab interface - docker run -it --rm -p $(PORT):$(PORT) $(DARGS) $(OPE_BOOK_REG)$(OPE_BOOK_IMAGE)$(OPE_PUBLIC_TAG)_$(TIME)_$(SIZE) $(ARGS) - - + docker run -it --rm -p $(PORT):$(PORT) $(DARGS) $(OPE_BOOK_REG)$(OPE_BOOK_IMAGE)$(OPE_PUBLIC_TAG)_$(TIME)_$(SIZE) $(ARGS) diff --git a/containers/default/README.md b/containers/default-class-container/README.md similarity index 100% rename from containers/default/README.md rename to containers/default-class-container/README.md diff --git a/containers/default/base/Dockerfile b/containers/default-class-container/base/Dockerfile similarity index 99% rename from containers/default/base/Dockerfile rename to containers/default-class-container/base/Dockerfile index 33b2239..abddd06 100755 --- a/containers/default/base/Dockerfile +++ b/containers/default-class-container/base/Dockerfile @@ -70,7 +70,7 @@ RUN touch /home/${NB_USER}/.hushlogin && \ # finally remove default working directory from joyvan home rmdir /home/${NB_USER}/work && \ # as per the nbstripout readme we setup nbstripout be always be used for the joyvan user for all repos - nbstripout --install --system + nbstripout --install --system # Static Customize for OPE USER ID choices # To avoid problems with start.sh logic we do not modify user name @@ -114,4 +114,3 @@ RUN chgrp ${OPE_GROUP} -R /home USER $NB_USER CMD ["/bin/bash", "-c", "cd /home/jovyan ; start-notebook.sh"] - diff --git a/containers/default/base/base_image b/containers/default-class-container/base/base_image similarity index 96% rename from containers/default/base/base_image rename to containers/default-class-container/base/base_image index 92e9d0e..3d48630 100644 --- a/containers/default/base/base_image +++ b/containers/default-class-container/base/base_image @@ -1,2 +1 @@ jupyter/minimal-notebook - diff --git a/containers/default/base/base_registry b/containers/default-class-container/base/base_registry similarity index 90% rename from containers/default/base/base_registry rename to containers/default-class-container/base/base_registry index d5703cd..243e482 100644 --- a/containers/default/base/base_registry +++ b/containers/default-class-container/base/base_registry @@ -1,2 +1 @@ docker.io - diff --git a/containers/default-class-container/base/base_tag b/containers/default-class-container/base/base_tag new file mode 100644 index 0000000..922ebee --- /dev/null +++ b/containers/default-class-container/base/base_tag @@ -0,0 +1 @@ +:x86_64-python-3.9.13 diff --git a/containers/default/base/checksum b/containers/default-class-container/base/checksum similarity index 98% rename from containers/default/base/checksum rename to containers/default-class-container/base/checksum index 4017f45..b94b67e 100644 --- a/containers/default/base/checksum +++ b/containers/default-class-container/base/checksum @@ -1,2 +1 @@ fb2cc759cef3e1a8dd909c66373991a5f028f0bc6b2fcb76bdc7a5b04683b6a5 - diff --git a/containers/default/base/customization_name b/containers/default-class-container/base/customization_name similarity index 93% rename from containers/default/base/customization_name rename to containers/default-class-container/base/customization_name index 0bafdcd..5c46288 100644 --- a/containers/default/base/customization_name +++ b/containers/default-class-container/base/customization_name @@ -1,2 +1 @@ base-ope-image - diff --git a/containers/default/base/distro_pkgs b/containers/default-class-container/base/distro_pkgs similarity index 99% rename from containers/default/base/distro_pkgs rename to containers/default-class-container/base/distro_pkgs index f10eb5c..c255aac 100644 --- a/containers/default/base/distro_pkgs +++ b/containers/default-class-container/base/distro_pkgs @@ -39,4 +39,3 @@ lsb-release xdg-utils wget dos2unix - diff --git a/containers/default/base/jupyter_disable_exts b/containers/default-class-container/base/jupyter_disable_exts similarity index 99% rename from containers/default/base/jupyter_disable_exts rename to containers/default-class-container/base/jupyter_disable_exts index c4c5740..aed7d93 100644 --- a/containers/default/base/jupyter_disable_exts +++ b/containers/default-class-container/base/jupyter_disable_exts @@ -16,4 +16,3 @@ @jupyterlab/inspector-extension:inspector @jupyterlab/inspector-extension:notebooks @jupyterlab/shortcuts-extension:shortcuts - diff --git a/containers/default/base/jupyter_enable_exts b/containers/default-class-container/base/jupyter_enable_exts similarity index 98% rename from containers/default/base/jupyter_enable_exts rename to containers/default-class-container/base/jupyter_enable_exts index 647daf3..89c5ff8 100644 --- a/containers/default/base/jupyter_enable_exts +++ b/containers/default-class-container/base/jupyter_enable_exts @@ -2,4 +2,3 @@ spellchecker/main splitcell/splitcell hide_input_all/main hide_input/main - diff --git a/containers/default/base/mkversions b/containers/default-class-container/base/mkversions similarity index 100% rename from containers/default/base/mkversions rename to containers/default-class-container/base/mkversions diff --git a/containers/default/base/ope_book b/containers/default-class-container/base/ope_book similarity index 100% rename from containers/default/base/ope_book rename to containers/default-class-container/base/ope_book diff --git a/containers/default/base/ope_book_registry b/containers/default-class-container/base/ope_book_registry similarity index 100% rename from containers/default/base/ope_book_registry rename to containers/default-class-container/base/ope_book_registry diff --git a/containers/default/base/ope_book_user b/containers/default-class-container/base/ope_book_user similarity index 100% rename from containers/default/base/ope_book_user rename to containers/default-class-container/base/ope_book_user diff --git a/containers/default/base/ope_gid b/containers/default-class-container/base/ope_gid similarity index 100% rename from containers/default/base/ope_gid rename to containers/default-class-container/base/ope_gid diff --git a/containers/default/base/ope_group b/containers/default-class-container/base/ope_group similarity index 100% rename from containers/default/base/ope_group rename to containers/default-class-container/base/ope_group diff --git a/containers/default/base/ope_uid b/containers/default-class-container/base/ope_uid similarity index 100% rename from containers/default/base/ope_uid rename to containers/default-class-container/base/ope_uid diff --git a/containers/default-class-container/base/pip_pkgs b/containers/default-class-container/base/pip_pkgs new file mode 100644 index 0000000..cc7378e --- /dev/null +++ b/containers/default-class-container/base/pip_pkgs @@ -0,0 +1 @@ +jupyterquiz diff --git a/containers/default/base/python_pkgs b/containers/default-class-container/base/python_pkgs similarity index 90% rename from containers/default/base/python_pkgs rename to containers/default-class-container/base/python_pkgs index ef38714..a928ed4 100644 --- a/containers/default/base/python_pkgs +++ b/containers/default-class-container/base/python_pkgs @@ -13,4 +13,4 @@ jupyterlab-spellchecker nbconvert pyppeteer gdb -jupyterlab-myst==1.1.3 \ No newline at end of file +jupyterlab-myst==1.1.3 diff --git a/containers/default/base/python_prereqs b/containers/default-class-container/base/python_prereqs similarity index 60% rename from containers/default/base/python_prereqs rename to containers/default-class-container/base/python_prereqs index 0d2fd1b..c4aa2e6 100644 --- a/containers/default/base/python_prereqs +++ b/containers/default-class-container/base/python_prereqs @@ -1,2 +1,2 @@ nbclient==0.5.13 -jsonschema \ No newline at end of file +jsonschema diff --git a/containers/default-class-container/base/settings/overrides.json b/containers/default-class-container/base/settings/overrides.json new file mode 100644 index 0000000..2d8819b --- /dev/null +++ b/containers/default-class-container/base/settings/overrides.json @@ -0,0 +1,1050 @@ +{ + "@jupyterlab/terminal-extension:plugin": { + "shutdownOnClose": true + }, + "@jupyterlab/statusbar-extension:plugin": { + "visible": false + }, + "@jupyterlab/mainmenu-extension:plugin": { + "menus": [ + { + "disabled": false, + "id": "jp-mainmenu-file", + "items": [ + { + "type": "submenu", + "submenu": { + "id": "jp-mainmenu-file-new", + "label": "New", + "items": [ + { + "disabled": false, + "command": "console:create", + "rank": 1 + }, + { + "disabled": false, + "command": "notebook:create-new", + "rank": 10 + } + ] + }, + "rank": 0 + }, + { + "type": "separator", + "rank": 2 + }, + { + "disabled": false, + "command": "filemenu:create-console", + "rank": 2.1 + }, + { + "disabled": false, + "command": "filemenu:close-and-cleanup", + "rank": 3.1 + }, + { + "type": "separator", + "rank": 99 + }, + { + "disabled": false, + "command": "filemenu:logout", + "rank": 99 + }, + { + "disabled": false, + "command": "filemenu:shutdown", + "rank": 99 + }, + { + "disabled": false, + "command": "docmanager:clone", + "rank": 2 + }, + { + "type": "separator", + "rank": 4 + }, + { + "disabled": false, + "command": "docmanager:save", + "rank": 4 + }, + { + "disabled": false, + "command": "docmanager:save-as", + "rank": 4 + }, + { + "disabled": false, + "command": "docmanager:save-all", + "rank": 4 + }, + { + "type": "separator", + "rank": 5 + }, + { + "disabled": false, + "command": "docmanager:reload", + "rank": 5 + }, + { + "disabled": false, + "command": "docmanager:restore-checkpoint", + "rank": 5 + }, + { + "disabled": false, + "command": "docmanager:rename", + "rank": 5 + }, + { + "type": "separator", + "rank": 3 + }, + { + "disabled": false, + "command": "application:close", + "rank": 3 + }, + { + "disabled": false, + "command": "application:close-all", + "rank": 3.2 + }, + { + "disabled": false, + "type": "separator", + "rank": 98 + }, + { + "disabled": false, + "command": "apputils:print", + "rank": 98 + }, + { + "disabled": false, + "command": "workspace-ui:save-as", + "rank": 40 + }, + { + "disabled": false, + "command": "workspace-ui:save", + "rank": 40 + }, + { + "type": "separator", + "rank": 6 + }, + { + "disabled": false, + "command": "docmanager:download", + "rank": 6 + }, + { + "type": "separator", + "rank": 6 + }, + { + "disabled": false, + "command": "filebrowser:create-main-launcher", + "rank": 1 + }, + { + "type": "separator", + "rank": 1 + }, + { + "disabled": false, + "command": "filebrowser:open-path", + "rank": 1 + }, + { + "disabled": false, + "command": "filebrowser:open-url", + "rank": 1 + }, + { + "type": "separator", + "rank": 100 + }, + { + "command": "hub:control-panel", + "rank": 100 + }, + { + "command": "hub:logout", + "rank": 100 + }, + { + "type": "separator", + "rank": 100 + }, + { + "type": "separator", + "rank": 10 + }, + { + "type": "submenu", + "disabled": false, + "rank": 10, + "submenu": { + "id": "jp-mainmenu-file-notebookexport", + "label": "Save and Export Notebook As…" + } + }, + { + "type": "separator", + "rank": 10 + } + ], + "label": "File", + "mnemonic": -1, + "rank": 1 + }, + { + "disabled": false, + "id": "jp-mainmenu-edit", + "items": [ + { + "command": "editmenu:undo", + "rank": 0 + }, + { + "command": "editmenu:redo", + "rank": 0 + }, + { + "type": "separator", + "rank": 10 + }, + { + "command": "editmenu:clear-current", + "rank": 10 + }, + { + "command": "editmenu:clear-all", + "rank": 10 + }, + { + "type": "separator", + "rank": 200 + }, + { + "command": "editmenu:go-to-line", + "rank": 200 + }, + { + "type": "separator", + "rank": 10 + }, + { + "command": "documentsearch:start", + "rank": 10 + }, + { + "command": "documentsearch:highlightNext", + "rank": 10 + }, + { + "command": "documentsearch:highlightPrevious", + "rank": 10 + }, + { + "type": "separator", + "rank": 10 + }, + { + "type": "separator", + "rank": 4 + }, + { + "command": "notebook:undo-cell-action", + "rank": 4 + }, + { + "command": "notebook:redo-cell-action", + "rank": 4 + }, + { + "type": "separator", + "rank": 5 + }, + { + "command": "notebook:cut-cell", + "rank": 5 + }, + { + "command": "notebook:copy-cell", + "rank": 5 + }, + { + "command": "notebook:paste-cell-below", + "rank": 5 + }, + { + "command": "notebook:paste-cell-above", + "rank": 5 + }, + { + "command": "notebook:paste-and-replace-cell", + "rank": 5 + }, + { + "type": "separator", + "rank": 6 + }, + { + "command": "notebook:delete-cell", + "rank": 6 + }, + { + "type": "separator", + "rank": 7 + }, + { + "command": "notebook:select-all", + "rank": 7 + }, + { + "command": "notebook:deselect-all", + "rank": 7 + }, + { + "type": "separator", + "rank": 8 + }, + { + "command": "notebook:move-cell-up", + "rank": 8 + }, + { + "command": "notebook:move-cell-down", + "rank": 8 + }, + { + "type": "separator", + "rank": 9 + }, + { + "command": "notebook:split-cell-at-cursor", + "rank": 9 + }, + { + "command": "notebook:merge-cells", + "rank": 9 + }, + { + "command": "notebook:merge-cell-above", + "rank": 9 + }, + { + "command": "notebook:merge-cell-below", + "rank": 9 + }, + { + "type": "separator", + "rank": 9 + } + ], + "label": "Edit", + "mnemonic": -1, + "rank": 2 + }, + { + "disabled": false, + "id": "jp-mainmenu-view", + "items": [ + { + "type": "separator", + "rank": 10 + }, + { + "command": "viewmenu:line-numbering", + "rank": 10 + }, + { + "command": "viewmenu:match-brackets", + "rank": 10 + }, + { + "command": "viewmenu:word-wrap", + "rank": 10 + }, + { + "type": "separator", + "rank": 1 + }, + { + "command": "application:toggle-mode", + "rank": 1 + }, + { + "command": "application:toggle-presentation-mode", + "rank": 1 + }, + { + "type": "separator", + "rank": 2 + }, + { + "command": "application:toggle-left-area", + "rank": 2 + }, + { + "command": "application:toggle-right-area", + "rank": 2 + }, + { + "command": "apputils:activate-command-palette", + "rank": 0 + }, + { + "type": "separator", + "rank": 40 + }, + { + "type": "submenu", + "submenu": { + "id": "jp-mainmenu-view-codemirror-theme", + "label": "Text Editor Syntax Highlighting" + }, + "rank": 40 + }, + { + "type": "separator", + "rank": 40 + }, + { + "command": "debugger:show-panel", + "rank": 5 + }, + { + "command": "filebrowser:toggle-hidden-files", + "rank": 9.95 + }, + { + "type": "separator" + }, + { + "command": "logconsole:open" + }, + { + "type": "separator" + }, + { + "type": "separator", + "rank": 10 + }, + { + "command": "notebook:hide-cell-code", + "rank": 10 + }, + { + "command": "notebook:hide-cell-outputs", + "rank": 10 + }, + { + "command": "notebook:hide-all-cell-code", + "rank": 10 + }, + { + "command": "notebook:hide-all-cell-outputs", + "rank": 10 + }, + { + "type": "separator", + "rank": 10 + }, + { + "command": "notebook:show-cell-code", + "rank": 11 + }, + { + "command": "notebook:show-cell-outputs", + "rank": 11 + }, + { + "command": "notebook:show-all-cell-code", + "rank": 11 + }, + { + "command": "notebook:show-all-cell-outputs", + "rank": 11 + }, + { + "type": "separator", + "rank": 11 + }, + { + "command": "notebook:toggle-render-side-by-side-current", + "rank": 12 + }, + { + "type": "separator", + "rank": 12 + }, + { + "type": "separator", + "rank": 1 + }, + { + "command": "statusbar:toggle", + "rank": 1 + }, + { + "type": "separator", + "rank": 1 + }, + { + "type": "separator" + }, + { + "command": "RISE:preview" + }, + { + "command": "RISE:fullscreen-plugin" + }, + { + "type": "separator" + } + ], + "label": "View", + "mnemonic": -1, + "rank": 3 + }, + { + "disabled": false, + "id": "jp-mainmenu-run", + "items": [ + { + "command": "runmenu:run", + "rank": 0 + }, + { + "type": "separator" + }, + { + "command": "runmenu:run-all", + "rank": 999 + }, + { + "command": "runmenu:restart-and-run-all", + "rank": 999 + }, + { + "type": "separator", + "rank": 10 + }, + { + "command": "notebook:run-cell-and-insert-below", + "rank": 10 + }, + { + "command": "notebook:run-cell", + "rank": 10 + }, + { + "command": "notebook:run-in-console", + "rank": 10 + }, + { + "type": "separator", + "rank": 11 + }, + { + "command": "notebook:run-all-above", + "rank": 11 + }, + { + "command": "notebook:run-all-below", + "rank": 11 + }, + { + "type": "separator", + "rank": 12 + }, + { + "command": "notebook:render-all-markdown", + "rank": 12 + }, + { + "type": "separator", + "rank": 12 + } + ], + "label": "Run", + "mnemonic": -1, + "rank": 4 + }, + { + "disabled": false, + "id": "jp-mainmenu-kernel", + "items": [ + { + "command": "kernelmenu:interrupt", + "rank": 0 + }, + { + "type": "separator", + "rank": 1 + }, + { + "command": "kernelmenu:restart", + "rank": 1 + }, + { + "command": "kernelmenu:restart-and-clear", + "rank": 1 + }, + { + "command": "runmenu:restart-and-run-all", + "rank": 1.1 + }, + { + "type": "separator", + "rank": 1.5 + }, + { + "command": "kernelmenu:reconnect-to-kernel", + "rank": 1.5 + }, + { + "type": "separator", + "rank": 2 + }, + { + "command": "kernelmenu:shutdown", + "rank": 2 + }, + { + "command": "kernelmenu:shutdownAll", + "rank": 2 + }, + { + "type": "separator", + "rank": 3 + }, + { + "command": "kernelmenu:change", + "rank": 3 + }, + { + "type": "separator", + "rank": 1.2 + }, + { + "command": "debugger:restart-debug", + "rank": 1.2 + }, + { + "command": "notebook:restart-and-run-to-selected", + "rank": 1 + } + ], + "label": "Kernel", + "mnemonic": -1, + "rank": 5 + }, + { + "disabled": false, + "id": "jp-mainmenu-tabs", + "items": [ + { + "command": "application:activate-next-tab", + "rank": 0 + }, + { + "command": "application:activate-previous-tab", + "rank": 0 + }, + { + "command": "application:activate-next-tab-bar", + "rank": 0 + }, + { + "command": "application:activate-previous-tab-bar", + "rank": 0 + }, + { + "command": "tabsmenu:activate-previously-used-tab", + "rank": 0 + } + ], + "label": "Tabs", + "mnemonic": -1, + "rank": 500 + }, + { + "disabled": false, + "id": "jp-mainmenu-settings", + "items": [ + { + "command": "settingeditor:open", + "rank": 1000 + }, + { + "type": "separator", + "rank": 4 + }, + { + "disabled": false, + "command": "docmanager:toggle-autosave", + "rank": 4 + }, + { + "disabled": false, + "command": "docmanager:toggle-name-file-on-save", + "rank": 4 + }, + { + "type": "separator", + "rank": 4 + }, + { + "type": "submenu", + "submenu": { + "disabled": false, + "id": "jp-mainmenu-settings-apputilstheme", + "label": "Theme", + "items": [ + { + "type": "separator" + }, + { + "command": "apputils:theme-scrollbars" + }, + { + "type": "separator" + }, + { + "disabled": false, + "command": "apputils:incr-font-size", + "args": { + "key": "code-font-size" + } + }, + { + "disabled": false, + "command": "apputils:decr-font-size", + "args": { + "key": "code-font-size" + } + }, + { + "type": "separator" + }, + { + "disabled": false, + "command": "apputils:incr-font-size", + "args": { + "key": "content-font-size1" + } + }, + { + "disabled": false, + "command": "apputils:decr-font-size", + "args": { + "key": "content-font-size1" + } + }, + { + "type": "separator" + }, + { + "command": "apputils:incr-font-size", + "args": { + "key": "ui-font-size1" + } + }, + { + "command": "apputils:decr-font-size", + "args": { + "key": "ui-font-size1" + } + } + ] + }, + "rank": 0 + }, + { + "type": "submenu", + "disabled": false, + "submenu": { + "id": "jp-mainmenu-view-codemirror-keymap", + "disabled": false, + "label": "Text Editor Key Map", + "items": [ + { + "command": "codemirror:change-keymap", + "args": { + "keyMap": "default" + } + }, + { + "command": "codemirror:change-keymap", + "args": { + "keyMap": "sublime" + } + }, + { + "command": "codemirror:change-keymap", + "args": { + "keyMap": "vim" + } + }, + { + "command": "codemirror:change-keymap", + "args": { + "keyMap": "emacs" + } + } + ] + }, + "rank": 31 + }, + { + "type": "submenu", + "disabled": false, + "submenu": { + "id": "jp-mainmenu-view-codemirror-theme", + "label": "Text Editor Theme", + "items": [ + { + "command": "codemirror:change-theme", + "args": { + "theme": "jupyter" + } + }, + { + "command": "codemirror:change-theme", + "args": { + "theme": "default" + } + }, + { + "command": "codemirror:change-theme", + "args": { + "theme": "abcdef" + } + }, + { + "command": "codemirror:change-theme", + "args": { + "theme": "base16-dark" + } + }, + { + "command": "codemirror:change-theme", + "args": { + "theme": "base16-light" + } + }, + { + "command": "codemirror:change-theme", + "args": { + "theme": "hopscotch" + } + }, + { + "command": "codemirror:change-theme", + "args": { + "theme": "material" + } + }, + { + "command": "codemirror:change-theme", + "args": { + "theme": "mbo" + } + }, + { + "command": "codemirror:change-theme", + "args": { + "theme": "mdn-like" + } + }, + { + "command": "codemirror:change-theme", + "args": { + "theme": "seti" + } + }, + { + "command": "codemirror:change-theme", + "args": { + "theme": "solarized dark" + } + }, + { + "command": "codemirror:change-theme", + "args": { + "theme": "solarized light" + } + }, + { + "command": "codemirror:change-theme", + "args": { + "theme": "the-matrix" + } + }, + { + "command": "codemirror:change-theme", + "args": { + "theme": "xq-light" + } + }, + { + "command": "codemirror:change-theme", + "args": { + "theme": "zenburn" + } + } + ] + }, + "rank": 31 + }, + { + "type": "separator", + "rank": 39 + }, + { + "type": "separator", + "rank": 9 + }, + { + "type": "submenu", + "disabled": false, + "submenu": { + "id": "jp-mainmenu-settings-consoleexecute", + "label": "Console Run Keystroke", + "items": [ + { + "command": "console:interaction-mode", + "args": { + "interactionMode": "terminal" + } + }, + { + "command": "console:interaction-mode", + "args": { + "interactionMode": "notebook" + } + } + ] + }, + "rank": 9 + }, + { + "type": "separator", + "rank": 9 + }, + { + "type": "separator", + "rank": 100 + }, + { + "disabled": false, + "command": "extensionmanager:toggle", + "rank": 100 + }, + { + "type": "separator", + "rank": 100 + }, + { + "type": "separator", + "rank": 5 + }, + { + "command": "filebrowser:toggle-navigate-to-current-directory", + "rank": 5 + }, + { + "type": "separator", + "rank": 5 + }, + { + "type": "separator", + "rank": 1 + }, + { + "type": "submenu", + "rank": 1, + "submenu": { + "id": "jp-mainmenu-settings-language", + "label": "Language" + } + }, + { + "type": "separator", + "rank": 1 + } + ], + "label": "Settings", + "mnemonic": -1, + "rank": 999 + }, + { + "disabled": false, + "id": "jp-mainmenu-help", + "items": [ + { + "command": "help:about", + "rank": 0 + }, + { + "type": "separator", + "rank": 0 + }, + { + "type": "separator", + "rank": 2 + }, + { + "disabled": false, + "command": "help:jupyter-forum", + "rank": 2 + }, + { + "type": "separator", + "rank": 2 + }, + { + "type": "separator", + "rank": 1 + }, + { + "disabled": false, + "command": "help:launch-classic-notebook", + "rank": 1 + }, + { + "type": "separator", + "rank": 1 + }, + { + "type": "separator", + "rank": 0.1 + }, + { + "disabled": false, + "command": "inspector:open", + "rank": 0.1 + }, + { + "type": "separator", + "rank": 0.1 + } + ], + "label": "Help", + "mnemonic": -1, + "rank": 1000 + } + ] + } +} diff --git a/containers/default-class-container/base/start-notebook.d/ope-sartup.sh b/containers/default-class-container/base/start-notebook.d/ope-sartup.sh new file mode 100644 index 0000000..a6beb63 --- /dev/null +++ b/containers/default-class-container/base/start-notebook.d/ope-sartup.sh @@ -0,0 +1,80 @@ +#!/bin/bash +#set -x +SN=ope-startup.sh +[[ -z $MOUNT_DIR ]] && export MOUNT_DIR=/opt/app-root/src +id +# this script is designed to be run by the jupyter stack /usr/local/bin/start.sh +echo "$0: $SN: BEGIN: $(id -a) : $HOME " + +if [[ ! -w $HOME ]] ; then + echo "$SN: home directory $HOME is not writeable: skipping customizations" +else +if [[ -d $MOUNT_DIR ]]; then + echo "$SN: Found $MOUNT_DIR" + if [[ ! -w $MOUNT_DIR ]]; then + echo "$SN: $MOUNT_DIR not writeable skipping mappings" + else + FILE=.gitconfig + echo "$SN: Mapping $FILE to $MOUNT_DIR/$FILE" + if [[ ! -a $HOME/$FILE ]]; then + if [[ ! -a $MOUNT_DIR/$FILE ]]; then + echo "$SN: creating $MOUNT_DIR/$FILE" + touch $MOUNT_DIR/$FILE + fi + if [[ -a $MOUNT_DIR/$FILE ]]; then + echo "$SN: Linking $MOUNT_DIR/$FILE -> $HOME/$FILE" + ln -s $MOUNT_DIR/$FILE $HOME/$FILE + fi + fi + DIR=.ssh + echo "$SN: Mapping $FILE to $MOUNT_DIR/$FILE" + if [[ ! -d $HOME/$DIR ]]; then + if [[ ! -d $MOUNT_DIR/$DIR ]]; then + echo "$SN: creating $MOUNT_DIR/$DIR" + if mkdir $MOUNT_DIR/$DIR; then + if [[ $DIR == .ssh ]]; then + echo "$SN: Fixing permisions for $MOUNT/$DIR" + chmod 700 $MOUNT_DIR/$DIR + fi + fi + fi + if [[ -d $MOUNT_DIR/$DIR ]]; then + echo "$SN: Linking $MOUNT_DIR/$DIR -> $HOME/$DIR" + ln -s $MOUNT_DIR/$DIR $HOME/$DIR + if [[ $DIR = .ssh ]]; then + # As per the standard jupyter startup scripts we use the following test to + # figure out if we have been started in a jupyterhub env or if we have started + # on openshift + if [[ -n "${JUPYTERHUB_API_TOKEN}" || ${JUPYTER_IMAGE} =~ openshift ]]; then + # during jupyter stacks startup logic "fixes permissions" on home directories + # this causes group permissions to be set. We undo this with a hammer on .ssh + echo "$SN: Fixing permissions in $HOME/$DIR" + chmod -R go-rwxs $HOME/$DIR + fi + fi + fi + fi + + if [[ ! -L $HOME/.jupyter/lab && -a $MOUNT_DIR ]]; then + if [[ ! -a $MOUNT_DIR/jupyter/lab ]]; then + mkdir -p $MOUNT_DIR/jupyter/lab + fi + [[ -a $HOME/.jupyter/lab ]] && mv $HOME/.jupyter/lab $HOME/.jupyter/lab.old + echo "$SN: linking: ln -s /opt/app-root/src/jupyter/lab ~/.jupyter/lab" + ln -s $MOUNT_DIR/jupyter/lab $HOME/.jupyter/lab + fi + + if [[ -a $MOUNT_DIR/.myjupyter_start.sh ]]; then + echo "$SN: Found $MOUNT_DIR/.myjupyter_start.sh: sourcing it" + . $MOUNT_DIR/.myjupyter_start.sh + fi + fi +fi + +fi + +# force classic notebook interface when run from start-singleuser.sh (aka we are on jupyter hub) +#export JUPYTERHUB_SINGLEUSER_APP='notebook.notebookapp.NotebookApp' +#echo "$SN: forced notebook interface on JupyterHub: JUPYTERHUB_SINGLEUSER_APP=$JUPYTERHUB_SINGLEUSER_APP" + +echo "$0: $SN: END" diff --git a/containers/default/requirements/requirements.txt b/containers/default-class-container/requirements/requirements.txt similarity index 62% rename from containers/default/requirements/requirements.txt rename to containers/default-class-container/requirements/requirements.txt index 1429385..2c2016e 100644 --- a/containers/default/requirements/requirements.txt +++ b/containers/default-class-container/requirements/requirements.txt @@ -3,9 +3,8 @@ matplotlib numpy pandas jq -ipywidgets -notebook +ipywidgets +notebook nbconvert -jupyterlab +jupyterlab nbclient - diff --git a/containers/default/base/base_tag b/containers/default/base/base_tag deleted file mode 100644 index 231410b..0000000 --- a/containers/default/base/base_tag +++ /dev/null @@ -1 +0,0 @@ -:x86_64-python-3.9.13 \ No newline at end of file diff --git a/containers/default/base/pip_pkgs b/containers/default/base/pip_pkgs deleted file mode 100644 index 28b1a83..0000000 --- a/containers/default/base/pip_pkgs +++ /dev/null @@ -1 +0,0 @@ -jupyterquiz \ No newline at end of file diff --git a/containers/default/base/settings/overrides.json b/containers/default/base/settings/overrides.json deleted file mode 100644 index 066c390..0000000 --- a/containers/default/base/settings/overrides.json +++ /dev/null @@ -1,1050 +0,0 @@ -{ - "@jupyterlab/terminal-extension:plugin": { - "shutdownOnClose": true - }, - "@jupyterlab/statusbar-extension:plugin": { - "visible": false - }, - "@jupyterlab/mainmenu-extension:plugin": { - "menus": [ - { - "disabled": false, - "id": "jp-mainmenu-file", - "items": [ - { - "type": "submenu", - "submenu": { - "id": "jp-mainmenu-file-new", - "label": "New", - "items": [ - { - "disabled": false, - "command": "console:create", - "rank": 1 - }, - { - "disabled": false, - "command": "notebook:create-new", - "rank": 10 - } - ] - }, - "rank": 0 - }, - { - "type": "separator", - "rank": 2 - }, - { - "disabled": false, - "command": "filemenu:create-console", - "rank": 2.1 - }, - { - "disabled": false, - "command": "filemenu:close-and-cleanup", - "rank": 3.1 - }, - { - "type": "separator", - "rank": 99 - }, - { - "disabled": false, - "command": "filemenu:logout", - "rank": 99 - }, - { - "disabled": false, - "command": "filemenu:shutdown", - "rank": 99 - }, - { - "disabled": false, - "command": "docmanager:clone", - "rank": 2 - }, - { - "type": "separator", - "rank": 4 - }, - { - "disabled": false, - "command": "docmanager:save", - "rank": 4 - }, - { - "disabled": false, - "command": "docmanager:save-as", - "rank": 4 - }, - { - "disabled": false, - "command": "docmanager:save-all", - "rank": 4 - }, - { - "type": "separator", - "rank": 5 - }, - { - "disabled": false, - "command": "docmanager:reload", - "rank": 5 - }, - { - "disabled": false, - "command": "docmanager:restore-checkpoint", - "rank": 5 - }, - { - "disabled": false, - "command": "docmanager:rename", - "rank": 5 - }, - { - "type": "separator", - "rank": 3 - }, - { - "disabled": false, - "command": "application:close", - "rank": 3 - }, - { - "disabled": false, - "command": "application:close-all", - "rank": 3.2 - }, - { - "disabled": false, - "type": "separator", - "rank": 98 - }, - { - "disabled": false, - "command": "apputils:print", - "rank": 98 - }, - { - "disabled": false, - "command": "workspace-ui:save-as", - "rank": 40 - }, - { - "disabled": false, - "command": "workspace-ui:save", - "rank": 40 - }, - { - "type": "separator", - "rank": 6 - }, - { - "disabled": false, - "command": "docmanager:download", - "rank": 6 - }, - { - "type": "separator", - "rank": 6 - }, - { - "disabled": false, - "command": "filebrowser:create-main-launcher", - "rank": 1 - }, - { - "type": "separator", - "rank": 1 - }, - { - "disabled": false, - "command": "filebrowser:open-path", - "rank": 1 - }, - { - "disabled": false, - "command": "filebrowser:open-url", - "rank": 1 - }, - { - "type": "separator", - "rank": 100 - }, - { - "command": "hub:control-panel", - "rank": 100 - }, - { - "command": "hub:logout", - "rank": 100 - }, - { - "type": "separator", - "rank": 100 - }, - { - "type": "separator", - "rank": 10 - }, - { - "type": "submenu", - "disabled": false, - "rank": 10, - "submenu": { - "id": "jp-mainmenu-file-notebookexport", - "label": "Save and Export Notebook As…" - } - }, - { - "type": "separator", - "rank": 10 - } - ], - "label": "File", - "mnemonic": -1, - "rank": 1 - }, - { - "disabled": false, - "id": "jp-mainmenu-edit", - "items": [ - { - "command": "editmenu:undo", - "rank": 0 - }, - { - "command": "editmenu:redo", - "rank": 0 - }, - { - "type": "separator", - "rank": 10 - }, - { - "command": "editmenu:clear-current", - "rank": 10 - }, - { - "command": "editmenu:clear-all", - "rank": 10 - }, - { - "type": "separator", - "rank": 200 - }, - { - "command": "editmenu:go-to-line", - "rank": 200 - }, - { - "type": "separator", - "rank": 10 - }, - { - "command": "documentsearch:start", - "rank": 10 - }, - { - "command": "documentsearch:highlightNext", - "rank": 10 - }, - { - "command": "documentsearch:highlightPrevious", - "rank": 10 - }, - { - "type": "separator", - "rank": 10 - }, - { - "type": "separator", - "rank": 4 - }, - { - "command": "notebook:undo-cell-action", - "rank": 4 - }, - { - "command": "notebook:redo-cell-action", - "rank": 4 - }, - { - "type": "separator", - "rank": 5 - }, - { - "command": "notebook:cut-cell", - "rank": 5 - }, - { - "command": "notebook:copy-cell", - "rank": 5 - }, - { - "command": "notebook:paste-cell-below", - "rank": 5 - }, - { - "command": "notebook:paste-cell-above", - "rank": 5 - }, - { - "command": "notebook:paste-and-replace-cell", - "rank": 5 - }, - { - "type": "separator", - "rank": 6 - }, - { - "command": "notebook:delete-cell", - "rank": 6 - }, - { - "type": "separator", - "rank": 7 - }, - { - "command": "notebook:select-all", - "rank": 7 - }, - { - "command": "notebook:deselect-all", - "rank": 7 - }, - { - "type": "separator", - "rank": 8 - }, - { - "command": "notebook:move-cell-up", - "rank": 8 - }, - { - "command": "notebook:move-cell-down", - "rank": 8 - }, - { - "type": "separator", - "rank": 9 - }, - { - "command": "notebook:split-cell-at-cursor", - "rank": 9 - }, - { - "command": "notebook:merge-cells", - "rank": 9 - }, - { - "command": "notebook:merge-cell-above", - "rank": 9 - }, - { - "command": "notebook:merge-cell-below", - "rank": 9 - }, - { - "type": "separator", - "rank": 9 - } - ], - "label": "Edit", - "mnemonic": -1, - "rank": 2 - }, - { - "disabled": false, - "id": "jp-mainmenu-view", - "items": [ - { - "type": "separator", - "rank": 10 - }, - { - "command": "viewmenu:line-numbering", - "rank": 10 - }, - { - "command": "viewmenu:match-brackets", - "rank": 10 - }, - { - "command": "viewmenu:word-wrap", - "rank": 10 - }, - { - "type": "separator", - "rank": 1 - }, - { - "command": "application:toggle-mode", - "rank": 1 - }, - { - "command": "application:toggle-presentation-mode", - "rank": 1 - }, - { - "type": "separator", - "rank": 2 - }, - { - "command": "application:toggle-left-area", - "rank": 2 - }, - { - "command": "application:toggle-right-area", - "rank": 2 - }, - { - "command": "apputils:activate-command-palette", - "rank": 0 - }, - { - "type": "separator", - "rank": 40 - }, - { - "type": "submenu", - "submenu": { - "id": "jp-mainmenu-view-codemirror-theme", - "label": "Text Editor Syntax Highlighting" - }, - "rank": 40 - }, - { - "type": "separator", - "rank": 40 - }, - { - "command": "debugger:show-panel", - "rank": 5 - }, - { - "command": "filebrowser:toggle-hidden-files", - "rank": 9.95 - }, - { - "type": "separator" - }, - { - "command": "logconsole:open" - }, - { - "type": "separator" - }, - { - "type": "separator", - "rank": 10 - }, - { - "command": "notebook:hide-cell-code", - "rank": 10 - }, - { - "command": "notebook:hide-cell-outputs", - "rank": 10 - }, - { - "command": "notebook:hide-all-cell-code", - "rank": 10 - }, - { - "command": "notebook:hide-all-cell-outputs", - "rank": 10 - }, - { - "type": "separator", - "rank": 10 - }, - { - "command": "notebook:show-cell-code", - "rank": 11 - }, - { - "command": "notebook:show-cell-outputs", - "rank": 11 - }, - { - "command": "notebook:show-all-cell-code", - "rank": 11 - }, - { - "command": "notebook:show-all-cell-outputs", - "rank": 11 - }, - { - "type": "separator", - "rank": 11 - }, - { - "command": "notebook:toggle-render-side-by-side-current", - "rank": 12 - }, - { - "type": "separator", - "rank": 12 - }, - { - "type": "separator", - "rank": 1 - }, - { - "command": "statusbar:toggle", - "rank": 1 - }, - { - "type": "separator", - "rank": 1 - }, - { - "type": "separator" - }, - { - "command": "RISE:preview" - }, - { - "command": "RISE:fullscreen-plugin" - }, - { - "type": "separator" - } - ], - "label": "View", - "mnemonic": -1, - "rank": 3 - }, - { - "disabled": false, - "id": "jp-mainmenu-run", - "items": [ - { - "command": "runmenu:run", - "rank": 0 - }, - { - "type": "separator" - }, - { - "command": "runmenu:run-all", - "rank": 999 - }, - { - "command": "runmenu:restart-and-run-all", - "rank": 999 - }, - { - "type": "separator", - "rank": 10 - }, - { - "command": "notebook:run-cell-and-insert-below", - "rank": 10 - }, - { - "command": "notebook:run-cell", - "rank": 10 - }, - { - "command": "notebook:run-in-console", - "rank": 10 - }, - { - "type": "separator", - "rank": 11 - }, - { - "command": "notebook:run-all-above", - "rank": 11 - }, - { - "command": "notebook:run-all-below", - "rank": 11 - }, - { - "type": "separator", - "rank": 12 - }, - { - "command": "notebook:render-all-markdown", - "rank": 12 - }, - { - "type": "separator", - "rank": 12 - } - ], - "label": "Run", - "mnemonic": -1, - "rank": 4 - }, - { - "disabled": false, - "id": "jp-mainmenu-kernel", - "items": [ - { - "command": "kernelmenu:interrupt", - "rank": 0 - }, - { - "type": "separator", - "rank": 1 - }, - { - "command": "kernelmenu:restart", - "rank": 1 - }, - { - "command": "kernelmenu:restart-and-clear", - "rank": 1 - }, - { - "command": "runmenu:restart-and-run-all", - "rank": 1.1 - }, - { - "type": "separator", - "rank": 1.5 - }, - { - "command": "kernelmenu:reconnect-to-kernel", - "rank": 1.5 - }, - { - "type": "separator", - "rank": 2 - }, - { - "command": "kernelmenu:shutdown", - "rank": 2 - }, - { - "command": "kernelmenu:shutdownAll", - "rank": 2 - }, - { - "type": "separator", - "rank": 3 - }, - { - "command": "kernelmenu:change", - "rank": 3 - }, - { - "type": "separator", - "rank": 1.2 - }, - { - "command": "debugger:restart-debug", - "rank": 1.2 - }, - { - "command": "notebook:restart-and-run-to-selected", - "rank": 1 - } - ], - "label": "Kernel", - "mnemonic": -1, - "rank": 5 - }, - { - "disabled": false, - "id": "jp-mainmenu-tabs", - "items": [ - { - "command": "application:activate-next-tab", - "rank": 0 - }, - { - "command": "application:activate-previous-tab", - "rank": 0 - }, - { - "command": "application:activate-next-tab-bar", - "rank": 0 - }, - { - "command": "application:activate-previous-tab-bar", - "rank": 0 - }, - { - "command": "tabsmenu:activate-previously-used-tab", - "rank": 0 - } - ], - "label": "Tabs", - "mnemonic": -1, - "rank": 500 - }, - { - "disabled": false, - "id": "jp-mainmenu-settings", - "items": [ - { - "command": "settingeditor:open", - "rank": 1000 - }, - { - "type": "separator", - "rank": 4 - }, - { - "disabled": false, - "command": "docmanager:toggle-autosave", - "rank": 4 - }, - { - "disabled": false, - "command": "docmanager:toggle-name-file-on-save", - "rank": 4 - }, - { - "type": "separator", - "rank": 4 - }, - { - "type": "submenu", - "submenu": { - "disabled": false, - "id": "jp-mainmenu-settings-apputilstheme", - "label": "Theme", - "items": [ - { - "type": "separator" - }, - { - "command": "apputils:theme-scrollbars" - }, - { - "type": "separator" - }, - { - "disabled": false, - "command": "apputils:incr-font-size", - "args": { - "key": "code-font-size" - } - }, - { - "disabled": false, - "command": "apputils:decr-font-size", - "args": { - "key": "code-font-size" - } - }, - { - "type": "separator" - }, - { - "disabled": false, - "command": "apputils:incr-font-size", - "args": { - "key": "content-font-size1" - } - }, - { - "disabled": false, - "command": "apputils:decr-font-size", - "args": { - "key": "content-font-size1" - } - }, - { - "type": "separator" - }, - { - "command": "apputils:incr-font-size", - "args": { - "key": "ui-font-size1" - } - }, - { - "command": "apputils:decr-font-size", - "args": { - "key": "ui-font-size1" - } - } - ] - }, - "rank": 0 - }, - { - "type": "submenu", - "disabled": false, - "submenu": { - "id": "jp-mainmenu-view-codemirror-keymap", - "disabled": false, - "label": "Text Editor Key Map", - "items": [ - { - "command": "codemirror:change-keymap", - "args": { - "keyMap": "default" - } - }, - { - "command": "codemirror:change-keymap", - "args": { - "keyMap": "sublime" - } - }, - { - "command": "codemirror:change-keymap", - "args": { - "keyMap": "vim" - } - }, - { - "command": "codemirror:change-keymap", - "args": { - "keyMap": "emacs" - } - } - ] - }, - "rank": 31 - }, - { - "type": "submenu", - "disabled": false, - "submenu": { - "id": "jp-mainmenu-view-codemirror-theme", - "label": "Text Editor Theme", - "items": [ - { - "command": "codemirror:change-theme", - "args": { - "theme": "jupyter" - } - }, - { - "command": "codemirror:change-theme", - "args": { - "theme": "default" - } - }, - { - "command": "codemirror:change-theme", - "args": { - "theme": "abcdef" - } - }, - { - "command": "codemirror:change-theme", - "args": { - "theme": "base16-dark" - } - }, - { - "command": "codemirror:change-theme", - "args": { - "theme": "base16-light" - } - }, - { - "command": "codemirror:change-theme", - "args": { - "theme": "hopscotch" - } - }, - { - "command": "codemirror:change-theme", - "args": { - "theme": "material" - } - }, - { - "command": "codemirror:change-theme", - "args": { - "theme": "mbo" - } - }, - { - "command": "codemirror:change-theme", - "args": { - "theme": "mdn-like" - } - }, - { - "command": "codemirror:change-theme", - "args": { - "theme": "seti" - } - }, - { - "command": "codemirror:change-theme", - "args": { - "theme": "solarized dark" - } - }, - { - "command": "codemirror:change-theme", - "args": { - "theme": "solarized light" - } - }, - { - "command": "codemirror:change-theme", - "args": { - "theme": "the-matrix" - } - }, - { - "command": "codemirror:change-theme", - "args": { - "theme": "xq-light" - } - }, - { - "command": "codemirror:change-theme", - "args": { - "theme": "zenburn" - } - } - ] - }, - "rank": 31 - }, - { - "type": "separator", - "rank": 39 - }, - { - "type": "separator", - "rank": 9 - }, - { - "type": "submenu", - "disabled": false, - "submenu": { - "id": "jp-mainmenu-settings-consoleexecute", - "label": "Console Run Keystroke", - "items": [ - { - "command": "console:interaction-mode", - "args": { - "interactionMode": "terminal" - } - }, - { - "command": "console:interaction-mode", - "args": { - "interactionMode": "notebook" - } - } - ] - }, - "rank": 9 - }, - { - "type": "separator", - "rank": 9 - }, - { - "type": "separator", - "rank": 100 - }, - { - "disabled": false, - "command": "extensionmanager:toggle", - "rank": 100 - }, - { - "type": "separator", - "rank": 100 - }, - { - "type": "separator", - "rank": 5 - }, - { - "command": "filebrowser:toggle-navigate-to-current-directory", - "rank": 5 - }, - { - "type": "separator", - "rank": 5 - }, - { - "type": "separator", - "rank": 1 - }, - { - "type": "submenu", - "rank": 1, - "submenu": { - "id": "jp-mainmenu-settings-language", - "label": "Language" - } - }, - { - "type": "separator", - "rank": 1 - } - ], - "label": "Settings", - "mnemonic": -1, - "rank": 999 - }, - { - "disabled": false, - "id": "jp-mainmenu-help", - "items": [ - { - "command": "help:about", - "rank": 0 - }, - { - "type": "separator", - "rank": 0 - }, - { - "type": "separator", - "rank": 2 - }, - { - "disabled": false, - "command": "help:jupyter-forum", - "rank": 2 - }, - { - "type": "separator", - "rank": 2 - }, - { - "type": "separator", - "rank": 1 - }, - { - "disabled": false, - "command": "help:launch-classic-notebook", - "rank": 1 - }, - { - "type": "separator", - "rank": 1 - }, - { - "type": "separator", - "rank": 0.1 - }, - { - "disabled": false, - "command": "inspector:open", - "rank": 0.1 - }, - { - "type": "separator", - "rank": 0.1 - } - ], - "label": "Help", - "mnemonic": -1, - "rank": 1000 - } - ] - } -} diff --git a/containers/default/base/start-notebook.d/ope-sartup.sh b/containers/default/base/start-notebook.d/ope-sartup.sh deleted file mode 100644 index 06b24ef..0000000 --- a/containers/default/base/start-notebook.d/ope-sartup.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/bash -#set -x -SN=ope-startup.sh -[[ -z $MOUNT_DIR ]] && export MOUNT_DIR=/opt/app-root/src -id -# this script is designed to be run by the jupyter stack /usr/local/bin/start.sh -echo "$0: $SN: BEGIN: $(id -a) : $HOME " - -if [[ ! -w $HOME ]] ; then - echo "$SN: home directory $HOME is not writeable: skipping customizations" -else -if [[ -d $MOUNT_DIR ]]; then - echo "$SN: Found $MOUNT_DIR" - if [[ ! -w $MOUNT_DIR ]]; then - echo "$SN: $MOUNT_DIR not writeable skipping mappings" - else - FILE=.gitconfig - echo "$SN: Mapping $FILE to $MOUNT_DIR/$FILE" - if [[ ! -a $HOME/$FILE ]]; then - if [[ ! -a $MOUNT_DIR/$FILE ]]; then - echo "$SN: creating $MOUNT_DIR/$FILE" - touch $MOUNT_DIR/$FILE - fi - if [[ -a $MOUNT_DIR/$FILE ]]; then - echo "$SN: Linking $MOUNT_DIR/$FILE -> $HOME/$FILE" - ln -s $MOUNT_DIR/$FILE $HOME/$FILE - fi - fi - DIR=.ssh - echo "$SN: Mapping $FILE to $MOUNT_DIR/$FILE" - if [[ ! -d $HOME/$DIR ]]; then - if [[ ! -d $MOUNT_DIR/$DIR ]]; then - echo "$SN: creating $MOUNT_DIR/$DIR" - if mkdir $MOUNT_DIR/$DIR; then - if [[ $DIR == .ssh ]]; then - echo "$SN: Fixing permisions for $MOUNT/$DIR" - chmod 700 $MOUNT_DIR/$DIR - fi - fi - fi - if [[ -d $MOUNT_DIR/$DIR ]]; then - echo "$SN: Linking $MOUNT_DIR/$DIR -> $HOME/$DIR" - ln -s $MOUNT_DIR/$DIR $HOME/$DIR - if [[ $DIR = .ssh ]]; then - # As per the standard jupyter startup scripts we use the following test to - # figure out if we have been started in a jupyterhub env or if we have started - # on openshift - if [[ -n "${JUPYTERHUB_API_TOKEN}" || ${JUPYTER_IMAGE} =~ openshift ]]; then - # during jupyter stacks startup logic "fixes permissions" on home directories - # this causes group permissions to be set. We undo this with a hammer on .ssh - echo "$SN: Fixing permissions in $HOME/$DIR" - chmod -R go-rwxs $HOME/$DIR - fi - fi - fi - fi - - if [[ ! -L $HOME/.jupyter/lab && -a $MOUNT_DIR ]]; then - if [[ ! -a $MOUNT_DIR/jupyter/lab ]]; then - mkdir -p $MOUNT_DIR/jupyter/lab - fi - [[ -a $HOME/.jupyter/lab ]] && mv $HOME/.jupyter/lab $HOME/.jupyter/lab.old - echo "$SN: linking: ln -s /opt/app-root/src/jupyter/lab ~/.jupyter/lab" - ln -s $MOUNT_DIR/jupyter/lab $HOME/.jupyter/lab - fi - - if [[ -a $MOUNT_DIR/.myjupyter_start.sh ]]; then - echo "$SN: Found $MOUNT_DIR/.myjupyter_start.sh: sourcing it" - . $MOUNT_DIR/.myjupyter_start.sh - fi - fi -fi - -fi - -# force classic notebook interface when run from start-singleuser.sh (aka we are on jupyter hub) -#export JUPYTERHUB_SINGLEUSER_APP='notebook.notebookapp.NotebookApp' -#echo "$SN: forced notebook interface on JupyterHub: JUPYTERHUB_SINGLEUSER_APP=$JUPYTERHUB_SINGLEUSER_APP" - -echo "$0: $SN: END" diff --git a/containers/group-sync/Dockerfile b/containers/group-sync/Dockerfile new file mode 100644 index 0000000..38ca3e1 --- /dev/null +++ b/containers/group-sync/Dockerfile @@ -0,0 +1,14 @@ +FROM quay.io/operate-first/opf-toolbox:v0.8.0 + +# Create the destination directory +WORKDIR /scripts + +# Install pip first +RUN dnf install -y python3-pip + +# Install requirements first to maximize caching +COPY requirements.txt ./requirements.txt +RUN pip3 install -r requirements.txt + +# Install the group-sync application +COPY group-sync.py ./ diff --git a/containers/group-sync/group-sync.py b/containers/group-sync/group-sync.py new file mode 100644 index 0000000..a25aaa6 --- /dev/null +++ b/containers/group-sync/group-sync.py @@ -0,0 +1,54 @@ +import os +import sys +import openshift_client as oc +import logging + +LOG = logging.getLogger(__name__) + + +def add_users_to_group(group): + # Run the `oc get` command, capture the JSON output, and load the data + rolebinding = oc.selector('rolebinding/edit').object() + + users_in_rolebinding = set( + subject['name'] for subject in rolebinding.model.subjects + ) + # Handle case where group.model.users might be None + users_in_group = set(group.model.users) if group.model.users else set() + + users_to_add = users_in_rolebinding.difference(users_in_group) + users_to_remove = users_in_group.difference(users_in_rolebinding) + + group_name = group.model.metadata.name + LOG.info('adding to group %s: %s', group_name, users_to_add) + LOG.info('removing from group %s: %s', group_name, users_to_remove) + # Update the group's users list + group.patch({'users': list(users_in_rolebinding)}) + # Remove users from the group + for user in users_to_remove: + group.model.users.remove(user) + group.patch({'users': list(group.model.users)}) + + +if __name__ == '__main__': + # Use environment variables for group name and namespace + group_name = os.environ.get('GROUP_NAME') + namespace_name = os.environ.get('NAMESPACE') + + logging.basicConfig(level='INFO') + + # Check if the required environment variables are present + if not group_name or not namespace_name: + LOG.error('GROUP_NAME and NAMESPACE environment variables are required.') + sys.exit(1) + + # Check that group exists + try: + group = oc.selector(f'group/{group_name}').object() + except oc.model.OpenShiftPythonException: + LOG.error('Unable to find group %s', group_name) + sys.exit(1) + + # Run add_users_to_group in the given namespace + with oc.project(namespace_name): + add_users_to_group(group) diff --git a/containers/group-sync/requirements.txt b/containers/group-sync/requirements.txt new file mode 100644 index 0000000..eb2b582 --- /dev/null +++ b/containers/group-sync/requirements.txt @@ -0,0 +1 @@ +openshift-client diff --git a/containers/testing/.gitignore b/containers/testing/.gitignore index 5236e1e..b25c15b 100644 --- a/containers/testing/.gitignore +++ b/containers/testing/.gitignore @@ -1,2 +1 @@ *~ - diff --git a/containers/testing/Makefile b/containers/testing/Makefile index 584ac46..6a7fcce 100644 --- a/containers/testing/Makefile +++ b/containers/testing/Makefile @@ -38,7 +38,7 @@ PYTHON_INSTALL_PACKAGES := $(shell cat base/python_pkgs) PIP_INSTALL_PACKAGES := $(shell cat base/pip_pkgs) JUPYTER_ENABLE_EXTENSIONS := $(shell cat base/jupyter_enable_exts) -JUPYTER_DISABLE_EXTENSIONS := $(shell if [[ -a base/jupyter_disable_exts ]]; then cat base/jupyter_disable_exts; fi) +JUPYTER_DISABLE_EXTENSIONS := $(shell if [[ -a base/jupyter_disable_exts ]]; then cat base/jupyter_disable_exts; fi) # expand installation so that the image feels more like a proper UNIX user environment with man pages, etc. UNMIN := yes @@ -112,7 +112,7 @@ show-tag: ARGS ?= show-tag: DARGS ?= show-tag: ## tag current private build as beta @-echo $(OPE_BOOK_REG)$(OPE_BOOK_IMAGE)$(OPE_BETA_TAG) - + ### DEBUG TARGETS @@ -128,7 +128,7 @@ user: ## start private version with usershell to poke around ### PUBLIC USAGE TARGETS -pull: +pull: pull: ## pull most recent public version docker pull $(OPE_BOOK_REG)$(OPE_BOOK_IMAGE)$(OPE_PUBLIC_TAG) @@ -137,6 +137,4 @@ run: ARGS ?= run: DARGS ?= -u $(OPE_UID):$(OPE_GID) -v "${HOST_DIR}":"${MOUNT_DIR}" -v "${SSH_AUTH_SOCK}":"${SSH_AUTH_SOCK}" -v "${SSH_AUTH_SOCK}":"${SSH_AUTH_SOCK}" -e SSH_AUTH_SOCK=${SSH_AUTH_SOCK} -p ${SSH_PORT}:22 run: PORT ?= 8888 run: ## start published version with jupyter lab interface - docker run -it --rm -p $(PORT):$(PORT) $(DARGS) $(OPE_BOOK_REG)$(OPE_BOOK_IMAGE)$(OPE_PUBLIC_TAG) $(ARGS) - - + docker run -it --rm -p $(PORT):$(PORT) $(DARGS) $(OPE_BOOK_REG)$(OPE_BOOK_IMAGE)$(OPE_PUBLIC_TAG) $(ARGS) diff --git a/containers/testing/README.md b/containers/testing/README.md index dd8802a..93842d8 100644 --- a/containers/testing/README.md +++ b/containers/testing/README.md @@ -36,7 +36,7 @@ Checksum verifies that the checksum of the container image is correct. ## Size-and-Time-Display -Test pulls the beta image and runs it (needed for size)This test uses the build time recorded in the build to display this time and also queries the image to find its size and display that. +Test pulls the beta image and runs it (needed for size)This test uses the build time recorded in the build to display this time and also queries the image to find its size and display that. ## UI-Test @@ -51,7 +51,7 @@ Test pulls the beta image and runs it. It then runs a bash sript calling gdb 100 ## Approval -Sends approval authorization request to approved authorization, test then waits for authorization has been granted to proceed. +Sends approval authorization request to approved authorization, test then waits for authorization has been granted to proceed. ## Publish @@ -61,5 +61,3 @@ This test publishes the latest stable base image to the OPE quay.io repo ## NOTE In order to push / pull to the appropriate quay.io repo you must update registry_user and registry_password to the correct corresponding variables - - diff --git a/containers/testing/base/Dockerfile b/containers/testing/base/Dockerfile index 83aef98..92c699f 100755 --- a/containers/testing/base/Dockerfile +++ b/containers/testing/base/Dockerfile @@ -115,4 +115,3 @@ RUN chgrp ${OPE_GROUP} -R /home USER $NB_USER CMD ["/bin/bash", "-c", "cd /home/jovyan ; start-notebook.sh"] - diff --git a/containers/testing/base/base_image b/containers/testing/base/base_image index 92e9d0e..3d48630 100644 --- a/containers/testing/base/base_image +++ b/containers/testing/base/base_image @@ -1,2 +1 @@ jupyter/minimal-notebook - diff --git a/containers/testing/base/base_registry b/containers/testing/base/base_registry index d5703cd..243e482 100644 --- a/containers/testing/base/base_registry +++ b/containers/testing/base/base_registry @@ -1,2 +1 @@ docker.io - diff --git a/containers/testing/base/base_tag b/containers/testing/base/base_tag index 231410b..922ebee 100644 --- a/containers/testing/base/base_tag +++ b/containers/testing/base/base_tag @@ -1 +1 @@ -:x86_64-python-3.9.13 \ No newline at end of file +:x86_64-python-3.9.13 diff --git a/containers/testing/base/checksum b/containers/testing/base/checksum index 4017f45..b94b67e 100644 --- a/containers/testing/base/checksum +++ b/containers/testing/base/checksum @@ -1,2 +1 @@ fb2cc759cef3e1a8dd909c66373991a5f028f0bc6b2fcb76bdc7a5b04683b6a5 - diff --git a/containers/testing/base/customization_name b/containers/testing/base/customization_name index 0bafdcd..5c46288 100644 --- a/containers/testing/base/customization_name +++ b/containers/testing/base/customization_name @@ -1,2 +1 @@ base-ope-image - diff --git a/containers/testing/base/distro_pkgs b/containers/testing/base/distro_pkgs index 4dbaa34..c255aac 100644 --- a/containers/testing/base/distro_pkgs +++ b/containers/testing/base/distro_pkgs @@ -38,4 +38,4 @@ libnss3 lsb-release xdg-utils wget -dos2unix \ No newline at end of file +dos2unix diff --git a/containers/testing/base/jupyter_disable_exts b/containers/testing/base/jupyter_disable_exts index c4c5740..aed7d93 100644 --- a/containers/testing/base/jupyter_disable_exts +++ b/containers/testing/base/jupyter_disable_exts @@ -16,4 +16,3 @@ @jupyterlab/inspector-extension:inspector @jupyterlab/inspector-extension:notebooks @jupyterlab/shortcuts-extension:shortcuts - diff --git a/containers/testing/base/jupyter_enable_exts b/containers/testing/base/jupyter_enable_exts index 647daf3..89c5ff8 100644 --- a/containers/testing/base/jupyter_enable_exts +++ b/containers/testing/base/jupyter_enable_exts @@ -2,4 +2,3 @@ spellchecker/main splitcell/splitcell hide_input_all/main hide_input/main - diff --git a/containers/testing/base/mkversions b/containers/testing/base/mkversions index 3f26685..b042206 100755 --- a/containers/testing/base/mkversions +++ b/containers/testing/base/mkversions @@ -4,7 +4,7 @@ VERSIONS=base/mamba_versions echo $(while read pkg; do info=$(grep "^${pkg}" $VERSIONS) - if [[ -n $info ]]; then + if [[ -n $info ]]; then echo "$info" | while read pkg ver rest; do echo "${pkg}=$ver" done @@ -12,4 +12,3 @@ echo $(while read pkg; do echo "${pkg}" fi done) - diff --git a/containers/testing/base/ope_book b/containers/testing/base/ope_book index f7ebc12..f870b9d 100644 --- a/containers/testing/base/ope_book +++ b/containers/testing/base/ope_book @@ -1 +1 @@ -ope-test \ No newline at end of file +ope-test diff --git a/containers/testing/base/ope_book_registry b/containers/testing/base/ope_book_registry index 3156016..a485438 100644 --- a/containers/testing/base/ope_book_registry +++ b/containers/testing/base/ope_book_registry @@ -1 +1 @@ -quay.io \ No newline at end of file +quay.io diff --git a/containers/testing/base/ope_book_user b/containers/testing/base/ope_book_user index 74f8cb6..ffbc3d2 100644 --- a/containers/testing/base/ope_book_user +++ b/containers/testing/base/ope_book_user @@ -1 +1 @@ -opeffort \ No newline at end of file +opeffort diff --git a/containers/testing/base/ope_gid b/containers/testing/base/ope_gid index c227083..573541a 100644 --- a/containers/testing/base/ope_gid +++ b/containers/testing/base/ope_gid @@ -1 +1 @@ -0 \ No newline at end of file +0 diff --git a/containers/testing/base/ope_group b/containers/testing/base/ope_group index 93ca142..d8649da 100644 --- a/containers/testing/base/ope_group +++ b/containers/testing/base/ope_group @@ -1 +1 @@ -root \ No newline at end of file +root diff --git a/containers/testing/base/ope_registry_user b/containers/testing/base/ope_registry_user index b0025f7..d168d04 100644 --- a/containers/testing/base/ope_registry_user +++ b/containers/testing/base/ope_registry_user @@ -1 +1 @@ -rh-ee-istaplet \ No newline at end of file +rh-ee-istaplet diff --git a/containers/testing/base/pip_pkgs b/containers/testing/base/pip_pkgs index 28b1a83..cc7378e 100644 --- a/containers/testing/base/pip_pkgs +++ b/containers/testing/base/pip_pkgs @@ -1 +1 @@ -jupyterquiz \ No newline at end of file +jupyterquiz diff --git a/containers/testing/base/python_pkgs b/containers/testing/base/python_pkgs index 35c6dc3..2d38264 100644 --- a/containers/testing/base/python_pkgs +++ b/containers/testing/base/python_pkgs @@ -15,4 +15,4 @@ pyppeteer gdb jupyterlab-myst==1.1.3 jupyterlab==3.6.1 -numpy==1.24.3 \ No newline at end of file +numpy==1.24.3 diff --git a/containers/testing/base/python_prereqs b/containers/testing/base/python_prereqs index 0d2fd1b..c4aa2e6 100644 --- a/containers/testing/base/python_prereqs +++ b/containers/testing/base/python_prereqs @@ -1,2 +1,2 @@ nbclient==0.5.13 -jsonschema \ No newline at end of file +jsonschema diff --git a/containers/testing/base/settings/overrides.json b/containers/testing/base/settings/overrides.json index 066c390..2d8819b 100644 --- a/containers/testing/base/settings/overrides.json +++ b/containers/testing/base/settings/overrides.json @@ -6,1045 +6,1045 @@ "visible": false }, "@jupyterlab/mainmenu-extension:plugin": { - "menus": [ + "menus": [ { - "disabled": false, - "id": "jp-mainmenu-file", - "items": [ + "disabled": false, + "id": "jp-mainmenu-file", + "items": [ { - "type": "submenu", - "submenu": { + "type": "submenu", + "submenu": { "id": "jp-mainmenu-file-new", "label": "New", "items": [ - { + { "disabled": false, "command": "console:create", "rank": 1 - }, - { + }, + { "disabled": false, "command": "notebook:create-new", "rank": 10 - } + } ] - }, - "rank": 0 + }, + "rank": 0 }, { - "type": "separator", - "rank": 2 + "type": "separator", + "rank": 2 }, { - "disabled": false, - "command": "filemenu:create-console", - "rank": 2.1 + "disabled": false, + "command": "filemenu:create-console", + "rank": 2.1 }, { - "disabled": false, - "command": "filemenu:close-and-cleanup", - "rank": 3.1 + "disabled": false, + "command": "filemenu:close-and-cleanup", + "rank": 3.1 }, { - "type": "separator", - "rank": 99 + "type": "separator", + "rank": 99 }, { - "disabled": false, - "command": "filemenu:logout", - "rank": 99 + "disabled": false, + "command": "filemenu:logout", + "rank": 99 }, { - "disabled": false, - "command": "filemenu:shutdown", - "rank": 99 + "disabled": false, + "command": "filemenu:shutdown", + "rank": 99 }, { - "disabled": false, - "command": "docmanager:clone", - "rank": 2 + "disabled": false, + "command": "docmanager:clone", + "rank": 2 }, { - "type": "separator", - "rank": 4 + "type": "separator", + "rank": 4 }, { - "disabled": false, - "command": "docmanager:save", - "rank": 4 + "disabled": false, + "command": "docmanager:save", + "rank": 4 }, { - "disabled": false, - "command": "docmanager:save-as", - "rank": 4 + "disabled": false, + "command": "docmanager:save-as", + "rank": 4 }, { - "disabled": false, - "command": "docmanager:save-all", - "rank": 4 + "disabled": false, + "command": "docmanager:save-all", + "rank": 4 }, { - "type": "separator", - "rank": 5 + "type": "separator", + "rank": 5 }, { - "disabled": false, - "command": "docmanager:reload", - "rank": 5 + "disabled": false, + "command": "docmanager:reload", + "rank": 5 }, { - "disabled": false, - "command": "docmanager:restore-checkpoint", - "rank": 5 + "disabled": false, + "command": "docmanager:restore-checkpoint", + "rank": 5 }, { - "disabled": false, - "command": "docmanager:rename", - "rank": 5 + "disabled": false, + "command": "docmanager:rename", + "rank": 5 }, { - "type": "separator", - "rank": 3 + "type": "separator", + "rank": 3 }, { - "disabled": false, - "command": "application:close", - "rank": 3 + "disabled": false, + "command": "application:close", + "rank": 3 }, { - "disabled": false, - "command": "application:close-all", - "rank": 3.2 + "disabled": false, + "command": "application:close-all", + "rank": 3.2 }, { - "disabled": false, - "type": "separator", - "rank": 98 + "disabled": false, + "type": "separator", + "rank": 98 }, { - "disabled": false, - "command": "apputils:print", - "rank": 98 + "disabled": false, + "command": "apputils:print", + "rank": 98 }, { - "disabled": false, - "command": "workspace-ui:save-as", - "rank": 40 + "disabled": false, + "command": "workspace-ui:save-as", + "rank": 40 }, { - "disabled": false, - "command": "workspace-ui:save", - "rank": 40 + "disabled": false, + "command": "workspace-ui:save", + "rank": 40 }, { - "type": "separator", - "rank": 6 + "type": "separator", + "rank": 6 }, { - "disabled": false, - "command": "docmanager:download", - "rank": 6 + "disabled": false, + "command": "docmanager:download", + "rank": 6 }, { - "type": "separator", - "rank": 6 + "type": "separator", + "rank": 6 }, { - "disabled": false, - "command": "filebrowser:create-main-launcher", - "rank": 1 + "disabled": false, + "command": "filebrowser:create-main-launcher", + "rank": 1 }, { - "type": "separator", - "rank": 1 + "type": "separator", + "rank": 1 }, { - "disabled": false, - "command": "filebrowser:open-path", - "rank": 1 + "disabled": false, + "command": "filebrowser:open-path", + "rank": 1 }, { - "disabled": false, - "command": "filebrowser:open-url", - "rank": 1 + "disabled": false, + "command": "filebrowser:open-url", + "rank": 1 }, { - "type": "separator", - "rank": 100 + "type": "separator", + "rank": 100 }, { - "command": "hub:control-panel", - "rank": 100 + "command": "hub:control-panel", + "rank": 100 }, { - "command": "hub:logout", - "rank": 100 + "command": "hub:logout", + "rank": 100 }, { - "type": "separator", - "rank": 100 + "type": "separator", + "rank": 100 }, { - "type": "separator", - "rank": 10 + "type": "separator", + "rank": 10 }, { - "type": "submenu", - "disabled": false, - "rank": 10, - "submenu": { + "type": "submenu", + "disabled": false, + "rank": 10, + "submenu": { "id": "jp-mainmenu-file-notebookexport", "label": "Save and Export Notebook As…" - } + } }, { - "type": "separator", - "rank": 10 + "type": "separator", + "rank": 10 } - ], - "label": "File", - "mnemonic": -1, - "rank": 1 + ], + "label": "File", + "mnemonic": -1, + "rank": 1 }, { - "disabled": false, - "id": "jp-mainmenu-edit", - "items": [ + "disabled": false, + "id": "jp-mainmenu-edit", + "items": [ { - "command": "editmenu:undo", - "rank": 0 + "command": "editmenu:undo", + "rank": 0 }, { - "command": "editmenu:redo", - "rank": 0 + "command": "editmenu:redo", + "rank": 0 }, { - "type": "separator", - "rank": 10 + "type": "separator", + "rank": 10 }, { - "command": "editmenu:clear-current", - "rank": 10 + "command": "editmenu:clear-current", + "rank": 10 }, { - "command": "editmenu:clear-all", - "rank": 10 + "command": "editmenu:clear-all", + "rank": 10 }, { - "type": "separator", - "rank": 200 + "type": "separator", + "rank": 200 }, { - "command": "editmenu:go-to-line", - "rank": 200 + "command": "editmenu:go-to-line", + "rank": 200 }, { - "type": "separator", - "rank": 10 + "type": "separator", + "rank": 10 }, { - "command": "documentsearch:start", - "rank": 10 + "command": "documentsearch:start", + "rank": 10 }, { - "command": "documentsearch:highlightNext", - "rank": 10 + "command": "documentsearch:highlightNext", + "rank": 10 }, { - "command": "documentsearch:highlightPrevious", - "rank": 10 + "command": "documentsearch:highlightPrevious", + "rank": 10 }, { - "type": "separator", - "rank": 10 + "type": "separator", + "rank": 10 }, { - "type": "separator", - "rank": 4 + "type": "separator", + "rank": 4 }, { - "command": "notebook:undo-cell-action", - "rank": 4 + "command": "notebook:undo-cell-action", + "rank": 4 }, { - "command": "notebook:redo-cell-action", - "rank": 4 + "command": "notebook:redo-cell-action", + "rank": 4 }, { - "type": "separator", - "rank": 5 + "type": "separator", + "rank": 5 }, { - "command": "notebook:cut-cell", - "rank": 5 + "command": "notebook:cut-cell", + "rank": 5 }, { - "command": "notebook:copy-cell", - "rank": 5 + "command": "notebook:copy-cell", + "rank": 5 }, { - "command": "notebook:paste-cell-below", - "rank": 5 + "command": "notebook:paste-cell-below", + "rank": 5 }, { - "command": "notebook:paste-cell-above", - "rank": 5 + "command": "notebook:paste-cell-above", + "rank": 5 }, { - "command": "notebook:paste-and-replace-cell", - "rank": 5 + "command": "notebook:paste-and-replace-cell", + "rank": 5 }, { - "type": "separator", - "rank": 6 + "type": "separator", + "rank": 6 }, { - "command": "notebook:delete-cell", - "rank": 6 + "command": "notebook:delete-cell", + "rank": 6 }, { - "type": "separator", - "rank": 7 + "type": "separator", + "rank": 7 }, { - "command": "notebook:select-all", - "rank": 7 + "command": "notebook:select-all", + "rank": 7 }, { - "command": "notebook:deselect-all", - "rank": 7 + "command": "notebook:deselect-all", + "rank": 7 }, { - "type": "separator", - "rank": 8 + "type": "separator", + "rank": 8 }, { - "command": "notebook:move-cell-up", - "rank": 8 + "command": "notebook:move-cell-up", + "rank": 8 }, { - "command": "notebook:move-cell-down", - "rank": 8 + "command": "notebook:move-cell-down", + "rank": 8 }, { - "type": "separator", - "rank": 9 + "type": "separator", + "rank": 9 }, { - "command": "notebook:split-cell-at-cursor", - "rank": 9 + "command": "notebook:split-cell-at-cursor", + "rank": 9 }, { - "command": "notebook:merge-cells", - "rank": 9 + "command": "notebook:merge-cells", + "rank": 9 }, { - "command": "notebook:merge-cell-above", - "rank": 9 + "command": "notebook:merge-cell-above", + "rank": 9 }, { - "command": "notebook:merge-cell-below", - "rank": 9 + "command": "notebook:merge-cell-below", + "rank": 9 }, { - "type": "separator", - "rank": 9 + "type": "separator", + "rank": 9 } - ], - "label": "Edit", - "mnemonic": -1, - "rank": 2 + ], + "label": "Edit", + "mnemonic": -1, + "rank": 2 }, { - "disabled": false, - "id": "jp-mainmenu-view", - "items": [ + "disabled": false, + "id": "jp-mainmenu-view", + "items": [ { - "type": "separator", - "rank": 10 + "type": "separator", + "rank": 10 }, { - "command": "viewmenu:line-numbering", - "rank": 10 + "command": "viewmenu:line-numbering", + "rank": 10 }, { - "command": "viewmenu:match-brackets", - "rank": 10 + "command": "viewmenu:match-brackets", + "rank": 10 }, { - "command": "viewmenu:word-wrap", - "rank": 10 + "command": "viewmenu:word-wrap", + "rank": 10 }, { - "type": "separator", - "rank": 1 + "type": "separator", + "rank": 1 }, { - "command": "application:toggle-mode", - "rank": 1 + "command": "application:toggle-mode", + "rank": 1 }, { - "command": "application:toggle-presentation-mode", - "rank": 1 + "command": "application:toggle-presentation-mode", + "rank": 1 }, { - "type": "separator", - "rank": 2 + "type": "separator", + "rank": 2 }, { - "command": "application:toggle-left-area", - "rank": 2 + "command": "application:toggle-left-area", + "rank": 2 }, { - "command": "application:toggle-right-area", - "rank": 2 + "command": "application:toggle-right-area", + "rank": 2 }, { - "command": "apputils:activate-command-palette", - "rank": 0 + "command": "apputils:activate-command-palette", + "rank": 0 }, { - "type": "separator", - "rank": 40 + "type": "separator", + "rank": 40 }, { - "type": "submenu", - "submenu": { + "type": "submenu", + "submenu": { "id": "jp-mainmenu-view-codemirror-theme", "label": "Text Editor Syntax Highlighting" - }, - "rank": 40 + }, + "rank": 40 }, { - "type": "separator", - "rank": 40 + "type": "separator", + "rank": 40 }, { - "command": "debugger:show-panel", - "rank": 5 + "command": "debugger:show-panel", + "rank": 5 }, { - "command": "filebrowser:toggle-hidden-files", - "rank": 9.95 + "command": "filebrowser:toggle-hidden-files", + "rank": 9.95 }, { - "type": "separator" + "type": "separator" }, { - "command": "logconsole:open" + "command": "logconsole:open" }, { - "type": "separator" + "type": "separator" }, { - "type": "separator", - "rank": 10 + "type": "separator", + "rank": 10 }, { - "command": "notebook:hide-cell-code", - "rank": 10 + "command": "notebook:hide-cell-code", + "rank": 10 }, { - "command": "notebook:hide-cell-outputs", - "rank": 10 + "command": "notebook:hide-cell-outputs", + "rank": 10 }, { - "command": "notebook:hide-all-cell-code", - "rank": 10 + "command": "notebook:hide-all-cell-code", + "rank": 10 }, { - "command": "notebook:hide-all-cell-outputs", - "rank": 10 + "command": "notebook:hide-all-cell-outputs", + "rank": 10 }, { - "type": "separator", - "rank": 10 + "type": "separator", + "rank": 10 }, { - "command": "notebook:show-cell-code", - "rank": 11 + "command": "notebook:show-cell-code", + "rank": 11 }, { - "command": "notebook:show-cell-outputs", - "rank": 11 + "command": "notebook:show-cell-outputs", + "rank": 11 }, { - "command": "notebook:show-all-cell-code", - "rank": 11 + "command": "notebook:show-all-cell-code", + "rank": 11 }, { - "command": "notebook:show-all-cell-outputs", - "rank": 11 + "command": "notebook:show-all-cell-outputs", + "rank": 11 }, { - "type": "separator", - "rank": 11 + "type": "separator", + "rank": 11 }, { - "command": "notebook:toggle-render-side-by-side-current", - "rank": 12 + "command": "notebook:toggle-render-side-by-side-current", + "rank": 12 }, { - "type": "separator", - "rank": 12 + "type": "separator", + "rank": 12 }, { - "type": "separator", - "rank": 1 + "type": "separator", + "rank": 1 }, { - "command": "statusbar:toggle", - "rank": 1 + "command": "statusbar:toggle", + "rank": 1 }, { - "type": "separator", - "rank": 1 + "type": "separator", + "rank": 1 }, { - "type": "separator" + "type": "separator" }, { - "command": "RISE:preview" + "command": "RISE:preview" }, { - "command": "RISE:fullscreen-plugin" + "command": "RISE:fullscreen-plugin" }, { - "type": "separator" + "type": "separator" } - ], - "label": "View", - "mnemonic": -1, - "rank": 3 + ], + "label": "View", + "mnemonic": -1, + "rank": 3 }, { - "disabled": false, - "id": "jp-mainmenu-run", - "items": [ + "disabled": false, + "id": "jp-mainmenu-run", + "items": [ { - "command": "runmenu:run", - "rank": 0 + "command": "runmenu:run", + "rank": 0 }, { - "type": "separator" + "type": "separator" }, { - "command": "runmenu:run-all", - "rank": 999 + "command": "runmenu:run-all", + "rank": 999 }, { - "command": "runmenu:restart-and-run-all", - "rank": 999 + "command": "runmenu:restart-and-run-all", + "rank": 999 }, { - "type": "separator", - "rank": 10 + "type": "separator", + "rank": 10 }, { - "command": "notebook:run-cell-and-insert-below", - "rank": 10 + "command": "notebook:run-cell-and-insert-below", + "rank": 10 }, { - "command": "notebook:run-cell", - "rank": 10 + "command": "notebook:run-cell", + "rank": 10 }, { - "command": "notebook:run-in-console", - "rank": 10 + "command": "notebook:run-in-console", + "rank": 10 }, { - "type": "separator", - "rank": 11 + "type": "separator", + "rank": 11 }, { - "command": "notebook:run-all-above", - "rank": 11 + "command": "notebook:run-all-above", + "rank": 11 }, { - "command": "notebook:run-all-below", - "rank": 11 + "command": "notebook:run-all-below", + "rank": 11 }, { - "type": "separator", - "rank": 12 + "type": "separator", + "rank": 12 }, { - "command": "notebook:render-all-markdown", - "rank": 12 + "command": "notebook:render-all-markdown", + "rank": 12 }, { - "type": "separator", - "rank": 12 + "type": "separator", + "rank": 12 } - ], - "label": "Run", - "mnemonic": -1, - "rank": 4 + ], + "label": "Run", + "mnemonic": -1, + "rank": 4 }, { - "disabled": false, - "id": "jp-mainmenu-kernel", - "items": [ + "disabled": false, + "id": "jp-mainmenu-kernel", + "items": [ { - "command": "kernelmenu:interrupt", - "rank": 0 + "command": "kernelmenu:interrupt", + "rank": 0 }, { - "type": "separator", - "rank": 1 + "type": "separator", + "rank": 1 }, { - "command": "kernelmenu:restart", - "rank": 1 + "command": "kernelmenu:restart", + "rank": 1 }, { - "command": "kernelmenu:restart-and-clear", - "rank": 1 + "command": "kernelmenu:restart-and-clear", + "rank": 1 }, { - "command": "runmenu:restart-and-run-all", - "rank": 1.1 + "command": "runmenu:restart-and-run-all", + "rank": 1.1 }, { - "type": "separator", - "rank": 1.5 + "type": "separator", + "rank": 1.5 }, { - "command": "kernelmenu:reconnect-to-kernel", - "rank": 1.5 + "command": "kernelmenu:reconnect-to-kernel", + "rank": 1.5 }, { - "type": "separator", - "rank": 2 + "type": "separator", + "rank": 2 }, { - "command": "kernelmenu:shutdown", - "rank": 2 + "command": "kernelmenu:shutdown", + "rank": 2 }, { - "command": "kernelmenu:shutdownAll", - "rank": 2 + "command": "kernelmenu:shutdownAll", + "rank": 2 }, { - "type": "separator", - "rank": 3 + "type": "separator", + "rank": 3 }, { - "command": "kernelmenu:change", - "rank": 3 + "command": "kernelmenu:change", + "rank": 3 }, { - "type": "separator", - "rank": 1.2 + "type": "separator", + "rank": 1.2 }, { - "command": "debugger:restart-debug", - "rank": 1.2 + "command": "debugger:restart-debug", + "rank": 1.2 }, { - "command": "notebook:restart-and-run-to-selected", - "rank": 1 + "command": "notebook:restart-and-run-to-selected", + "rank": 1 } - ], - "label": "Kernel", - "mnemonic": -1, - "rank": 5 + ], + "label": "Kernel", + "mnemonic": -1, + "rank": 5 }, { - "disabled": false, - "id": "jp-mainmenu-tabs", - "items": [ + "disabled": false, + "id": "jp-mainmenu-tabs", + "items": [ { - "command": "application:activate-next-tab", - "rank": 0 + "command": "application:activate-next-tab", + "rank": 0 }, { - "command": "application:activate-previous-tab", - "rank": 0 + "command": "application:activate-previous-tab", + "rank": 0 }, { - "command": "application:activate-next-tab-bar", - "rank": 0 + "command": "application:activate-next-tab-bar", + "rank": 0 }, { - "command": "application:activate-previous-tab-bar", - "rank": 0 + "command": "application:activate-previous-tab-bar", + "rank": 0 }, { - "command": "tabsmenu:activate-previously-used-tab", - "rank": 0 + "command": "tabsmenu:activate-previously-used-tab", + "rank": 0 } - ], - "label": "Tabs", - "mnemonic": -1, - "rank": 500 + ], + "label": "Tabs", + "mnemonic": -1, + "rank": 500 }, { - "disabled": false, - "id": "jp-mainmenu-settings", - "items": [ + "disabled": false, + "id": "jp-mainmenu-settings", + "items": [ { - "command": "settingeditor:open", - "rank": 1000 + "command": "settingeditor:open", + "rank": 1000 }, { - "type": "separator", - "rank": 4 + "type": "separator", + "rank": 4 }, { - "disabled": false, - "command": "docmanager:toggle-autosave", - "rank": 4 + "disabled": false, + "command": "docmanager:toggle-autosave", + "rank": 4 }, { - "disabled": false, - "command": "docmanager:toggle-name-file-on-save", - "rank": 4 + "disabled": false, + "command": "docmanager:toggle-name-file-on-save", + "rank": 4 }, { - "type": "separator", - "rank": 4 + "type": "separator", + "rank": 4 }, { - "type": "submenu", - "submenu": { + "type": "submenu", + "submenu": { "disabled": false, "id": "jp-mainmenu-settings-apputilstheme", "label": "Theme", "items": [ - { + { "type": "separator" - }, - { + }, + { "command": "apputils:theme-scrollbars" - }, - { + }, + { "type": "separator" - }, - { + }, + { "disabled": false, "command": "apputils:incr-font-size", "args": { - "key": "code-font-size" + "key": "code-font-size" } - }, - { + }, + { "disabled": false, "command": "apputils:decr-font-size", "args": { - "key": "code-font-size" + "key": "code-font-size" } - }, - { + }, + { "type": "separator" - }, - { + }, + { "disabled": false, "command": "apputils:incr-font-size", "args": { - "key": "content-font-size1" + "key": "content-font-size1" } - }, - { + }, + { "disabled": false, "command": "apputils:decr-font-size", "args": { - "key": "content-font-size1" + "key": "content-font-size1" } - }, - { + }, + { "type": "separator" - }, - { + }, + { "command": "apputils:incr-font-size", "args": { - "key": "ui-font-size1" + "key": "ui-font-size1" } - }, - { + }, + { "command": "apputils:decr-font-size", "args": { - "key": "ui-font-size1" + "key": "ui-font-size1" } - } + } ] - }, - "rank": 0 + }, + "rank": 0 }, { - "type": "submenu", - "disabled": false, - "submenu": { + "type": "submenu", + "disabled": false, + "submenu": { "id": "jp-mainmenu-view-codemirror-keymap", "disabled": false, "label": "Text Editor Key Map", "items": [ - { + { "command": "codemirror:change-keymap", "args": { - "keyMap": "default" + "keyMap": "default" } - }, - { + }, + { "command": "codemirror:change-keymap", "args": { - "keyMap": "sublime" + "keyMap": "sublime" } - }, - { + }, + { "command": "codemirror:change-keymap", "args": { - "keyMap": "vim" + "keyMap": "vim" } - }, - { + }, + { "command": "codemirror:change-keymap", "args": { - "keyMap": "emacs" + "keyMap": "emacs" } - } + } ] - }, - "rank": 31 + }, + "rank": 31 }, { - "type": "submenu", - "disabled": false, - "submenu": { + "type": "submenu", + "disabled": false, + "submenu": { "id": "jp-mainmenu-view-codemirror-theme", "label": "Text Editor Theme", "items": [ - { + { "command": "codemirror:change-theme", "args": { - "theme": "jupyter" + "theme": "jupyter" } - }, - { + }, + { "command": "codemirror:change-theme", "args": { - "theme": "default" + "theme": "default" } - }, - { + }, + { "command": "codemirror:change-theme", "args": { - "theme": "abcdef" + "theme": "abcdef" } - }, - { + }, + { "command": "codemirror:change-theme", "args": { - "theme": "base16-dark" + "theme": "base16-dark" } - }, - { + }, + { "command": "codemirror:change-theme", "args": { - "theme": "base16-light" + "theme": "base16-light" } - }, - { + }, + { "command": "codemirror:change-theme", "args": { - "theme": "hopscotch" + "theme": "hopscotch" } - }, - { + }, + { "command": "codemirror:change-theme", "args": { - "theme": "material" + "theme": "material" } - }, - { + }, + { "command": "codemirror:change-theme", "args": { - "theme": "mbo" + "theme": "mbo" } - }, - { + }, + { "command": "codemirror:change-theme", "args": { - "theme": "mdn-like" + "theme": "mdn-like" } - }, - { + }, + { "command": "codemirror:change-theme", "args": { - "theme": "seti" + "theme": "seti" } - }, - { + }, + { "command": "codemirror:change-theme", "args": { - "theme": "solarized dark" + "theme": "solarized dark" } - }, - { + }, + { "command": "codemirror:change-theme", "args": { - "theme": "solarized light" + "theme": "solarized light" } - }, - { + }, + { "command": "codemirror:change-theme", "args": { - "theme": "the-matrix" + "theme": "the-matrix" } - }, - { + }, + { "command": "codemirror:change-theme", "args": { - "theme": "xq-light" + "theme": "xq-light" } - }, - { + }, + { "command": "codemirror:change-theme", "args": { - "theme": "zenburn" + "theme": "zenburn" } - } + } ] - }, - "rank": 31 + }, + "rank": 31 }, { - "type": "separator", - "rank": 39 + "type": "separator", + "rank": 39 }, { - "type": "separator", - "rank": 9 + "type": "separator", + "rank": 9 }, { - "type": "submenu", - "disabled": false, - "submenu": { + "type": "submenu", + "disabled": false, + "submenu": { "id": "jp-mainmenu-settings-consoleexecute", "label": "Console Run Keystroke", "items": [ - { + { "command": "console:interaction-mode", "args": { - "interactionMode": "terminal" + "interactionMode": "terminal" } - }, - { + }, + { "command": "console:interaction-mode", "args": { - "interactionMode": "notebook" + "interactionMode": "notebook" } - } + } ] - }, - "rank": 9 + }, + "rank": 9 }, { - "type": "separator", - "rank": 9 + "type": "separator", + "rank": 9 }, { - "type": "separator", - "rank": 100 + "type": "separator", + "rank": 100 }, { - "disabled": false, - "command": "extensionmanager:toggle", - "rank": 100 + "disabled": false, + "command": "extensionmanager:toggle", + "rank": 100 }, { - "type": "separator", - "rank": 100 + "type": "separator", + "rank": 100 }, { - "type": "separator", - "rank": 5 + "type": "separator", + "rank": 5 }, { - "command": "filebrowser:toggle-navigate-to-current-directory", - "rank": 5 + "command": "filebrowser:toggle-navigate-to-current-directory", + "rank": 5 }, { - "type": "separator", - "rank": 5 + "type": "separator", + "rank": 5 }, { - "type": "separator", - "rank": 1 + "type": "separator", + "rank": 1 }, { - "type": "submenu", - "rank": 1, - "submenu": { + "type": "submenu", + "rank": 1, + "submenu": { "id": "jp-mainmenu-settings-language", "label": "Language" - } + } }, { - "type": "separator", - "rank": 1 + "type": "separator", + "rank": 1 } - ], - "label": "Settings", - "mnemonic": -1, - "rank": 999 + ], + "label": "Settings", + "mnemonic": -1, + "rank": 999 }, { - "disabled": false, - "id": "jp-mainmenu-help", - "items": [ + "disabled": false, + "id": "jp-mainmenu-help", + "items": [ { - "command": "help:about", - "rank": 0 + "command": "help:about", + "rank": 0 }, { - "type": "separator", - "rank": 0 + "type": "separator", + "rank": 0 }, { - "type": "separator", - "rank": 2 + "type": "separator", + "rank": 2 }, { - "disabled": false, - "command": "help:jupyter-forum", - "rank": 2 + "disabled": false, + "command": "help:jupyter-forum", + "rank": 2 }, { - "type": "separator", - "rank": 2 + "type": "separator", + "rank": 2 }, { - "type": "separator", - "rank": 1 + "type": "separator", + "rank": 1 }, { - "disabled": false, - "command": "help:launch-classic-notebook", - "rank": 1 + "disabled": false, + "command": "help:launch-classic-notebook", + "rank": 1 }, { - "type": "separator", - "rank": 1 + "type": "separator", + "rank": 1 }, { - "type": "separator", - "rank": 0.1 + "type": "separator", + "rank": 0.1 }, { - "disabled": false, - "command": "inspector:open", - "rank": 0.1 + "disabled": false, + "command": "inspector:open", + "rank": 0.1 }, { - "type": "separator", - "rank": 0.1 + "type": "separator", + "rank": 0.1 } - ], - "label": "Help", - "mnemonic": -1, - "rank": 1000 + ], + "label": "Help", + "mnemonic": -1, + "rank": 1000 } - ] + ] } } diff --git a/containers/testing/base/start-notebook.d/ope-sartup.sh b/containers/testing/base/start-notebook.d/ope-sartup.sh index 06b24ef..a6beb63 100644 --- a/containers/testing/base/start-notebook.d/ope-sartup.sh +++ b/containers/testing/base/start-notebook.d/ope-sartup.sh @@ -12,63 +12,63 @@ else if [[ -d $MOUNT_DIR ]]; then echo "$SN: Found $MOUNT_DIR" if [[ ! -w $MOUNT_DIR ]]; then - echo "$SN: $MOUNT_DIR not writeable skipping mappings" - else + echo "$SN: $MOUNT_DIR not writeable skipping mappings" + else FILE=.gitconfig echo "$SN: Mapping $FILE to $MOUNT_DIR/$FILE" if [[ ! -a $HOME/$FILE ]]; then - if [[ ! -a $MOUNT_DIR/$FILE ]]; then - echo "$SN: creating $MOUNT_DIR/$FILE" - touch $MOUNT_DIR/$FILE - fi - if [[ -a $MOUNT_DIR/$FILE ]]; then - echo "$SN: Linking $MOUNT_DIR/$FILE -> $HOME/$FILE" - ln -s $MOUNT_DIR/$FILE $HOME/$FILE - fi + if [[ ! -a $MOUNT_DIR/$FILE ]]; then + echo "$SN: creating $MOUNT_DIR/$FILE" + touch $MOUNT_DIR/$FILE + fi + if [[ -a $MOUNT_DIR/$FILE ]]; then + echo "$SN: Linking $MOUNT_DIR/$FILE -> $HOME/$FILE" + ln -s $MOUNT_DIR/$FILE $HOME/$FILE + fi fi DIR=.ssh echo "$SN: Mapping $FILE to $MOUNT_DIR/$FILE" - if [[ ! -d $HOME/$DIR ]]; then - if [[ ! -d $MOUNT_DIR/$DIR ]]; then - echo "$SN: creating $MOUNT_DIR/$DIR" - if mkdir $MOUNT_DIR/$DIR; then - if [[ $DIR == .ssh ]]; then - echo "$SN: Fixing permisions for $MOUNT/$DIR" - chmod 700 $MOUNT_DIR/$DIR - fi - fi - fi - if [[ -d $MOUNT_DIR/$DIR ]]; then - echo "$SN: Linking $MOUNT_DIR/$DIR -> $HOME/$DIR" - ln -s $MOUNT_DIR/$DIR $HOME/$DIR - if [[ $DIR = .ssh ]]; then - # As per the standard jupyter startup scripts we use the following test to - # figure out if we have been started in a jupyterhub env or if we have started - # on openshift - if [[ -n "${JUPYTERHUB_API_TOKEN}" || ${JUPYTER_IMAGE} =~ openshift ]]; then - # during jupyter stacks startup logic "fixes permissions" on home directories - # this causes group permissions to be set. We undo this with a hammer on .ssh - echo "$SN: Fixing permissions in $HOME/$DIR" - chmod -R go-rwxs $HOME/$DIR - fi - fi - fi + if [[ ! -d $HOME/$DIR ]]; then + if [[ ! -d $MOUNT_DIR/$DIR ]]; then + echo "$SN: creating $MOUNT_DIR/$DIR" + if mkdir $MOUNT_DIR/$DIR; then + if [[ $DIR == .ssh ]]; then + echo "$SN: Fixing permisions for $MOUNT/$DIR" + chmod 700 $MOUNT_DIR/$DIR + fi + fi + fi + if [[ -d $MOUNT_DIR/$DIR ]]; then + echo "$SN: Linking $MOUNT_DIR/$DIR -> $HOME/$DIR" + ln -s $MOUNT_DIR/$DIR $HOME/$DIR + if [[ $DIR = .ssh ]]; then + # As per the standard jupyter startup scripts we use the following test to + # figure out if we have been started in a jupyterhub env or if we have started + # on openshift + if [[ -n "${JUPYTERHUB_API_TOKEN}" || ${JUPYTER_IMAGE} =~ openshift ]]; then + # during jupyter stacks startup logic "fixes permissions" on home directories + # this causes group permissions to be set. We undo this with a hammer on .ssh + echo "$SN: Fixing permissions in $HOME/$DIR" + chmod -R go-rwxs $HOME/$DIR + fi + fi fi - + fi + if [[ ! -L $HOME/.jupyter/lab && -a $MOUNT_DIR ]]; then - if [[ ! -a $MOUNT_DIR/jupyter/lab ]]; then + if [[ ! -a $MOUNT_DIR/jupyter/lab ]]; then mkdir -p $MOUNT_DIR/jupyter/lab - fi - [[ -a $HOME/.jupyter/lab ]] && mv $HOME/.jupyter/lab $HOME/.jupyter/lab.old - echo "$SN: linking: ln -s /opt/app-root/src/jupyter/lab ~/.jupyter/lab" - ln -s $MOUNT_DIR/jupyter/lab $HOME/.jupyter/lab + fi + [[ -a $HOME/.jupyter/lab ]] && mv $HOME/.jupyter/lab $HOME/.jupyter/lab.old + echo "$SN: linking: ln -s /opt/app-root/src/jupyter/lab ~/.jupyter/lab" + ln -s $MOUNT_DIR/jupyter/lab $HOME/.jupyter/lab fi if [[ -a $MOUNT_DIR/.myjupyter_start.sh ]]; then - echo "$SN: Found $MOUNT_DIR/.myjupyter_start.sh: sourcing it" - . $MOUNT_DIR/.myjupyter_start.sh + echo "$SN: Found $MOUNT_DIR/.myjupyter_start.sh: sourcing it" + . $MOUNT_DIR/.myjupyter_start.sh fi - fi + fi fi fi diff --git a/containers/testing/tests/Makefile b/containers/testing/tests/Makefile index f53d1f2..52a2e5d 100644 --- a/containers/testing/tests/Makefile +++ b/containers/testing/tests/Makefile @@ -28,5 +28,3 @@ clean: rm -rf *.png rm -rf *.ipynb rm -rf .ipynb_checkpoints - - diff --git a/containers/testing/tests/requirements.txt b/containers/testing/tests/requirements.txt index 7c08e8e..2df7ab3 100644 --- a/containers/testing/tests/requirements.txt +++ b/containers/testing/tests/requirements.txt @@ -1,3 +1,3 @@ selenium webdriver_manager -pillow \ No newline at end of file +pillow diff --git a/containers/testing/tests/rise_test.py b/containers/testing/tests/rise_test.py index 29746f3..17c5600 100644 --- a/containers/testing/tests/rise_test.py +++ b/containers/testing/tests/rise_test.py @@ -72,5 +72,3 @@ driver.switch_to.window(main_window_handle) driver.refresh() - - diff --git a/containers/testing/tests/screenshots_diff.py b/containers/testing/tests/screenshots_diff.py index 44cac22..b8ce864 100644 --- a/containers/testing/tests/screenshots_diff.py +++ b/containers/testing/tests/screenshots_diff.py @@ -14,5 +14,3 @@ print("ERROR: " + filename + " are different") else: print(filename + " are identical") - - diff --git a/containers/testing/tests/versions.txt b/containers/testing/tests/versions.txt index beb0e2e..720b2ed 100644 --- a/containers/testing/tests/versions.txt +++ b/containers/testing/tests/versions.txt @@ -5,4 +5,4 @@ numpy 1.24.3 pandas 2.0.1 nbconvert 7.4.0 jupyterlab 3.6.1 -nbclient 0.5.13 \ No newline at end of file +nbclient 0.5.13 diff --git a/content/README.md b/content/README.md index 2bf9e83..f6605d9 100644 --- a/content/README.md +++ b/content/README.md @@ -1,3 +1,3 @@ # Open Source Course Materials Template -This template is to help get you started on your journey to creating Open Source educational content \ No newline at end of file +This template is to help get you started on your journey to creating Open Source educational content diff --git a/content/contributor_guide/rhoai_ta_access.md b/content/contributor_guide/rhoai_ta_access.md new file mode 100644 index 0000000..52863b6 --- /dev/null +++ b/content/contributor_guide/rhoai_ta_access.md @@ -0,0 +1,67 @@ +# Getting Access to Openshift Console + +## Login + +Open this link in a browser: + +- + +Select mss-keycloak login option: + +![](images/openshift-login.png) + +You will be redirected to a site managed by CILogon where you will select your institutional or commercial identity provider as shown below (Choose Boston University if you are using your BU ID): + +![](images/select-provider.png) + +Once selected, you will be redirected to your institutional or commercial identity provider where you will login as shown here: + +![](images/bulogin.png) + +## Openshift Web Interface + +After successful logon, your browser will direct you to NERC Openshift Console rhods-notebooks namespace. Here, you have the ability to view and manage workloads, as well as search for and list notebooks. Please note that the workloads and notebooks displayed include from cs506, cs210 and ec440 classes. It’s important to be aware of this broader scope while managing resources in the namespace. + +### View workloads + +![](images/view-workloads.png) + +### Search for notebooks + +On the left panel, select ‘Home’ -> ‘Search’, from the ‘Project’ dropdown box, choose ‘rhods-notebooks’, from the ‘Resource’ dropdown box, type ‘notebook’ . Then you can see a list of notebooks: + +![](images/search-notebooks.png) + +## Openshift CLI + +### CLI Login + +Using the OpenShift CLI (Command Line Interface) allows users to manage student resources efficiently. + +#### Download Openshift CLI + +Follow these instructions to download the CLI tools from: + +#### Get your login token + +On the NERC OCP console: + +- From the top right username dropdown box, select “Copy login command”. +- You will be redirected to a new page. Click “Display Token”. +- Copy the cli command under “Log in with this token”. +- Paste this command in your local terminal to login. + +![](images/copy-login.png) + +### List notebooks with CLI + +``` +oc get notebooks -n rhods-notebooks +``` + +Note, this command displays all notebooks under rhods-notebooks namespace. + +### Launch student notebooks + +Notebook name is in the format of “`jupyter-nb-`”. A script has been provided for you to retrieve a student jupyter notebook URL. Please refer to this README file: +You can open the URL in your browser to view the student notebook. diff --git a/content/contributor_guide_config.yml b/content/contributor_guide_config.yml index 712e112..e94f8d9 100644 --- a/content/contributor_guide_config.yml +++ b/content/contributor_guide_config.yml @@ -1,22 +1,21 @@ +--- ####################################################################################### # A default configuration that will be loaded for all jupyter books -# See the documentation for help and more options: +# See the documentation for help and more options: # https://jupyterbook.org/customize/config.html ####################################################################################### # Book settings -title : OPE Contributor Guide # The title of the book. Will be placed in the left navbar. -author : OPE Team # The author of the book -copyright : "2023" # Copyright year to be placed in the footer -logo : logo.png # A path to the book logo -only_build_toc_files : true - +title: OPE Contributor Guide # The title of the book. Will be placed in the left navbar. +author: OPE Team # The author of the book +copyright: '2023' # Copyright year to be placed in the footer +logo: logo.png # A path to the book logo +only_build_toc_files: true # url should be the published url for the book repository: url: https://github.com/OPEFFORT/ope-project path_to_book: content branch: main - html: use_repository_button: true use_edit_page_button: true @@ -25,16 +24,14 @@ html: hypothesis: true # utterances: # repo: "jappavoo/UndertheCovers" - launch_buttons: # open textbook pages in lab to provide desktop env - notebook_interface: "jupyterlab" - binderhub_url: "" # trying to remove binder + notebook_interface: jupyterlab + binderhub_url: '' # trying to remove binder # launch on the public RedHat Operate First Service - jupyterhub_url: "https://jupyterhub-opf-jupyterhub.apps.smaug.na.operate-first.cloud" - # launch on the private BU service -- use this for BU courses -- students must be authorized - #jupyterhub_url: "https://jupyterhub-redhat-ods-applications.apps.bu-rosa.8pmt.p1.openshiftapps.com" - + jupyterhub_url: https://jupyterhub-opf-jupyterhub.apps.smaug.na.operate-first.cloud + # launch on the private BU service -- use this for BU courses -- students must be authorized + # jupyterhub_url: "https://jupyterhub-redhat-ods-applications.apps.bu-rosa.8pmt.p1.openshiftapps.com" parse: # see https://jupyterbook.org/customize/config.html # don't forget to list any other extensions you want enabled, @@ -47,7 +44,6 @@ parse: - linkify - substitution - html_admonition - execute: - execute_notebooks: "cache" + execute_notebooks: cache allow_error: true diff --git a/content/contributor_guide_intro.md b/content/contributor_guide_intro.md index c2af422..4d157c0 100644 --- a/content/contributor_guide_intro.md +++ b/content/contributor_guide_intro.md @@ -1,7 +1,7 @@ -OPE Contributor Guide +OPE Contributor Guide ===================================== -This contributor guide is intended to be a guide to people collaborating with us on the OPE project. +This contributor guide is intended to be a guide to people collaborating with us on the OPE project. diff --git a/content/contributor_guide_toc.yml b/content/contributor_guide_toc.yml index 71a7e06..988c3f6 100644 --- a/content/contributor_guide_toc.yml +++ b/content/contributor_guide_toc.yml @@ -1,14 +1,15 @@ +--- format: jb-book root: contributor_guide_intro parts: -- caption: 'Introduction' - numbered: true - chapters: - - file: contributor_guide/intro/chapter1 - - file: contributor_guide/intro/chapter2 -- caption: 'How to Contribute' - numbered: true - chapters: - - file: contributor_guide/contribute/chapter1 - - file: contributor_guide/contribute/chapter2 - - file: contributor_guide/contribute/chapter3 + - caption: Introduction + numbered: true + chapters: + - file: contributor_guide/intro/chapter1 + - file: contributor_guide/intro/chapter2 + - caption: How to Contribute + numbered: true + chapters: + - file: contributor_guide/contribute/chapter1 + - file: contributor_guide/contribute/chapter2 + - file: contributor_guide/contribute/chapter3 diff --git a/content/css/ln.css b/content/css/ln.css index 383cb5c..b9d9284 100644 --- a/content/css/ln.css +++ b/content/css/ln.css @@ -1,7 +1,7 @@ /* an example to illustrate how to tweak font sizes */ -/* to apply a setting only when under RISE, +/* to apply a setting only when under RISE, be aware that the tag has class .rise-enabled */ @@ -16,14 +16,14 @@ body.rise-enabled div.inner_cell>div.input_area { font-size: inherit; } -/* output cells that generate markddown... not sure why but 80% seems to match +/* output cells that generate markddown... not sure why but 80% seems to match the rest of the areas but it seems to work */ body.rise-enabled div.output_subarea.output_markdown.rendered_html { font-size: 80%; } -/* Ingnoring these +/* Ingnoring these body.rise-enabled div.output_subarea.output_text.output_result { font-size: 10%; } @@ -55,13 +55,13 @@ div.inner_cell>div.text_cell_render.rendered_html>pre>code { font-size: 100%; } -#notebook { padding-top:0px !important; } +#notebook { padding-top:0px !important; } -.container { width:100% !important; } +.container { width:100% !important; } .CodeMirror { width:100% !important;} -.end_space { min-height:0px !important; } +.end_space { min-height:0px !important; } .prompt { display:none } @@ -69,11 +69,11 @@ div.inner_cell>div.text_cell_render.rendered_html>pre>code { div.mywarn { background-color: #fcf2f2;border-color: #dFb5b4; border-left: 5px solid #dfb5b4; padding: 0.5em;} -/* JA Hacks to get annimation buttons looking better with RISE. +/* JA Hacks to get annimation buttons looking better with RISE. This is very crude and overkill */ /* button { background-color: #cccccc00; font-size: 30% } */ /* .animation { background-color: white } */ -.anim-controls { color: blue; background-color: white; font-size: 40% } +.anim-controls { color: blue; background-color: white; font-size: 40% } /* not very useful, but an OBVIOUS setting that you cannot miss */ div.cell.code_cell.rendered { @@ -83,5 +83,3 @@ div.cell.code_cell.rendered { div.input_area { border-radius: 0px 0px 0px 0px; } - - diff --git a/content/examples/README.md b/content/examples/README.md index 6e41f5c..1a986bb 100644 --- a/content/examples/README.md +++ b/content/examples/README.md @@ -2,5 +2,3 @@ This directory has various examples it that you can use to help you create your own content. - - diff --git a/content/examples/book/README.md b/content/examples/book/README.md index af6d012..fb1623a 100644 --- a/content/examples/book/README.md +++ b/content/examples/book/README.md @@ -1,4 +1,3 @@ # BOOK SEED FILES Please do not delete these files. They are used to seed the book specific content files. `ope new_book` creates a book specific version of these for each book added to the project. If it cannot find these files the `new_book` command will fail - diff --git a/content/examples/book/config.yml b/content/examples/book/config.yml index ce45748..cc7d74c 100644 --- a/content/examples/book/config.yml +++ b/content/examples/book/config.yml @@ -1,22 +1,21 @@ +--- ####################################################################################### # A default configuration that will be loaded for all jupyter books -# See the documentation for help and more options: +# See the documentation for help and more options: # https://jupyterbook.org/customize/config.html ####################################################################################### # Book settings -title : MyBook # The title of the book. Will be placed in the left navbar. -author : Author # The author of the book -copyright : "XXXX" # Copyright year to be placed in the footer -logo : logo.png # A path to the book logo -only_build_toc_files : true - +title: MyBook # The title of the book. Will be placed in the left navbar. +author: Author # The author of the book +copyright: XXXX # Copyright year to be placed in the footer +logo: logo.png # A path to the book logo +only_build_toc_files: true # url should be the published url for the book repository: url: https://github.com/jappavoo/coursecontenttemplate path_to_book: content branch: main - html: use_repository_button: true use_edit_page_button: true @@ -25,16 +24,14 @@ html: hypothesis: true # utterances: # repo: "jappavoo/UndertheCovers" - launch_buttons: # open textbook pages in lab to provide desktop env - notebook_interface: "jupyterlab" - binderhub_url: "" # trying to remove binder + notebook_interface: jupyterlab + binderhub_url: '' # trying to remove binder # launch on the public RedHat Operate First Service - jupyterhub_url: "https://jupyterhub-opf-jupyterhub.apps.smaug.na.operate-first.cloud" - # launch on the private BU service -- use this for BU courses -- students must be authorized - #jupyterhub_url: "https://jupyterhub-redhat-ods-applications.apps.bu-rosa.8pmt.p1.openshiftapps.com" - + jupyterhub_url: https://jupyterhub-opf-jupyterhub.apps.smaug.na.operate-first.cloud + # launch on the private BU service -- use this for BU courses -- students must be authorized + # jupyterhub_url: "https://jupyterhub-redhat-ods-applications.apps.bu-rosa.8pmt.p1.openshiftapps.com" parse: # see https://jupyterbook.org/customize/config.html # don't forget to list any other extensions you want enabled, @@ -47,7 +44,6 @@ parse: - linkify - substitution - html_admonition - execute: - execute_notebooks: "cache" + execute_notebooks: cache allow_error: true diff --git a/content/examples/book/intro.md b/content/examples/book/intro.md index c5ec08c..b137d7c 100644 --- a/content/examples/book/intro.md +++ b/content/examples/book/intro.md @@ -1,13 +1,13 @@ -Book Title +Book Title ===================================== In this book we will teach something fun - fun thing one - fun thing 2 -The material is broken down in to three parts: -1. Part 1 +The material is broken down in to three parts: +1. Part 1 2. Part 2 3. Part 3 diff --git a/content/examples/css/ln.css b/content/examples/css/ln.css index 383cb5c..b9d9284 100644 --- a/content/examples/css/ln.css +++ b/content/examples/css/ln.css @@ -1,7 +1,7 @@ /* an example to illustrate how to tweak font sizes */ -/* to apply a setting only when under RISE, +/* to apply a setting only when under RISE, be aware that the tag has class .rise-enabled */ @@ -16,14 +16,14 @@ body.rise-enabled div.inner_cell>div.input_area { font-size: inherit; } -/* output cells that generate markddown... not sure why but 80% seems to match +/* output cells that generate markddown... not sure why but 80% seems to match the rest of the areas but it seems to work */ body.rise-enabled div.output_subarea.output_markdown.rendered_html { font-size: 80%; } -/* Ingnoring these +/* Ingnoring these body.rise-enabled div.output_subarea.output_text.output_result { font-size: 10%; } @@ -55,13 +55,13 @@ div.inner_cell>div.text_cell_render.rendered_html>pre>code { font-size: 100%; } -#notebook { padding-top:0px !important; } +#notebook { padding-top:0px !important; } -.container { width:100% !important; } +.container { width:100% !important; } .CodeMirror { width:100% !important;} -.end_space { min-height:0px !important; } +.end_space { min-height:0px !important; } .prompt { display:none } @@ -69,11 +69,11 @@ div.inner_cell>div.text_cell_render.rendered_html>pre>code { div.mywarn { background-color: #fcf2f2;border-color: #dFb5b4; border-left: 5px solid #dfb5b4; padding: 0.5em;} -/* JA Hacks to get annimation buttons looking better with RISE. +/* JA Hacks to get annimation buttons looking better with RISE. This is very crude and overkill */ /* button { background-color: #cccccc00; font-size: 30% } */ /* .animation { background-color: white } */ -.anim-controls { color: blue; background-color: white; font-size: 40% } +.anim-controls { color: blue; background-color: white; font-size: 40% } /* not very useful, but an OBVIOUS setting that you cannot miss */ div.cell.code_cell.rendered { @@ -83,5 +83,3 @@ div.cell.code_cell.rendered { div.input_area { border-radius: 0px 0px 0px 0px; } - - diff --git a/content/examples/images/src/README.md b/content/examples/images/src/README.md index b468605..a187bbf 100644 --- a/content/examples/images/src/README.md +++ b/content/examples/images/src/README.md @@ -1,10 +1,7 @@ # Source for files for image content -Please place source for images in this directory. Please use open source tools and file formats. The recommended format is draw.io xml. +Please place source for images in this directory. Please use open source tools and file formats. The recommended format is draw.io xml. -See https://www.diagrams.net/about +See https://www.diagrams.net/about While the jupyterlab environment has support for editing drawio files at this point we recommend that you use the desktop version as it is more stable. Please be sure to commit all your source to your repository. - - - diff --git a/content/examples/images/src/logo.drawio b/content/examples/images/src/logo.drawio index 0968d6b..05e8a13 100644 --- a/content/examples/images/src/logo.drawio +++ b/content/examples/images/src/logo.drawio @@ -1 +1 @@ -vZZNj5swEIZ/TY4rgQ3EOTZpunvY3X6kUs/GHsBagyPHlKS/vkNikhCItK2SPeF5xzbj5x0ZJnRRbh8tXxcvRoKekEBuJ/TzhJAwDBJ8tMruoLA2aoXcKuknnYSV+gNeDLxaKwmb3kRnjHZq3ReFqSoQrqdxa03Tn5YZ3X/rmucwEFaC66H6S0lX+FPEwUl/ApUX7nhgnyl5N9kLm4JL05xJdDmhC2uMO4zK7QJ0C6/jclj35Ur2WJiFyr1nwfeYLFbih3xOV48JmRZlyPKHMDps85vr2p/YV+t2HQLcBmljMG8K5WC15qLNNGg4aoUrNUYhDjNTOe8gDdpYab0w2tj9PjTLhCQM9dxyqbDqXo7HQbtm46x5g7OMnM7SfcYXCtbB9iqC8AgWOxJMCc7ucEq3oDPHN2PYedOcrI0SrxVnthLqRe7bKT/ufSKOAw/9XwyIBwb8hK2bG/N2NyNkDExGY7AZSWmS3Ag2vYDNhrDD2QhsdjfW0wHrZ56+8Krm+n5dzwQIMQY7ZXEU36izyQVskgxhk+BDYbMhbLygawuvxuGNfrfm5sCyUd6JYJBmt+FN39HcH8x7NuD99dvydcAZz+z6QPugKlPBBVUvca3yCkOBjAD1eUtQ4afyk0+USkp9zUFr6kpCe4LgwsRZF/siyS0unyju+xMN/aHTEX/+46LH8PQV3+fO/oXo8i8= \ No newline at end of file +vZZNj5swEIZ/TY4rgQ3EOTZpunvY3X6kUs/GHsBagyPHlKS/vkNikhCItK2SPeF5xzbj5x0ZJnRRbh8tXxcvRoKekEBuJ/TzhJAwDBJ8tMruoLA2aoXcKuknnYSV+gNeDLxaKwmb3kRnjHZq3ReFqSoQrqdxa03Tn5YZ3X/rmucwEFaC66H6S0lX+FPEwUl/ApUX7nhgnyl5N9kLm4JL05xJdDmhC2uMO4zK7QJ0C6/jclj35Ur2WJiFyr1nwfeYLFbih3xOV48JmRZlyPKHMDps85vr2p/YV+t2HQLcBmljMG8K5WC15qLNNGg4aoUrNUYhDjNTOe8gDdpYab0w2tj9PjTLhCQM9dxyqbDqXo7HQbtm46x5g7OMnM7SfcYXCtbB9iqC8AgWOxJMCc7ucEq3oDPHN2PYedOcrI0SrxVnthLqRe7bKT/ufSKOAw/9XwyIBwb8hK2bG/N2NyNkDExGY7AZSWmS3Ag2vYDNhrDD2QhsdjfW0wHrZ56+8Krm+n5dzwQIMQY7ZXEU36izyQVskgxhk+BDYbMhbLygawuvxuGNfrfm5sCyUd6JYJBmt+FN39HcH8x7NuD99dvydcAZz+z6QPugKlPBBVUvca3yCkOBjAD1eUtQ4afyk0+USkp9zUFr6kpCe4LgwsRZF/siyS0unyju+xMN/aHTEX/+46LH8PQV3+fO/oXo8i8= diff --git a/content/examples/python/common.py b/content/examples/python/common.py index 0417b25..4dc2841 100644 --- a/content/examples/python/common.py +++ b/content/examples/python/common.py @@ -25,18 +25,18 @@ # # display(Javascript(js_code)) -# common functions +# common functions # Custom functions and classes to help with standard slide elements that I use # NOTE: I don't know python so this probably all should be rewritten by someone -# who knows what they are doing. +# who knows what they are doing. def MDBox(contents, title="", w="100%", h="100%", fontsize="inherit", color="inherit", bgcolor="inherit", overflow="auto"): - - md_text = title + + md_text = title md_text += '''
-''' +''' md_text += contents md_text += ''' @@ -66,10 +66,10 @@ def FileCodeBox(file, lang, number=False, **kwargs): text_file = open(file, "r") #read whole file to a string data = text_file.read() - + if number: data = numberLines(data) - + #close file text_file.close() # build contents from file and language @@ -89,7 +89,7 @@ def FileMDBox(file, **kwargs): #close file text_file.close() # build contents from file and language - # build output widget + # build output widget return MDBox(data, **kwargs) # Make a box that display the specified image files from the specified @@ -97,13 +97,13 @@ def FileMDBox(file, **kwargs): # displayed. A slider is used to select between the images # Note if you want control the order you must specifiy the files # explicitly -# This function requires backend kernel see +# This function requires backend kernel see def mkImgsBox(dir,files=[]): if len(files)==0: files=os.listdir(dir); - + interact(lambda i,d=fixed(dir), - f=fixed(files): + f=fixed(files): display(Image(dir + '/' + files[i])), i=widgets.IntSlider(min=0, max=(len(files)), step=1, value=0)); @@ -111,7 +111,7 @@ def files_to_imgArray(dir, files=[]): if len(files)==0: files=os.listdir(dir); # print(files) - + imgs = []; for f in files: imgs.append(plt.imread(dir + "/" + f)) @@ -135,7 +135,7 @@ def mkImgsAnimateBox(dir, files=[], dpi=100.0, xpixels=0, ypixels=0, force=False if os.path.exists(dir + "/" + saveto): os.remove(dir + "/" + saveto); - + imgs=files_to_imgArray(dir, files) if (xpixels==0): xpixels = imgs[0].shape[0] @@ -206,7 +206,7 @@ def bin2Hex(x, sep=' $\\rightarrow$ '): # v=np.uint8(0xaa) # w=np.bitwise_and(u,v) # Empty value is indicated via [""] note also force cell height to avoid -# the empty row from shrinking +# the empty row from shrinking # displayBytes([[u],[v],[""]],labels=["u","v", "u & v"], td_height="85px") # displayBytes([[u],[v],[w]],labels=["u","v", "u & v"]) # @@ -232,7 +232,7 @@ def toBits(v,dtype,count,numbits): def bitLabels(n): labels=[None] * n n=n-1 - for i in range(n,-1,-1): + for i in range(n,-1,-1): labels[n-i]="b" + str(i) + "" return labels @@ -246,13 +246,13 @@ def displayBytes(bytes=[[0x00]], numbits=8, dtype=np.uint8, columns=["[b7", - "b6", - "b5", - "b4", - "b3", - "b2", + "b6", + "b5", + "b4", + "b3", + "b2", "b1", - "b0]"], + "b0]"], center=True, th_font_size="1.5vw", th_border_color="#cccccc", @@ -265,38 +265,38 @@ def displayBytes(bytes=[[0x00]], td_hover_bgcolor="white", td_hover_color="black", disp=True, - prehtml='
', - posthtml='
' + prehtml='
', + posthtml='
' ): # if no labels specified then send in blanks to supress # there is probably a better way to do this #if not labels: # labels = ["" for i in range(len(bytes))] - + # print(bytes, bytes[0], np.dtype(bytes[0], dtype(bytes[0]).nbytes)) sizeinbits = dtype(0).nbytes sizeinbits = sizeinbits * 8 - + # have attempted to support specifiy the number of bits # but not sure it really works will need to be tested if numbits
''' if extratxt: html_text += '''
-''' + extratxt + ''' +''' + extratxt + ''' -
+
''' if caption: html_text += '''
@@ -475,7 +475,7 @@ def toImg(i): if ((not type(i) == type({})) or (not 'src' in i)): raise ValueError('img must at have a src specified') - + return i # a list of imgs @@ -483,9 +483,9 @@ def toImgs(i): # already a list so don't do anyting if type(i) == type([]): return i - + i=[toImg(i)] - + return i def htmlFigTableStart(id, align, width, margin): @@ -498,7 +498,7 @@ def htmlFigTableStart(id, align, width, margin): return html_text def htmlFigTRStart(): - html_text = ''' + html_text = ''' ''' return html_text @@ -514,7 +514,7 @@ def htmlFigTableEnd(): def htmlFigCaption(caption, align): html_text = ''' - ''' + caption + ''' + ''' + caption + ''' ''' return html_text @@ -526,13 +526,13 @@ def htmlFig(imgs, id="", align="center", width="100%", rows = len(imgs) maxcols = 1 - html_text =''' ''' @@ -548,20 +548,20 @@ def htmlFig(imgs, id="", align="center", width="100%", rows.append(r) if (len(r)>maxcols): maxcols = len(r); - + for r in rows: cols=len(r) html_text += htmlFigTRStart() - + for i in r: img=toImg(i) html_text += htmlFigTD(img) html_text += htmlFigTREnd() - + if caption: html_text += htmlFigCaption(caption, captionalign) - + html_text += htmlFigTableEnd() return html_text @@ -581,9 +581,9 @@ def htmlTerm(text, html_text += '''; font-size: ''' + fontsize html_text += '''; color: ''' + color html_text += '''">''' - + html_text += text - + html_text +='''''' return html_text @@ -597,7 +597,7 @@ def htmlTerm(text, | # or a single 8-bit byte Fe (omitting CSI) [\x80-\x9A\x9C-\x9F] | # or CSI + control codes - (?: # 7-bit CSI, ESC [ + (?: # 7-bit CSI, ESC [ \x1B\[ | # 8-bit CSI, 9B \x9B @@ -620,25 +620,25 @@ def closeTtySession(session): def closeAllOpenTtySessions(): for s in TtyOpenSessions: closeTtySession(s) - + def openTtySession(cmd, cwd, rows, cols): global TtySessions session = dict() master, slave = pty.openpty() session['master']=master session['slave']=slave - + # not sure this is really necessary ... I am guessing that Popen takes care of this # ioctl(slave, I_PUSH, "ptem") # ioctl(slave, I_PUSH, "ldterm") - # terminal size stuff from - # https://github.com/terminal-labs/cli-passthrough/blob/master/cli_passthrough/_passthrough.py + # terminal size stuff from + # https://github.com/terminal-labs/cli-passthrough/blob/master/cli_passthrough/_passthrough.py size = struct.pack("HHHH", rows, cols, 0, 0) fcntl.ioctl(master, termios.TIOCSWINSZ, size) - + # nttysettings = termios.tcgetattr(master) # nttysettings[3] &= ~termios.ECHO - # termios.tcsetattr(master, termios.TCSANOW, nttysettings) + # termios.tcsetattr(master, termios.TCSANOW, nttysettings) p=subprocess.Popen(cmd, cwd=cwd, stdin=slave, stdout=slave, stderr=slave, start_new_session=True) session['process']=p session['output']=b'' @@ -658,8 +658,8 @@ def renderTtySessionOutput(output, height='100%', width='', outputlayout={'borde if width: outputlayout['width']=width outputlayout['overflow_x']='auto' - - text=text.decode(encoding,decodeerrors) + + text=text.decode(encoding,decodeerrors) out=widgets.Output(layout=outputlayout) with out: print(text,end='') @@ -668,46 +668,46 @@ def renderTtySessionOutput(output, height='100%', width='', outputlayout={'borde def bashSessionSendEOF(session): master = session['master'] os.write(master, b'\x04') - -def bashSessionRawWrite(data, session, - batchsize=1, - interbatchdelayms=140, - sendEOF=True, - stoponprompt=True, - ignoreoutput=False, + +def bashSessionRawWrite(data, session, + batchsize=1, + interbatchdelayms=140, + sendEOF=True, + stoponprompt=True, + ignoreoutput=False, tmout=0.5): # for ioctl call - buf_ = array.array('i', [0]) + buf_ = array.array('i', [0]) master = session['master'] slave = session['slave'] p = session['process'] - + delaysec = interbatchdelayms / 1000 - + if isinstance(data, str): data = data.encode('utf-8') - n = len(data) + n = len(data) if batchsize <1: batchsize = n - + # print("n:", n, "batchsize: ", batchsize) s = 0 e = 0 output = b'' - + writeset = [master] - + while True: #print("n: ", n, " s: ", s, " e: ", e) - read_fds,write_fds,error_fds = select.select([master],writeset,[master],tmout) + read_fds,write_fds,error_fds = select.select([master],writeset,[master],tmout) # process errors if len(error_fds): # print("errors found") break; - - # write data aslong as there is data to write - if e<=n and len(write_fds): + + # write data aslong as there is data to write + if e<=n and len(write_fds): s = e e = s + batchsize if (e>n): @@ -725,15 +725,15 @@ def bashSessionRawWrite(data, session, # print("EOF: zero lenght write") #os.write(master, b'') bashSessionSendEOF(session) - e = n + 1 - + e = n + 1 + # read data if there is any to read, if we find a prompt assume we are done if len(read_fds): if fcntl.ioctl(master, termios.FIONREAD, buf_, 1) == -1: break # print("num bytes available for read:", buf_[0]) rdata = os.read(master, buf_[0]) - + # print("read:", rdata) if len(rdata)>0: output += rdata @@ -741,26 +741,26 @@ def bashSessionRawWrite(data, session, if stoponprompt and rn>=2 and output[rn-2] == 36 and output[rn-1] == 32: # print("prompt received") break - + if len(error_fds) == 0 and len(write_fds) == 0 and len(read_fds) == 0: # print("time out") break - + if not p.returncode == None: - break - + break + if not ignoreoutput: #print(output) session['output'] = session['output'] + output - + # output = b'$ ' + output return output,session - -#def cleanTermBytes(bytes): + +#def cleanTermBytes(bytes): # return ansi_escape_8bit.sub(b'', bytes) def bashSessionCmds(cmds, cwd=os.getcwd(), bufsize=4096, wait=True, rows=20, cols=80, session=None, close=True, ignoreoutput=False, **kwargs): if not session: - session = openTtySession(['bash', '-l', '-i'], cwd, rows, cols) + session = openTtySession(['bash', '-l', '-i'], cwd, rows, cols) new_session = True initdone = 0 else: @@ -770,28 +770,28 @@ def bashSessionCmds(cmds, cwd=os.getcwd(), bufsize=4096, wait=True, rows=20, col return None, None new_session=False initdone = 2 - + master = session['master'] slave = session['slave'] p = session['process'] - + if not isinstance(cmds,list): if isinstance(cmds, str): cmds = cmds.encode('utf-8') cmds = cmds.split(b'\n') - + output = b'' numcmds = len(cmds) # print("numcmds:", numcmds) i = 0 - + if not new_session: #print(cmds[i] + b'\n') os.write(master, cmds[i] + b'\n') - i=i+1 - + i=i+1 + while True: - read_fds,_,error_fds = select.select([master],[],[master]) + read_fds,_,error_fds = select.select([master],[],[master]) if len(error_fds): kill = True; break; @@ -832,10 +832,10 @@ def bashSessionCmds(cmds, cwd=os.getcwd(), bufsize=4096, wait=True, rows=20, col if not p.returncode == None: kill = True break - + if close: closeTtySession(session) - + if not ignoreoutput: #print(output) session['output'] = session['output'] + output @@ -849,58 +849,58 @@ def bashSessionClose(session): def bashSessionOpen(cwd=os.getenv('HOME'), **kwargs): _, session = bashSessionCmds(cmds=[], close=False, cwd=cwd, **kwargs) return session - + def bashCmds(cmds, cwd=os.getenv('HOME'), **kwargs): output, session = bashSessionCmds(cmds=cmds, cwd=cwd, **kwargs) return renderTtySessionOutput(output, **kwargs) class BashSession: - # create session + # create session def __init__(self, **kwargs): self.session = bashSessionOpen(**kwargs) # turn off tab completion self.runNoOutput("bind 'set disable-completion on'", ignoreoutput=True) # clear all bash history so that our history examples are clean self.runNoOutput(" history -c\n history -w", ignoreoutput=True) - + # Deleting (Calling destructor) def __del__(self): bashSessionClose(self.session) - + def run(self, cmds, **kwargs): text, self.session = bashSessionCmds(cmds, session=self.session, close=False, **kwargs) return renderTtySessionOutput(text, **kwargs) def runNoOutput(self, cmds, **kwargs): _, self.session = bashSessionCmds(cmds, session=self.session, close=False, **kwargs) - + def runAllOutput(self, cmds, **kwargs): self.runNoOutput(cmds, **kwargs) return self.output(**kwargs) - + def rawWrite(self, data, **kwargs): text, self.session = bashSessionRawWrite(data, session=self.session, **kwargs) return renderTtySessionOutput(text, **kwargs) - + def rawWriteNoOutput(self, data, **kwargs): _, self.session = bashSessionRawWrite(data, session=self.session, **kwargs) - + def rawWriteAllOutput(self, data, **kwargs): self.rawWriteNoOutput(data, **kwargs) return self.output(**kwargs) - + def sendEOF(self): bashSessionSendEOF(self.session) - + def output(self, **kwargs): return renderTtySessionOutput(self.session, **kwargs) - - + + def getPid(self): return self.session['process'].pid - - + + # FIXME: JA Given the new Session code above this needs to be re thought out and cleaned up or removed def runTermCmd(cmd, cwd=os.getcwd(), bufsize=4096, wait=True, tmout=1.0, rows=20, cols=80): master, slave = pty.openpty() @@ -908,12 +908,12 @@ def runTermCmd(cmd, cwd=os.getcwd(), bufsize=4096, wait=True, tmout=1.0, rows=20 # not sure this is really necessary ... I am guessing that Popen takes care of this # ioctl(slave, I_PUSH, "ptem") # ioctl(slave, I_PUSH, "ldterm") - - # terminal size stuff from - # https://github.com/terminal-labs/cli-passthrough/blob/master/cli_passthrough/_passthrough.py + + # terminal size stuff from + # https://github.com/terminal-labs/cli-passthrough/blob/master/cli_passthrough/_passthrough.py size = struct.pack("HHHH", rows, cols, 0, 0) fcntl.ioctl(master, termios.TIOCSWINSZ, size) - + p=subprocess.Popen(['bash', '-l', '-i', '-c', cmd], cwd=cwd, stdin=slave, stdout=slave, stderr=slave, start_new_session=True) if wait: @@ -921,11 +921,11 @@ def runTermCmd(cmd, cwd=os.getcwd(), bufsize=4096, wait=True, tmout=1.0, rows=20 if select.select([master,],[],[],0.0)[0]: output = os.read(master, bufsize) else: - output = b'' + output = b'' else: output = b'' while True: - read_fds,_,error_fds = select.select([master],[],[master],tmout) + read_fds,_,error_fds = select.select([master],[],[master],tmout) if len(error_fds): break; if len(read_fds): @@ -934,28 +934,28 @@ def runTermCmd(cmd, cwd=os.getcwd(), bufsize=4096, wait=True, tmout=1.0, rows=20 output += data else: break - else: + else: break; if not p.returncode == None: break - + subprocess.Popen.kill(p) os.close(master) return output -# 'latin-1' +# 'latin-1' def TermShellCmd(cmd, prompt='$ ', markdown=False, pretext='', posttext='', prenl=True, stripnl=False, height='100%', width='', outputlayout={'border': '1px solid black'}, noposttext=False, raw=False, encoding=sys.getdefaultencoding(), decodeerrors='replace', **kwargs): output = runTermCmd(cmd, **kwargs) output=output.decode('utf-8',decodeerrors) if stripnl: output.strip() - + if prenl: prenl=''' ''' else: prenl='' - + if height: outputlayout['height']=height outputlayout['overflow_y']='scroll' @@ -963,12 +963,12 @@ def TermShellCmd(cmd, prompt='$ ', markdown=False, pretext='', posttext='', pren if width: outputlayout['width']=width outputlayout['overflow_x']='auto' - + if prompt: pretext += prompt + cmd #+ "\n" if not noposttext: posttext += prompt - + if markdown: md = Markdown(htmlTerm(''' ''' + pretext + output + posttext )) @@ -1010,6 +1010,6 @@ def Answer(md): def setupExamples(name,files,basedir=os.getenv('HOME')): global exdir exdir=basedir + "/" + name - output=runTermCmd("[[ -d " + exdir + " ]] && rm -rf "+ exdir + - ";mkdir " + exdir + - ";cp " + files + " " + exdir) \ No newline at end of file + output=runTermCmd("[[ -d " + exdir + " ]] && rm -rf "+ exdir + + ";mkdir " + exdir + + ";cp " + files + " " + exdir) diff --git a/content/examples/python/ln_preamble.py b/content/examples/python/ln_preamble.py index 7990663..d80a0b7 100644 --- a/content/examples/python/ln_preamble.py +++ b/content/examples/python/ln_preamble.py @@ -28,7 +28,7 @@ def showDT(title="TERMINAL Window for Debugger"): api_url=localhost_url + 'api/' api_term_url=api_url + 'terminals' api_token=info['token'] - # urls used as relative to my server + # urls used as relative to my server base_url=info['base_url'] # on the operate-firrst jupyterhub I found that api_token is not set but @@ -44,7 +44,7 @@ def showDT(title="TERMINAL Window for Debugger"): print("ERROR: unable to deterimine API token"); - # get list of current terminals so that we can reuse this if enough exist + # get list of current terminals so that we can reuse this if enough exist # otherwise we will create new ones as needed r=requests.get(url=api_term_url, headers={'Authorization': 'token ' + api_token}) TERMINALS=r.json() @@ -64,12 +64,12 @@ def mkTerm(): try: BUILDTERM=TERMINALS[1]['name'] - except IndexError: + except IndexError: BUILDTERM=mkTerm() try: DEBUGGERTERM=TERMINALS[2]['name'] - except IndexError: + except IndexError: DEBUGGERTERM=mkTerm() @@ -93,10 +93,10 @@ def mkTerm(): # CSS customization for RISE has been moved # to .css which RISE attempts to load # see rise examples in the rise repo - # cusomization of ccs to make slides look better + # cusomization of ccs to make slides look better # display(HTML( # '