mirror of
https://github.com/jupyter/docker-stacks.git
synced 2025-10-11 12:02:56 +00:00
Create base image to base-notebook for non-server Jupyter applications (#1825)
* Create base-jupyter from base-notebook for non-server jupyter applications * Fix pre-commit errors and begin test refactoring * More test refactoring * Add base-jupyter to images_hierarchy * Use folder work instead of .jupyter in nb-user test * Add base-jupyter to tagging hierarchy * Linting: trailing comma * Apply review comments, remove obsolute Miniforge reference * Add self-signed cert comment back to base-notebook doc * Update docs/using/selecting.md Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com> * Remove redundant apt-get upgrade per review * Remove b/c approaches per review * Move test_nb_user_change back to base-notebook tests, per review * fix linting * Rename base-jupyter to docker-stacks-foundation, per review * Rename tests/base-jupyter to docker-stacks-foundation * Use alphabetical order * Use alphabetical order * Fix markdown style * Split test_nb_user_change between the foundation and base tests Co-authored-by: Ayaz Salikhov <mathbunnyru@users.noreply.github.com>
This commit is contained in:
20
.github/actions/download-manifests/action.yml
vendored
20
.github/actions/download-manifests/action.yml
vendored
@@ -18,6 +18,16 @@ inputs:
|
|||||||
runs:
|
runs:
|
||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
|
- name: Download artifact 📥
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: docker-stacks-foundation-aarch64-history_line
|
||||||
|
path: ${{ inputs.histLineDir }}
|
||||||
|
- name: Download artifact 📥
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: docker-stacks-foundation-x86_64-history_line
|
||||||
|
path: ${{ inputs.histLineDir }}
|
||||||
- name: Download artifact 📥
|
- name: Download artifact 📥
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
@@ -94,6 +104,16 @@ runs:
|
|||||||
name: all-spark-notebook-x86_64-history_line
|
name: all-spark-notebook-x86_64-history_line
|
||||||
path: ${{ inputs.histLineDir }}
|
path: ${{ inputs.histLineDir }}
|
||||||
|
|
||||||
|
- name: Download artifact 📥
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: docker-stacks-foundation-aarch64-manifest
|
||||||
|
path: ${{ inputs.manifestDir }}
|
||||||
|
- name: Download artifact 📥
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: docker-stacks-foundation-x86_64-manifest
|
||||||
|
path: ${{ inputs.manifestDir }}
|
||||||
- name: Download artifact 📥
|
- name: Download artifact 📥
|
||||||
uses: actions/download-artifact@v3
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
29
.github/workflows/docker.yml
vendored
29
.github/workflows/docker.yml
vendored
@@ -23,6 +23,7 @@ on:
|
|||||||
- "all-spark-notebook/**"
|
- "all-spark-notebook/**"
|
||||||
- "base-notebook/**"
|
- "base-notebook/**"
|
||||||
- "datascience-notebook/**"
|
- "datascience-notebook/**"
|
||||||
|
- "docker-stacks-foundation/**"
|
||||||
- "minimal-notebook/**"
|
- "minimal-notebook/**"
|
||||||
- "pyspark-notebook/**"
|
- "pyspark-notebook/**"
|
||||||
- "r-notebook/**"
|
- "r-notebook/**"
|
||||||
@@ -49,6 +50,7 @@ on:
|
|||||||
- "all-spark-notebook/**"
|
- "all-spark-notebook/**"
|
||||||
- "base-notebook/**"
|
- "base-notebook/**"
|
||||||
- "datascience-notebook/**"
|
- "datascience-notebook/**"
|
||||||
|
- "docker-stacks-foundation/**"
|
||||||
- "minimal-notebook/**"
|
- "minimal-notebook/**"
|
||||||
- "pyspark-notebook/**"
|
- "pyspark-notebook/**"
|
||||||
- "r-notebook/**"
|
- "r-notebook/**"
|
||||||
@@ -67,18 +69,36 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
aarch64-base:
|
aarch64-foundation:
|
||||||
uses: ./.github/workflows/docker-build-test-upload.yml
|
uses: ./.github/workflows/docker-build-test-upload.yml
|
||||||
with:
|
with:
|
||||||
parentImage: ""
|
parentImage: ""
|
||||||
|
image: docker-stacks-foundation
|
||||||
|
platform: aarch64
|
||||||
|
runsOn: ARM64
|
||||||
|
|
||||||
|
x86_64-foundation:
|
||||||
|
uses: ./.github/workflows/docker-build-test-upload.yml
|
||||||
|
with:
|
||||||
|
parentImage: ""
|
||||||
|
image: docker-stacks-foundation
|
||||||
|
platform: x86_64
|
||||||
|
runsOn: ubuntu-latest
|
||||||
|
|
||||||
|
aarch64-base:
|
||||||
|
needs: [aarch64-foundation]
|
||||||
|
uses: ./.github/workflows/docker-build-test-upload.yml
|
||||||
|
with:
|
||||||
|
parentImage: docker-stacks-foundation
|
||||||
image: base-notebook
|
image: base-notebook
|
||||||
platform: aarch64
|
platform: aarch64
|
||||||
runsOn: ARM64
|
runsOn: ARM64
|
||||||
|
|
||||||
x86_64-base:
|
x86_64-base:
|
||||||
|
needs: [x86_64-foundation]
|
||||||
uses: ./.github/workflows/docker-build-test-upload.yml
|
uses: ./.github/workflows/docker-build-test-upload.yml
|
||||||
with:
|
with:
|
||||||
parentImage: ""
|
parentImage: docker-stacks-foundation
|
||||||
image: base-notebook
|
image: base-notebook
|
||||||
platform: x86_64
|
platform: x86_64
|
||||||
runsOn: ubuntu-latest
|
runsOn: ubuntu-latest
|
||||||
@@ -206,6 +226,7 @@ jobs:
|
|||||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
needs:
|
needs:
|
||||||
[
|
[
|
||||||
|
aarch64-foundation,
|
||||||
aarch64-base,
|
aarch64-base,
|
||||||
aarch64-minimal,
|
aarch64-minimal,
|
||||||
aarch64-scipy,
|
aarch64-scipy,
|
||||||
@@ -218,6 +239,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
image:
|
image:
|
||||||
[
|
[
|
||||||
|
docker-stacks-foundation,
|
||||||
base-notebook,
|
base-notebook,
|
||||||
minimal-notebook,
|
minimal-notebook,
|
||||||
scipy-notebook,
|
scipy-notebook,
|
||||||
@@ -237,6 +259,7 @@ jobs:
|
|||||||
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
needs:
|
needs:
|
||||||
[
|
[
|
||||||
|
x86_64-foundation,
|
||||||
x86_64-base,
|
x86_64-base,
|
||||||
x86_64-minimal,
|
x86_64-minimal,
|
||||||
x86_64-scipy,
|
x86_64-scipy,
|
||||||
@@ -250,6 +273,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
image:
|
image:
|
||||||
[
|
[
|
||||||
|
docker-stacks-foundation,
|
||||||
base-notebook,
|
base-notebook,
|
||||||
minimal-notebook,
|
minimal-notebook,
|
||||||
scipy-notebook,
|
scipy-notebook,
|
||||||
@@ -273,6 +297,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
image:
|
image:
|
||||||
[
|
[
|
||||||
|
docker-stacks-foundation,
|
||||||
base-notebook,
|
base-notebook,
|
||||||
minimal-notebook,
|
minimal-notebook,
|
||||||
scipy-notebook,
|
scipy-notebook,
|
||||||
|
5
.github/workflows/hub-overview.yml
vendored
5
.github/workflows/hub-overview.yml
vendored
@@ -13,6 +13,7 @@ on:
|
|||||||
- "all-spark-notebook/README.md"
|
- "all-spark-notebook/README.md"
|
||||||
- "base-notebook/README.md"
|
- "base-notebook/README.md"
|
||||||
- "datascience-notebook/README.md"
|
- "datascience-notebook/README.md"
|
||||||
|
- "docker-stacks-foundation/README.md"
|
||||||
- "minimal-notebook/README.md"
|
- "minimal-notebook/README.md"
|
||||||
- "pyspark-notebook/README.md"
|
- "pyspark-notebook/README.md"
|
||||||
- "r-notebook/README.md"
|
- "r-notebook/README.md"
|
||||||
@@ -27,8 +28,10 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
- image: docker-stacks-foundation
|
||||||
|
description: "Small base image on which Jupyter applications can be built from https://github.com/jupyter/docker-stacks"
|
||||||
- image: base-notebook
|
- image: base-notebook
|
||||||
description: "Small base image for Jupyter Notebook stacks from https://github.com/jupyter/docker-stacks"
|
description: "Base image for Jupyter Notebook stacks from https://github.com/jupyter/docker-stacks"
|
||||||
- image: minimal-notebook
|
- image: minimal-notebook
|
||||||
description: "Minimal Jupyter Notebook Python Stack from https://github.com/jupyter/docker-stacks"
|
description: "Minimal Jupyter Notebook Python Stack from https://github.com/jupyter/docker-stacks"
|
||||||
- image: scipy-notebook
|
- image: scipy-notebook
|
||||||
|
2
Makefile
2
Makefile
@@ -9,6 +9,7 @@ OWNER?=jupyter
|
|||||||
# Need to list the images in build dependency order
|
# Need to list the images in build dependency order
|
||||||
# All of the images
|
# All of the images
|
||||||
ALL_IMAGES:= \
|
ALL_IMAGES:= \
|
||||||
|
docker-stacks-foundation \
|
||||||
base-notebook \
|
base-notebook \
|
||||||
minimal-notebook \
|
minimal-notebook \
|
||||||
r-notebook \
|
r-notebook \
|
||||||
@@ -19,6 +20,7 @@ ALL_IMAGES:= \
|
|||||||
all-spark-notebook
|
all-spark-notebook
|
||||||
|
|
||||||
AARCH64_IMAGES:= \
|
AARCH64_IMAGES:= \
|
||||||
|
docker-stacks-foundation \
|
||||||
base-notebook \
|
base-notebook \
|
||||||
minimal-notebook \
|
minimal-notebook \
|
||||||
r-notebook \
|
r-notebook \
|
||||||
|
@@ -1,16 +1,10 @@
|
|||||||
# Copyright (c) Jupyter Development Team.
|
# Copyright (c) Jupyter Development Team.
|
||||||
# Distributed under the terms of the Modified BSD License.
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
ARG OWNER=jupyter
|
||||||
# Ubuntu 22.04 (jammy)
|
ARG BASE_CONTAINER=$OWNER/docker-stacks-foundation
|
||||||
# https://hub.docker.com/_/ubuntu/tags?page=1&name=jammy
|
FROM $BASE_CONTAINER
|
||||||
ARG ROOT_CONTAINER=ubuntu:22.04
|
|
||||||
|
|
||||||
FROM $ROOT_CONTAINER
|
|
||||||
|
|
||||||
LABEL maintainer="Jupyter Project <jupyter@googlegroups.com>"
|
LABEL maintainer="Jupyter Project <jupyter@googlegroups.com>"
|
||||||
ARG NB_USER="jovyan"
|
|
||||||
ARG NB_UID="1000"
|
|
||||||
ARG NB_GID="100"
|
|
||||||
|
|
||||||
# Fix: https://github.com/hadolint/hadolint/wiki/DL4006
|
# Fix: https://github.com/hadolint/hadolint/wiki/DL4006
|
||||||
# Fix: https://github.com/koalaman/shellcheck/wiki/SC3014
|
# Fix: https://github.com/koalaman/shellcheck/wiki/SC3014
|
||||||
@@ -20,115 +14,31 @@ USER root
|
|||||||
|
|
||||||
# Install all OS dependencies for notebook server that starts but lacks all
|
# Install all OS dependencies for notebook server that starts but lacks all
|
||||||
# features (e.g., download as all possible file formats)
|
# features (e.g., download as all possible file formats)
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
|
||||||
RUN apt-get update --yes && \
|
RUN apt-get update --yes && \
|
||||||
# - apt-get upgrade is run to patch known vulnerabilities in apt-get packages as
|
|
||||||
# the ubuntu base image is rebuilt too seldom sometimes (less than once a month)
|
|
||||||
apt-get upgrade --yes && \
|
|
||||||
apt-get install --yes --no-install-recommends \
|
apt-get install --yes --no-install-recommends \
|
||||||
# - bzip2 is necessary to extract the micromamba executable.
|
|
||||||
bzip2 \
|
|
||||||
ca-certificates \
|
|
||||||
fonts-liberation \
|
fonts-liberation \
|
||||||
locales \
|
|
||||||
# - pandoc is used to convert notebooks to html files
|
# - pandoc is used to convert notebooks to html files
|
||||||
# it's not present in aarch64 ubuntu image, so we install it here
|
# it's not present in aarch64 ubuntu image, so we install it here
|
||||||
pandoc \
|
pandoc \
|
||||||
# - run-one - a wrapper script that runs no more
|
# - run-one - a wrapper script that runs no more
|
||||||
# than one unique instance of some command with a unique set of arguments,
|
# than one unique instance of some command with a unique set of arguments,
|
||||||
# we use `run-one-constantly` to support `RESTARTABLE` option
|
# we use `run-one-constantly` to support `RESTARTABLE` option
|
||||||
run-one \
|
run-one && \
|
||||||
sudo \
|
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
# - tini is installed as a helpful container entrypoint that reaps zombie
|
|
||||||
# processes and such of the actual executable we want to start, see
|
|
||||||
# https://github.com/krallin/tini#why-tini for details.
|
|
||||||
tini \
|
|
||||||
wget && \
|
|
||||||
apt-get clean && rm -rf /var/lib/apt/lists/* && \
|
|
||||||
echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \
|
|
||||||
locale-gen
|
|
||||||
|
|
||||||
# Configure environment
|
|
||||||
ENV CONDA_DIR=/opt/conda \
|
|
||||||
SHELL=/bin/bash \
|
|
||||||
NB_USER="${NB_USER}" \
|
|
||||||
NB_UID=${NB_UID} \
|
|
||||||
NB_GID=${NB_GID} \
|
|
||||||
LC_ALL=en_US.UTF-8 \
|
|
||||||
LANG=en_US.UTF-8 \
|
|
||||||
LANGUAGE=en_US.UTF-8
|
|
||||||
ENV PATH="${CONDA_DIR}/bin:${PATH}" \
|
|
||||||
HOME="/home/${NB_USER}"
|
|
||||||
|
|
||||||
# Copy a script that we will use to correct permissions after running certain commands
|
|
||||||
COPY fix-permissions /usr/local/bin/fix-permissions
|
|
||||||
RUN chmod a+rx /usr/local/bin/fix-permissions
|
|
||||||
|
|
||||||
# Enable prompt color in the skeleton .bashrc before creating the default NB_USER
|
|
||||||
# hadolint ignore=SC2016
|
|
||||||
RUN sed -i 's/^#force_color_prompt=yes/force_color_prompt=yes/' /etc/skel/.bashrc && \
|
|
||||||
# Add call to conda init script see https://stackoverflow.com/a/58081608/4413446
|
|
||||||
echo 'eval "$(command conda shell.bash hook 2> /dev/null)"' >> /etc/skel/.bashrc
|
|
||||||
|
|
||||||
# Create NB_USER with name jovyan user with UID=1000 and in the 'users' group
|
|
||||||
# and make sure these dirs are writable by the `users` group.
|
|
||||||
RUN echo "auth requisite pam_deny.so" >> /etc/pam.d/su && \
|
|
||||||
sed -i.bak -e 's/^%admin/#%admin/' /etc/sudoers && \
|
|
||||||
sed -i.bak -e 's/^%sudo/#%sudo/' /etc/sudoers && \
|
|
||||||
useradd -l -m -s /bin/bash -N -u "${NB_UID}" "${NB_USER}" && \
|
|
||||||
mkdir -p "${CONDA_DIR}" && \
|
|
||||||
chown "${NB_USER}:${NB_GID}" "${CONDA_DIR}" && \
|
|
||||||
chmod g+w /etc/passwd && \
|
|
||||||
fix-permissions "${HOME}" && \
|
|
||||||
fix-permissions "${CONDA_DIR}"
|
|
||||||
|
|
||||||
USER ${NB_UID}
|
USER ${NB_UID}
|
||||||
|
|
||||||
# Pin python version here, or set it to "default"
|
# Install Jupyter Notebook, Lab, and Hub
|
||||||
ARG PYTHON_VERSION=3.10
|
|
||||||
|
|
||||||
# Setup work directory for backward-compatibility
|
|
||||||
RUN mkdir "/home/${NB_USER}/work" && \
|
|
||||||
fix-permissions "/home/${NB_USER}"
|
|
||||||
|
|
||||||
# Download and install Micromamba, and initialize Conda prefix.
|
|
||||||
# <https://github.com/mamba-org/mamba#micromamba>
|
|
||||||
# Similar projects using Micromamba:
|
|
||||||
# - Micromamba-Docker: <https://github.com/mamba-org/micromamba-docker>
|
|
||||||
# - repo2docker: <https://github.com/jupyterhub/repo2docker>
|
|
||||||
# Install Python, Mamba, Jupyter Notebook, Lab, and Hub
|
|
||||||
# Generate a notebook server config
|
# Generate a notebook server config
|
||||||
# Cleanup temporary files and remove Micromamba
|
# Cleanup temporary files
|
||||||
# Correct permissions
|
# Correct permissions
|
||||||
# Do all this in a single RUN command to avoid duplicating all of the
|
# Do all this in a single RUN command to avoid duplicating all of the
|
||||||
# files across image layers when the permissions change
|
# files across image layers when the permissions change
|
||||||
COPY --chown="${NB_UID}:${NB_GID}" initial-condarc "${CONDA_DIR}/.condarc"
|
|
||||||
WORKDIR /tmp
|
WORKDIR /tmp
|
||||||
RUN set -x && \
|
RUN mamba install --quiet --yes \
|
||||||
arch=$(uname -m) && \
|
'notebook' \
|
||||||
if [ "${arch}" = "x86_64" ]; then \
|
'jupyterhub' \
|
||||||
# Should be simpler, see <https://github.com/mamba-org/mamba/issues/1437>
|
'jupyterlab' && \
|
||||||
arch="64"; \
|
|
||||||
fi && \
|
|
||||||
wget -qO /tmp/micromamba.tar.bz2 \
|
|
||||||
"https://micromamba.snakepit.net/api/micromamba/linux-${arch}/latest" && \
|
|
||||||
tar -xvjf /tmp/micromamba.tar.bz2 --strip-components=1 bin/micromamba && \
|
|
||||||
rm /tmp/micromamba.tar.bz2 && \
|
|
||||||
PYTHON_SPECIFIER="python=${PYTHON_VERSION}" && \
|
|
||||||
if [[ "${PYTHON_VERSION}" == "default" ]]; then PYTHON_SPECIFIER="python"; fi && \
|
|
||||||
# Install the packages
|
|
||||||
./micromamba install \
|
|
||||||
--root-prefix="${CONDA_DIR}" \
|
|
||||||
--prefix="${CONDA_DIR}" \
|
|
||||||
--yes \
|
|
||||||
"${PYTHON_SPECIFIER}" \
|
|
||||||
'mamba' \
|
|
||||||
'notebook' \
|
|
||||||
'jupyterhub' \
|
|
||||||
'jupyterlab' && \
|
|
||||||
rm micromamba && \
|
|
||||||
# Pin major.minor version of python
|
|
||||||
mamba list python | grep '^python ' | tr -s ' ' | cut -d ' ' -f 1,2 >> "${CONDA_DIR}/conda-meta/pinned" && \
|
|
||||||
jupyter notebook --generate-config && \
|
jupyter notebook --generate-config && \
|
||||||
mamba clean --all -f -y && \
|
mamba clean --all -f -y && \
|
||||||
npm cache clean --force && \
|
npm cache clean --force && \
|
||||||
@@ -140,11 +50,10 @@ RUN set -x && \
|
|||||||
EXPOSE 8888
|
EXPOSE 8888
|
||||||
|
|
||||||
# Configure container startup
|
# Configure container startup
|
||||||
ENTRYPOINT ["tini", "-g", "--"]
|
|
||||||
CMD ["start-notebook.sh"]
|
CMD ["start-notebook.sh"]
|
||||||
|
|
||||||
# Copy local files as late as possible to avoid cache busting
|
# Copy local files as late as possible to avoid cache busting
|
||||||
COPY start.sh start-notebook.sh start-singleuser.sh /usr/local/bin/
|
COPY start-notebook.sh start-singleuser.sh /usr/local/bin/
|
||||||
# Currently need to have both jupyter_notebook_config and jupyter_server_config to support classic and lab
|
# Currently need to have both jupyter_notebook_config and jupyter_server_config to support classic and lab
|
||||||
COPY jupyter_server_config.py /etc/jupyter/
|
COPY jupyter_server_config.py /etc/jupyter/
|
||||||
|
|
||||||
|
2
docker-stacks-foundation/.dockerignore
Normal file
2
docker-stacks-foundation/.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# Documentation
|
||||||
|
README.md
|
135
docker-stacks-foundation/Dockerfile
Normal file
135
docker-stacks-foundation/Dockerfile
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# Copyright (c) Jupyter Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
# Ubuntu 22.04 (jammy)
|
||||||
|
# https://hub.docker.com/_/ubuntu/tags?page=1&name=jammy
|
||||||
|
ARG ROOT_CONTAINER=ubuntu:22.04
|
||||||
|
|
||||||
|
FROM $ROOT_CONTAINER
|
||||||
|
|
||||||
|
LABEL maintainer="Jupyter Project <jupyter@googlegroups.com>"
|
||||||
|
ARG NB_USER="jovyan"
|
||||||
|
ARG NB_UID="1000"
|
||||||
|
ARG NB_GID="100"
|
||||||
|
|
||||||
|
# Fix: https://github.com/hadolint/hadolint/wiki/DL4006
|
||||||
|
# Fix: https://github.com/koalaman/shellcheck/wiki/SC3014
|
||||||
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
|
USER root
|
||||||
|
|
||||||
|
# Install all OS dependencies for notebook server that starts but lacks all
|
||||||
|
# features (e.g., download as all possible file formats)
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
RUN apt-get update --yes && \
|
||||||
|
# - apt-get upgrade is run to patch known vulnerabilities in apt-get packages as
|
||||||
|
# the ubuntu base image is rebuilt too seldom sometimes (less than once a month)
|
||||||
|
apt-get upgrade --yes && \
|
||||||
|
apt-get install --yes --no-install-recommends \
|
||||||
|
# - bzip2 is necessary to extract the micromamba executable.
|
||||||
|
bzip2 \
|
||||||
|
ca-certificates \
|
||||||
|
locales \
|
||||||
|
sudo \
|
||||||
|
# - tini is installed as a helpful container entrypoint that reaps zombie
|
||||||
|
# processes and such of the actual executable we want to start, see
|
||||||
|
# https://github.com/krallin/tini#why-tini for details.
|
||||||
|
tini \
|
||||||
|
wget && \
|
||||||
|
apt-get clean && rm -rf /var/lib/apt/lists/* && \
|
||||||
|
echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \
|
||||||
|
locale-gen
|
||||||
|
|
||||||
|
# Configure environment
|
||||||
|
ENV CONDA_DIR=/opt/conda \
|
||||||
|
SHELL=/bin/bash \
|
||||||
|
NB_USER="${NB_USER}" \
|
||||||
|
NB_UID=${NB_UID} \
|
||||||
|
NB_GID=${NB_GID} \
|
||||||
|
LC_ALL=en_US.UTF-8 \
|
||||||
|
LANG=en_US.UTF-8 \
|
||||||
|
LANGUAGE=en_US.UTF-8
|
||||||
|
ENV PATH="${CONDA_DIR}/bin:${PATH}" \
|
||||||
|
HOME="/home/${NB_USER}"
|
||||||
|
|
||||||
|
# Copy a script that we will use to correct permissions after running certain commands
|
||||||
|
COPY fix-permissions /usr/local/bin/fix-permissions
|
||||||
|
RUN chmod a+rx /usr/local/bin/fix-permissions
|
||||||
|
|
||||||
|
# Enable prompt color in the skeleton .bashrc before creating the default NB_USER
|
||||||
|
# hadolint ignore=SC2016
|
||||||
|
RUN sed -i 's/^#force_color_prompt=yes/force_color_prompt=yes/' /etc/skel/.bashrc && \
|
||||||
|
# Add call to conda init script see https://stackoverflow.com/a/58081608/4413446
|
||||||
|
echo 'eval "$(command conda shell.bash hook 2> /dev/null)"' >> /etc/skel/.bashrc
|
||||||
|
|
||||||
|
# Create NB_USER with name jovyan user with UID=1000 and in the 'users' group
|
||||||
|
# and make sure these dirs are writable by the `users` group.
|
||||||
|
RUN echo "auth requisite pam_deny.so" >> /etc/pam.d/su && \
|
||||||
|
sed -i.bak -e 's/^%admin/#%admin/' /etc/sudoers && \
|
||||||
|
sed -i.bak -e 's/^%sudo/#%sudo/' /etc/sudoers && \
|
||||||
|
useradd -l -m -s /bin/bash -N -u "${NB_UID}" "${NB_USER}" && \
|
||||||
|
mkdir -p "${CONDA_DIR}" && \
|
||||||
|
chown "${NB_USER}:${NB_GID}" "${CONDA_DIR}" && \
|
||||||
|
chmod g+w /etc/passwd && \
|
||||||
|
fix-permissions "${HOME}" && \
|
||||||
|
fix-permissions "${CONDA_DIR}"
|
||||||
|
|
||||||
|
USER ${NB_UID}
|
||||||
|
|
||||||
|
# Pin python version here, or set it to "default"
|
||||||
|
ARG PYTHON_VERSION=3.10
|
||||||
|
|
||||||
|
# Setup work directory for backward-compatibility
|
||||||
|
RUN mkdir "/home/${NB_USER}/work" && \
|
||||||
|
fix-permissions "/home/${NB_USER}"
|
||||||
|
|
||||||
|
# Download and install Micromamba, and initialize Conda prefix.
|
||||||
|
# <https://github.com/mamba-org/mamba#micromamba>
|
||||||
|
# Similar projects using Micromamba:
|
||||||
|
# - Micromamba-Docker: <https://github.com/mamba-org/micromamba-docker>
|
||||||
|
# - repo2docker: <https://github.com/jupyterhub/repo2docker>
|
||||||
|
# Install Python, Mamba and jupyter_core
|
||||||
|
# Cleanup temporary files and remove Micromamba
|
||||||
|
# Correct permissions
|
||||||
|
# Do all this in a single RUN command to avoid duplicating all of the
|
||||||
|
# files across image layers when the permissions change
|
||||||
|
COPY --chown="${NB_UID}:${NB_GID}" initial-condarc "${CONDA_DIR}/.condarc"
|
||||||
|
WORKDIR /tmp
|
||||||
|
RUN set -x && \
|
||||||
|
arch=$(uname -m) && \
|
||||||
|
if [ "${arch}" = "x86_64" ]; then \
|
||||||
|
# Should be simpler, see <https://github.com/mamba-org/mamba/issues/1437>
|
||||||
|
arch="64"; \
|
||||||
|
fi && \
|
||||||
|
wget -qO /tmp/micromamba.tar.bz2 \
|
||||||
|
"https://micromamba.snakepit.net/api/micromamba/linux-${arch}/latest" && \
|
||||||
|
tar -xvjf /tmp/micromamba.tar.bz2 --strip-components=1 bin/micromamba && \
|
||||||
|
rm /tmp/micromamba.tar.bz2 && \
|
||||||
|
PYTHON_SPECIFIER="python=${PYTHON_VERSION}" && \
|
||||||
|
if [[ "${PYTHON_VERSION}" == "default" ]]; then PYTHON_SPECIFIER="python"; fi && \
|
||||||
|
# Install the packages
|
||||||
|
./micromamba install \
|
||||||
|
--root-prefix="${CONDA_DIR}" \
|
||||||
|
--prefix="${CONDA_DIR}" \
|
||||||
|
--yes \
|
||||||
|
"${PYTHON_SPECIFIER}" \
|
||||||
|
'mamba' \
|
||||||
|
'jupyter_core' && \
|
||||||
|
rm micromamba && \
|
||||||
|
# Pin major.minor version of python
|
||||||
|
mamba list python | grep '^python ' | tr -s ' ' | cut -d ' ' -f 1,2 >> "${CONDA_DIR}/conda-meta/pinned" && \
|
||||||
|
mamba clean --all -f -y && \
|
||||||
|
fix-permissions "${CONDA_DIR}" && \
|
||||||
|
fix-permissions "/home/${NB_USER}"
|
||||||
|
|
||||||
|
# Configure container startup
|
||||||
|
ENTRYPOINT ["tini", "-g", "--"]
|
||||||
|
CMD ["start.sh"]
|
||||||
|
|
||||||
|
# Copy local files as late as possible to avoid cache busting
|
||||||
|
COPY start.sh /usr/local/bin/
|
||||||
|
|
||||||
|
# Switch back to jovyan to avoid accidental container runs as root
|
||||||
|
USER ${NB_UID}
|
||||||
|
|
||||||
|
WORKDIR "${HOME}"
|
12
docker-stacks-foundation/README.md
Normal file
12
docker-stacks-foundation/README.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Base Jupyter Stack
|
||||||
|
|
||||||
|
[](https://hub.docker.com/r/jupyter/docker-stacks-foundation/)
|
||||||
|
[](https://hub.docker.com/r/jupyter/docker-stacks-foundation/)
|
||||||
|
[](https://hub.docker.com/r/jupyter/docker-stacks-foundation/ "jupyter/docker-stacks-foundation image size")
|
||||||
|
|
||||||
|
GitHub Actions in the <https://github.com/jupyter/docker-stacks> project builds and pushes this image to Docker Hub.
|
||||||
|
|
||||||
|
Please visit the project documentation site for help to use and contribute to this image and others.
|
||||||
|
|
||||||
|
- [Jupyter Docker Stacks on ReadTheDocs](https://jupyter-docker-stacks.readthedocs.io/en/latest/index.html)
|
||||||
|
- [Selecting an Image :: Core Stacks :: jupyter/docker-stacks-foundation](https://jupyter-docker-stacks.readthedocs.io/en/latest/using/selecting.html#jupyter-docker-stacks-foundation)
|
@@ -11,10 +11,10 @@ We use `pytest` module to run tests on the image.
|
|||||||
`conftest.py` and `pytest.ini` in the `tests` folder define the environment in which tests are run.
|
`conftest.py` and `pytest.ini` in the `tests` folder define the environment in which tests are run.
|
||||||
More info on `pytest` can be found [here](https://docs.pytest.org/en/latest/contents.html).
|
More info on `pytest` can be found [here](https://docs.pytest.org/en/latest/contents.html).
|
||||||
|
|
||||||
The actual image-specific test files are located in folders like `tests/<somestack>-notebook/`.
|
The actual image-specific test files are located in folders like `tests/<somestack>/` (e.g., `tests/docker-stacks-foundation/`, `tests/minimal-notebook/`, etc.).
|
||||||
|
|
||||||
```{note}
|
```{note}
|
||||||
If your test is located in `tests/<somestack>-notebook/`, it will be run against `jupyter/<somestack>-notebook` image and against all the [images inherited from this image](https://jupyter-docker-stacks.readthedocs.io/en/latest/using/selecting.html#image-relationships.
|
If your test is located in `tests/<somestack>/`, it will be run against `jupyter/<somestack>` image and against all the [images inherited from this image](https://jupyter-docker-stacks.readthedocs.io/en/latest/using/selecting.html#image-relationships.
|
||||||
```
|
```
|
||||||
|
|
||||||
Many tests make use of global [pytest fixtures](https://docs.pytest.org/en/latest/reference/fixtures.html)
|
Many tests make use of global [pytest fixtures](https://docs.pytest.org/en/latest/reference/fixtures.html)
|
||||||
@@ -23,7 +23,7 @@ defined in the [conftest.py](https://github.com/jupyter/docker-stacks/blob/main/
|
|||||||
## Unit tests
|
## Unit tests
|
||||||
|
|
||||||
If you want to run a python script in one of our images, you could add a unit test.
|
If you want to run a python script in one of our images, you could add a unit test.
|
||||||
You can do this by creating a `tests/<somestack>-notebook/units/` directory, if it doesn't already exist and put your file there.
|
You can do this by creating a `tests/<somestack>/units/` directory, if it doesn't already exist and put your file there.
|
||||||
Files in this folder will be executed in the container when tests are run.
|
Files in this folder will be executed in the container when tests are run.
|
||||||
You could see an [example for the TensorFlow package here](https://github.com/jupyter/docker-stacks/blob/HEAD/tests/tensorflow-notebook/units/unit_tensorflow.py).
|
You could see an [example for the TensorFlow package here](https://github.com/jupyter/docker-stacks/blob/HEAD/tests/tensorflow-notebook/units/unit_tensorflow.py).
|
||||||
|
|
||||||
@@ -31,13 +31,13 @@ You could see an [example for the TensorFlow package here](https://github.com/ju
|
|||||||
|
|
||||||
Please follow the process below to add new tests:
|
Please follow the process below to add new tests:
|
||||||
|
|
||||||
1. Add your test code to one of the modules in `tests/<somestack>-notebook/` directory or create a new module.
|
1. Add your test code to one of the modules in `tests/<somestack>/` directory or create a new module.
|
||||||
2. Build one or more images you intend to test and run the tests locally.
|
2. Build one or more images you intend to test and run the tests locally.
|
||||||
If you use `make`, call:
|
If you use `make`, call:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make build/<somestack>-notebook
|
make build/<somestack>
|
||||||
make test/<somestack>-notebook
|
make test/<somestack>
|
||||||
```
|
```
|
||||||
|
|
||||||
3. [Submit a pull request](https://github.com/PointCloudLibrary/pcl/wiki/A-step-by-step-guide-on-preparing-and-submitting-a-pull-request)
|
3. [Submit a pull request](https://github.com/PointCloudLibrary/pcl/wiki/A-step-by-step-guide-on-preparing-and-submitting-a-pull-request)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version='1.0' encoding='UTF-8'?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
<svg viewBox="0 0 1216 280" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink">
|
<svg viewBox="0 0 1408 280" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
<defs id="defs_block">
|
<defs id="defs_block">
|
||||||
<filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
|
<filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252">
|
||||||
<feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2" />
|
<feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2" />
|
||||||
@@ -12,51 +12,56 @@
|
|||||||
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="259" y="46" />
|
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="259" y="46" />
|
||||||
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="451" y="46" />
|
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="451" y="46" />
|
||||||
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="643" y="46" />
|
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="643" y="46" />
|
||||||
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="643" y="126" />
|
|
||||||
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="835" y="46" />
|
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="835" y="46" />
|
||||||
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="835" y="126" />
|
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="835" y="126" />
|
||||||
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="835" y="206" />
|
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="1027" y="46" />
|
||||||
|
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="1027" y="126" />
|
||||||
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="1027" y="206" />
|
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="1027" y="206" />
|
||||||
|
<rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="128" x="1219" y="206" />
|
||||||
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="40" />
|
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="64" y="40" />
|
||||||
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="30" x="128" y="59">ubuntu</text>
|
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="30" x="128" y="59">ubuntu</text>
|
||||||
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="119" x="128" y="70">(LTS with point release)</text>
|
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="119" x="128" y="70">(LTS with point release)</text>
|
||||||
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="256" y="40" />
|
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="256" y="40" />
|
||||||
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="65" x="320" y="65">base-notebook</text>
|
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="119" x="320" y="65">docker-stacks-foundation</text>
|
||||||
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="448" y="40" />
|
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="448" y="40" />
|
||||||
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="80" x="512" y="65">minimal-notebook</text>
|
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="65" x="512" y="65">base-notebook</text>
|
||||||
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="640" y="40" />
|
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="640" y="40" />
|
||||||
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="70" x="704" y="65">scipy-notebook</text>
|
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="80" x="704" y="65">minimal-notebook</text>
|
||||||
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="640" y="120" />
|
|
||||||
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="50" x="704" y="145">r-notebook</text>
|
|
||||||
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="832" y="40" />
|
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="832" y="40" />
|
||||||
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="95" x="896" y="59">tensorflow-notebook</text>
|
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="70" x="896" y="65">scipy-notebook</text>
|
||||||
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="65" x="896" y="70">(only x86_64)</text>
|
|
||||||
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="832" y="120" />
|
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="832" y="120" />
|
||||||
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="100" x="896" y="145">datascience-notebook</text>
|
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="50" x="896" y="145">r-notebook</text>
|
||||||
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="832" y="200" />
|
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="1024" y="40" />
|
||||||
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="80" x="896" y="225">pyspark-notebook</text>
|
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="95" x="1088" y="59">tensorflow-notebook</text>
|
||||||
|
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="65" x="1088" y="70">(only x86_64)</text>
|
||||||
|
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="1024" y="120" />
|
||||||
|
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="100" x="1088" y="145">datascience-notebook</text>
|
||||||
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="1024" y="200" />
|
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="1024" y="200" />
|
||||||
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="90" x="1088" y="225">all-spark-notebook</text>
|
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="80" x="1088" y="225">pyspark-notebook</text>
|
||||||
|
<rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="128" x="1216" y="200" />
|
||||||
|
<text fill="rgb(0,0,0)" font-family="sans-serif" font-size="9" font-style="normal" font-weight="normal" text-anchor="middle" textLength="90" x="1280" y="225">all-spark-notebook</text>
|
||||||
<path d="M 192 60 L 248 60" fill="none" stroke="rgb(0,0,0)" />
|
<path d="M 192 60 L 248 60" fill="none" stroke="rgb(0,0,0)" />
|
||||||
<polygon fill="rgb(0,0,0)" points="255,60 248,56 248,64 255,60" stroke="rgb(0,0,0)" />
|
<polygon fill="rgb(0,0,0)" points="255,60 248,56 248,64 255,60" stroke="rgb(0,0,0)" />
|
||||||
<path d="M 384 60 L 440 60" fill="none" stroke="rgb(0,0,0)" />
|
<path d="M 384 60 L 440 60" fill="none" stroke="rgb(0,0,0)" />
|
||||||
<polygon fill="rgb(0,0,0)" points="447,60 440,56 440,64 447,60" stroke="rgb(0,0,0)" />
|
<polygon fill="rgb(0,0,0)" points="447,60 440,56 440,64 447,60" stroke="rgb(0,0,0)" />
|
||||||
<path d="M 576 60 L 632 60" fill="none" stroke="rgb(0,0,0)" />
|
<path d="M 576 60 L 632 60" fill="none" stroke="rgb(0,0,0)" />
|
||||||
<polygon fill="rgb(0,0,0)" points="639,60 632,56 632,64 639,60" stroke="rgb(0,0,0)" />
|
<polygon fill="rgb(0,0,0)" points="639,60 632,56 632,64 639,60" stroke="rgb(0,0,0)" />
|
||||||
<path d="M 576 60 L 608 60" fill="none" stroke="rgb(0,0,0)" />
|
<path d="M 768 60 L 824 60" fill="none" stroke="rgb(0,0,0)" />
|
||||||
<path d="M 608 60 L 608 140" fill="none" stroke="rgb(0,0,0)" />
|
<polygon fill="rgb(0,0,0)" points="831,60 824,56 824,64 831,60" stroke="rgb(0,0,0)" />
|
||||||
<path d="M 608 140 L 632 140" fill="none" stroke="rgb(0,0,0)" />
|
|
||||||
<polygon fill="rgb(0,0,0)" points="639,140 632,136 632,144 639,140" stroke="rgb(0,0,0)" />
|
|
||||||
<path d="M 768 60 L 800 60" fill="none" stroke="rgb(0,0,0)" />
|
<path d="M 768 60 L 800 60" fill="none" stroke="rgb(0,0,0)" />
|
||||||
<path d="M 800 60 L 800 140" fill="none" stroke="rgb(0,0,0)" />
|
<path d="M 800 60 L 800 140" fill="none" stroke="rgb(0,0,0)" />
|
||||||
<path d="M 800 140 L 824 140" fill="none" stroke="rgb(0,0,0)" />
|
<path d="M 800 140 L 824 140" fill="none" stroke="rgb(0,0,0)" />
|
||||||
<polygon fill="rgb(0,0,0)" points="831,140 824,136 824,144 831,140" stroke="rgb(0,0,0)" />
|
<polygon fill="rgb(0,0,0)" points="831,140 824,136 824,144 831,140" stroke="rgb(0,0,0)" />
|
||||||
<path d="M 768 60 L 824 60" fill="none" stroke="rgb(0,0,0)" />
|
<path d="M 960 60 L 1016 60" fill="none" stroke="rgb(0,0,0)" />
|
||||||
<polygon fill="rgb(0,0,0)" points="831,60 824,56 824,64 831,60" stroke="rgb(0,0,0)" />
|
<polygon fill="rgb(0,0,0)" points="1023,60 1016,56 1016,64 1023,60" stroke="rgb(0,0,0)" />
|
||||||
<path d="M 768 60 L 800 60" fill="none" stroke="rgb(0,0,0)" />
|
<path d="M 960 60 L 992 60" fill="none" stroke="rgb(0,0,0)" />
|
||||||
<path d="M 800 60 L 800 220" fill="none" stroke="rgb(0,0,0)" />
|
<path d="M 992 60 L 992 220" fill="none" stroke="rgb(0,0,0)" />
|
||||||
<path d="M 800 220 L 824 220" fill="none" stroke="rgb(0,0,0)" />
|
<path d="M 992 220 L 1016 220" fill="none" stroke="rgb(0,0,0)" />
|
||||||
<polygon fill="rgb(0,0,0)" points="831,220 824,216 824,224 831,220" stroke="rgb(0,0,0)" />
|
|
||||||
<path d="M 960 220 L 1016 220" fill="none" stroke="rgb(0,0,0)" />
|
|
||||||
<polygon fill="rgb(0,0,0)" points="1023,220 1016,216 1016,224 1023,220" stroke="rgb(0,0,0)" />
|
<polygon fill="rgb(0,0,0)" points="1023,220 1016,216 1016,224 1023,220" stroke="rgb(0,0,0)" />
|
||||||
|
<path d="M 960 60 L 992 60" fill="none" stroke="rgb(0,0,0)" />
|
||||||
|
<path d="M 992 60 L 992 140" fill="none" stroke="rgb(0,0,0)" />
|
||||||
|
<path d="M 992 140 L 1016 140" fill="none" stroke="rgb(0,0,0)" />
|
||||||
|
<polygon fill="rgb(0,0,0)" points="1023,140 1016,136 1016,144 1023,140" stroke="rgb(0,0,0)" />
|
||||||
|
<path d="M 1152 220 L 1208 220" fill="none" stroke="rgb(0,0,0)" />
|
||||||
|
<polygon fill="rgb(0,0,0)" points="1215,220 1208,216 1208,224 1215,220" stroke="rgb(0,0,0)" />
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 6.9 KiB |
@@ -26,9 +26,9 @@ When a new `Python` version is released, we wait for two things:
|
|||||||
|
|
||||||
## Updating the Ubuntu Base Image
|
## Updating the Ubuntu Base Image
|
||||||
|
|
||||||
`base-notebook` is based on the LTS Ubuntu docker image.
|
`docker-stacks-foundation` is based on the LTS Ubuntu docker image.
|
||||||
We wait for the first point release of the new LTS Ubuntu before updating the version.
|
We wait for the first point release of the new LTS Ubuntu before updating the version.
|
||||||
Other images are directly or indirectly inherited from `base-notebook`.
|
Other images are directly or indirectly inherited from `docker-stacks-foundation`.
|
||||||
We rebuild our images automatically each week, which means they frequently receive the updates.
|
We rebuild our images automatically each week, which means they frequently receive the updates.
|
||||||
|
|
||||||
When there's a security fix in the Ubuntu base image, it's a good idea to manually trigger images rebuild [from the GitHub actions workflow UI](https://github.com/jupyter/docker-stacks/actions/workflows/docker.yml).
|
When there's a security fix in the Ubuntu base image, it's a good idea to manually trigger images rebuild [from the GitHub actions workflow UI](https://github.com/jupyter/docker-stacks/actions/workflows/docker.yml).
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# Common Features
|
# Common Features
|
||||||
|
|
||||||
By default, a container launched from any Jupyter Docker Stacks image runs a Jupyter Server with JupyterLab frontend.
|
Except `jupyter/docker-stacks-foundation`, a container launched from any Jupyter Docker Stacks image runs a Jupyter Server with JupyterLab frontend.
|
||||||
The container does so by executing a `start-notebook.sh` script.
|
The container does so by executing a `start-notebook.sh` script.
|
||||||
This script configures the internal container environment and then runs `jupyter lab`, passing any command-line arguments received.
|
This script configures the internal container environment and then runs `jupyter lab`, passing any command-line arguments received.
|
||||||
|
|
||||||
|
@@ -16,27 +16,46 @@ This section provides details about the first.
|
|||||||
The Jupyter team maintains a set of Docker image definitions in the <https://github.com/jupyter/docker-stacks> GitHub repository.
|
The Jupyter team maintains a set of Docker image definitions in the <https://github.com/jupyter/docker-stacks> GitHub repository.
|
||||||
The following sections describe these images, including their contents, relationships, and versioning strategy.
|
The following sections describe these images, including their contents, relationships, and versioning strategy.
|
||||||
|
|
||||||
|
### jupyter/docker-stacks-foundation
|
||||||
|
|
||||||
|
[Source on GitHub](https://github.com/jupyter/docker-stacks/tree/main/docker-stacks-foundation) |
|
||||||
|
[Dockerfile commit history](https://github.com/jupyter/docker-stacks/commits/main/docker-stacks-foundation/Dockerfile) |
|
||||||
|
[Docker Hub image tags](https://hub.docker.com/r/jupyter/docker-stacks-foundation/tags/)
|
||||||
|
|
||||||
|
`jupyter/docker-stacks-foundation` is a small image supporting a majority of [options common across all core stacks](common.md).
|
||||||
|
It is the basis for all other stacks on which Jupyter-related applications can be built
|
||||||
|
(e.g., kernel-based containers, [nbclient](https://github.com/jupyter/nbclient) applications, etc.).
|
||||||
|
As such, it does not contain application-level software like Jupyter Notebook server, Jupyter Lab or Jupyter Hub.
|
||||||
|
|
||||||
|
It contains:
|
||||||
|
|
||||||
|
- Package managers
|
||||||
|
- [conda](https://github.com/conda/conda): "cross-platform, language-agnostic binary package manager".
|
||||||
|
- [mamba](https://github.com/mamba-org/mamba): "reimplementation of the conda package manager in C++". We use this package manager by default when installing packages.
|
||||||
|
- Unprivileged user `jovyan` (`uid=1000`, configurable, [see options in the common features section](./common.md) of this documentation) in group `users` (`gid=100`)
|
||||||
|
with ownership over the `/home/jovyan` and `/opt/conda` paths
|
||||||
|
- `tini` as the container entrypoint
|
||||||
|
- A `start.sh` script as the default command - useful for running alternative commands in the container as applications are added (e.g. `ipython`, `jupyter kernelgateway`, `jupyter lab`)
|
||||||
|
- Options for a passwordless sudo
|
||||||
|
- No preinstalled scientific computing packages
|
||||||
|
|
||||||
### jupyter/base-notebook
|
### jupyter/base-notebook
|
||||||
|
|
||||||
[Source on GitHub](https://github.com/jupyter/docker-stacks/tree/main/base-notebook) |
|
[Source on GitHub](https://github.com/jupyter/docker-stacks/tree/main/base-notebook) |
|
||||||
[Dockerfile commit history](https://github.com/jupyter/docker-stacks/commits/main/base-notebook/Dockerfile) |
|
[Dockerfile commit history](https://github.com/jupyter/docker-stacks/commits/main/base-notebook/Dockerfile) |
|
||||||
[Docker Hub image tags](https://hub.docker.com/r/jupyter/base-notebook/tags/)
|
[Docker Hub image tags](https://hub.docker.com/r/jupyter/base-notebook/tags/)
|
||||||
|
|
||||||
`jupyter/base-notebook` is a small image supporting the [options common across all core stacks](common.md).
|
`jupyter/base-notebook` adds base Jupyter server applications like Notebook, Jupyter Lab and Jupyter Hub
|
||||||
It is the basis for all other stacks and contains:
|
and serves as the basis for all other stacks besides `jupyter/docker-stacks-foundation`.
|
||||||
|
|
||||||
|
It contains:
|
||||||
|
|
||||||
|
- Everything in `jupyter/docker-stacks-foundation`
|
||||||
- Minimally-functional Jupyter Notebook server (e.g., no LaTeX support for saving notebooks as PDFs)
|
- Minimally-functional Jupyter Notebook server (e.g., no LaTeX support for saving notebooks as PDFs)
|
||||||
- [Miniforge](https://github.com/conda-forge/miniforge) Python 3.x in `/opt/conda` with two package managers
|
|
||||||
- [conda](https://github.com/conda/conda): "cross-platform, language-agnostic binary package manager".
|
|
||||||
- [mamba](https://github.com/mamba-org/mamba): "reimplementation of the conda package manager in C++". We use this package manager by default when installing packages.
|
|
||||||
- `notebook`, `jupyterhub` and `jupyterlab` packages
|
- `notebook`, `jupyterhub` and `jupyterlab` packages
|
||||||
- No preinstalled scientific computing packages
|
- A `start-notebook.sh` script as the default command
|
||||||
- Unprivileged user `jovyan` (`uid=1000`, configurable, [see options in the common features section](./common.md) of this documentation) in group `users` (`gid=100`)
|
|
||||||
with ownership over the `/home/jovyan` and `/opt/conda` paths
|
|
||||||
- `tini` as the container entrypoint and a `start-notebook.sh` script as the default command
|
|
||||||
- A `start-singleuser.sh` script useful for launching containers in JupyterHub
|
- A `start-singleuser.sh` script useful for launching containers in JupyterHub
|
||||||
- A `start.sh` script useful for running alternative commands in the container (e.g. `ipython`, `jupyter kernelgateway`, `jupyter lab`)
|
- Options for a self-signed HTTPS certificate
|
||||||
- Options for a self-signed HTTPS certificate and passwordless sudo
|
|
||||||
|
|
||||||
### jupyter/minimal-notebook
|
### jupyter/minimal-notebook
|
||||||
|
|
||||||
@@ -191,7 +210,7 @@ The following diagram depicts the build dependency tree of the core images. (i.e
|
|||||||
Any given image inherits the complete content of all ancestor images pointing to it.
|
Any given image inherits the complete content of all ancestor images pointing to it.
|
||||||
|
|
||||||
[](http://interactive.blockdiag.com/?compression=deflate&src=eJyFzrEKwjAQxvG9T3FkskM3KUrRJ3DTUShJe9XQ9C4kKbWK7266CCmCW_jnd_Apw03fanmDVwbQYidHE-qOKXj9RDjAvsrihxjVSGG80uZ0OcOkwx0sawrg0KD0mAsojqDiqyAOqJj7Kp4lYRGDJj1Ik6B1W5xvtJ0TlZbFiIDk2XWGp2-PA5nMDI9dWZfbXPy-bGWQsSI1-HeJ-7PCzt5K1ydq3RYnjSnW8v0BwS-D-w)
|
diagram](../images/inherit.svg)](http://interactive.blockdiag.com/image?compression=deflate&encoding=base64&src=eJyFj0FqxDAMRfc5hchqsvCuDFNCe4Lu2mVhUBKlNXGkYMtkMqV3r70pOLRk-_T-56tz0k-DxQ_4qgAGGjE6vY7CGuyd4Ake2yod6thF1vjOp5e3V1itfsIilhU8OcJATQ3mGYbURd4ExX4KZpTIA6oVbnP1P7ec61KDYVHqRKYsFyAbs2U7oyukPcte6O2yFVZJslMrcRA_Oll_eXpM2G1wu5yv54em_juZFmOixD0dLvEHK5YtLOinwtqz7KFzZm9-_wDZDphP)
|
||||||
|
|
||||||
### Builds
|
### Builds
|
||||||
|
|
||||||
|
@@ -38,7 +38,7 @@ class ImageDescription:
|
|||||||
|
|
||||||
|
|
||||||
ALL_IMAGES = {
|
ALL_IMAGES = {
|
||||||
"base-notebook": ImageDescription(
|
"docker-stacks-foundation": ImageDescription(
|
||||||
parent_image=None,
|
parent_image=None,
|
||||||
taggers=[
|
taggers=[
|
||||||
SHATagger(),
|
SHATagger(),
|
||||||
@@ -46,11 +46,16 @@ ALL_IMAGES = {
|
|||||||
UbuntuVersionTagger(),
|
UbuntuVersionTagger(),
|
||||||
PythonMajorMinorVersionTagger(),
|
PythonMajorMinorVersionTagger(),
|
||||||
PythonVersionTagger(),
|
PythonVersionTagger(),
|
||||||
|
],
|
||||||
|
manifests=[CondaEnvironmentManifest(), AptPackagesManifest()],
|
||||||
|
),
|
||||||
|
"base-notebook": ImageDescription(
|
||||||
|
parent_image="docker-stacks-foundation",
|
||||||
|
taggers=[
|
||||||
JupyterNotebookVersionTagger(),
|
JupyterNotebookVersionTagger(),
|
||||||
JupyterLabVersionTagger(),
|
JupyterLabVersionTagger(),
|
||||||
JupyterHubVersionTagger(),
|
JupyterHubVersionTagger(),
|
||||||
],
|
],
|
||||||
manifests=[CondaEnvironmentManifest(), AptPackagesManifest()],
|
|
||||||
),
|
),
|
||||||
"minimal-notebook": ImageDescription(parent_image="base-notebook"),
|
"minimal-notebook": ImageDescription(parent_image="base-notebook"),
|
||||||
"scipy-notebook": ImageDescription(parent_image="minimal-notebook"),
|
"scipy-notebook": ImageDescription(parent_image="minimal-notebook"),
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
# Copyright (c) Jupyter Development Team.
|
# Copyright (c) Jupyter Development Team.
|
||||||
# Distributed under the terms of the Modified BSD License.
|
# Distributed under the terms of the Modified BSD License.
|
||||||
import logging
|
import logging
|
||||||
import pathlib
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import pytest # type: ignore
|
import pytest # type: ignore
|
||||||
@@ -30,6 +29,31 @@ def test_cli_args(container: TrackedContainer, http_client: requests.Session) ->
|
|||||||
assert "login_submit" not in resp.text
|
assert "login_submit" not in resp.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_nb_user_change(container: TrackedContainer) -> None:
|
||||||
|
"""Container should change the username (`NB_USER`) of the default user."""
|
||||||
|
nb_user = "nayvoj"
|
||||||
|
running_container = container.run_detached(
|
||||||
|
tty=True,
|
||||||
|
user="root",
|
||||||
|
environment=[f"NB_USER={nb_user}", "CHOWN_HOME=yes"],
|
||||||
|
command=["start.sh", "bash", "-c", "sleep infinity"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Give the chown time to complete.
|
||||||
|
# Use sleep, not wait, because the container sleeps forever.
|
||||||
|
time.sleep(1)
|
||||||
|
LOGGER.info(
|
||||||
|
f"Checking if home folder of {nb_user} contains the hidden '.jupyter' folder with appropriate permissions ..."
|
||||||
|
)
|
||||||
|
command = f'stat -c "%F %U %G" /home/{nb_user}/.jupyter'
|
||||||
|
expected_output = f"directory {nb_user} users"
|
||||||
|
cmd = running_container.exec_run(command, workdir=f"/home/{nb_user}")
|
||||||
|
output = cmd.output.decode("utf-8").strip("\n")
|
||||||
|
assert (
|
||||||
|
output == expected_output
|
||||||
|
), f"Hidden folder .jupyter was not copied properly to {nb_user} home folder. stat: {output}, expected {expected_output}"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.filterwarnings("ignore:Unverified HTTPS request")
|
@pytest.mark.filterwarnings("ignore:Unverified HTTPS request")
|
||||||
def test_unsigned_ssl(
|
def test_unsigned_ssl(
|
||||||
container: TrackedContainer, http_client: requests.Session
|
container: TrackedContainer, http_client: requests.Session
|
||||||
@@ -54,282 +78,3 @@ def test_unsigned_ssl(
|
|||||||
assert "ERROR" not in logs
|
assert "ERROR" not in logs
|
||||||
warnings = TrackedContainer.get_warnings(logs)
|
warnings = TrackedContainer.get_warnings(logs)
|
||||||
assert not warnings
|
assert not warnings
|
||||||
|
|
||||||
|
|
||||||
def test_uid_change(container: TrackedContainer) -> None:
|
|
||||||
"""Container should change the UID of the default user."""
|
|
||||||
logs = container.run_and_wait(
|
|
||||||
timeout=120, # usermod is slow so give it some time
|
|
||||||
tty=True,
|
|
||||||
user="root",
|
|
||||||
environment=["NB_UID=1010"],
|
|
||||||
command=["start.sh", "bash", "-c", "id && touch /opt/conda/test-file"],
|
|
||||||
)
|
|
||||||
assert "uid=1010(jovyan)" in logs
|
|
||||||
|
|
||||||
|
|
||||||
def test_gid_change(container: TrackedContainer) -> None:
|
|
||||||
"""Container should change the GID of the default user."""
|
|
||||||
logs = container.run_and_wait(
|
|
||||||
timeout=10,
|
|
||||||
tty=True,
|
|
||||||
user="root",
|
|
||||||
environment=["NB_GID=110"],
|
|
||||||
command=["start.sh", "id"],
|
|
||||||
)
|
|
||||||
assert "gid=110(jovyan)" in logs
|
|
||||||
assert "groups=110(jovyan),100(users)" in logs
|
|
||||||
|
|
||||||
|
|
||||||
def test_nb_user_change(container: TrackedContainer) -> None:
|
|
||||||
"""Container should change the username (`NB_USER`) of the default user."""
|
|
||||||
nb_user = "nayvoj"
|
|
||||||
running_container = container.run_detached(
|
|
||||||
tty=True,
|
|
||||||
user="root",
|
|
||||||
environment=[f"NB_USER={nb_user}", "CHOWN_HOME=yes"],
|
|
||||||
command=["start.sh", "bash", "-c", "sleep infinity"],
|
|
||||||
)
|
|
||||||
|
|
||||||
# Give the chown time to complete.
|
|
||||||
# Use sleep, not wait, because the container sleeps forever.
|
|
||||||
time.sleep(1)
|
|
||||||
LOGGER.info(f"Checking if the user is changed to {nb_user} by the start script ...")
|
|
||||||
output = running_container.logs().decode("utf-8")
|
|
||||||
assert "ERROR" not in output
|
|
||||||
assert "WARNING" not in output
|
|
||||||
assert (
|
|
||||||
f"username: jovyan -> {nb_user}" in output
|
|
||||||
), f"User is not changed to {nb_user}"
|
|
||||||
|
|
||||||
LOGGER.info(f"Checking {nb_user} id ...")
|
|
||||||
command = "id"
|
|
||||||
expected_output = f"uid=1000({nb_user}) gid=100(users) groups=100(users)"
|
|
||||||
cmd = running_container.exec_run(command, user=nb_user, workdir=f"/home/{nb_user}")
|
|
||||||
output = cmd.output.decode("utf-8").strip("\n")
|
|
||||||
assert output == expected_output, f"Bad user {output}, expected {expected_output}"
|
|
||||||
|
|
||||||
LOGGER.info(f"Checking if {nb_user} owns his home folder ...")
|
|
||||||
command = f'stat -c "%U %G" /home/{nb_user}/'
|
|
||||||
expected_output = f"{nb_user} users"
|
|
||||||
cmd = running_container.exec_run(command, workdir=f"/home/{nb_user}")
|
|
||||||
output = cmd.output.decode("utf-8").strip("\n")
|
|
||||||
assert (
|
|
||||||
output == expected_output
|
|
||||||
), f"Bad owner for the {nb_user} home folder {output}, expected {expected_output}"
|
|
||||||
|
|
||||||
LOGGER.info(
|
|
||||||
f"Checking if home folder of {nb_user} contains the hidden '.jupyter' folder with appropriate permissions ..."
|
|
||||||
)
|
|
||||||
command = f'stat -c "%F %U %G" /home/{nb_user}/.jupyter'
|
|
||||||
expected_output = f"directory {nb_user} users"
|
|
||||||
cmd = running_container.exec_run(command, workdir=f"/home/{nb_user}")
|
|
||||||
output = cmd.output.decode("utf-8").strip("\n")
|
|
||||||
assert (
|
|
||||||
output == expected_output
|
|
||||||
), f"Hidden folder .jupyter was not copied properly to {nb_user} home folder. stat: {output}, expected {expected_output}"
|
|
||||||
|
|
||||||
|
|
||||||
def test_chown_extra(container: TrackedContainer) -> None:
|
|
||||||
"""Container should change the UID/GID of a comma separated
|
|
||||||
CHOWN_EXTRA list of folders."""
|
|
||||||
logs = container.run_and_wait(
|
|
||||||
timeout=120, # chown is slow so give it some time
|
|
||||||
tty=True,
|
|
||||||
user="root",
|
|
||||||
environment=[
|
|
||||||
"NB_UID=1010",
|
|
||||||
"NB_GID=101",
|
|
||||||
"CHOWN_EXTRA=/home/jovyan,/opt/conda/bin",
|
|
||||||
"CHOWN_EXTRA_OPTS=-R",
|
|
||||||
],
|
|
||||||
command=[
|
|
||||||
"start.sh",
|
|
||||||
"bash",
|
|
||||||
"-c",
|
|
||||||
"stat -c '%n:%u:%g' /home/jovyan/.bashrc /opt/conda/bin/jupyter",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
assert "/home/jovyan/.bashrc:1010:101" in logs
|
|
||||||
assert "/opt/conda/bin/jupyter:1010:101" in logs
|
|
||||||
|
|
||||||
|
|
||||||
def test_chown_home(container: TrackedContainer) -> None:
|
|
||||||
"""Container should change the NB_USER home directory owner and
|
|
||||||
group to the current value of NB_UID and NB_GID."""
|
|
||||||
logs = container.run_and_wait(
|
|
||||||
timeout=120, # chown is slow so give it some time
|
|
||||||
tty=True,
|
|
||||||
user="root",
|
|
||||||
environment=[
|
|
||||||
"CHOWN_HOME=yes",
|
|
||||||
"CHOWN_HOME_OPTS=-R",
|
|
||||||
"NB_USER=kitten",
|
|
||||||
"NB_UID=1010",
|
|
||||||
"NB_GID=101",
|
|
||||||
],
|
|
||||||
command=["start.sh", "bash", "-c", "stat -c '%n:%u:%g' /home/kitten/.bashrc"],
|
|
||||||
)
|
|
||||||
assert "/home/kitten/.bashrc:1010:101" in logs
|
|
||||||
|
|
||||||
|
|
||||||
def test_sudo(container: TrackedContainer) -> None:
|
|
||||||
"""Container should grant passwordless sudo to the default user."""
|
|
||||||
logs = container.run_and_wait(
|
|
||||||
timeout=10,
|
|
||||||
tty=True,
|
|
||||||
user="root",
|
|
||||||
environment=["GRANT_SUDO=yes"],
|
|
||||||
command=["start.sh", "sudo", "id"],
|
|
||||||
)
|
|
||||||
assert "uid=0(root)" in logs
|
|
||||||
|
|
||||||
|
|
||||||
def test_sudo_path(container: TrackedContainer) -> None:
|
|
||||||
"""Container should include /opt/conda/bin in the sudo secure_path."""
|
|
||||||
logs = container.run_and_wait(
|
|
||||||
timeout=10,
|
|
||||||
tty=True,
|
|
||||||
user="root",
|
|
||||||
environment=["GRANT_SUDO=yes"],
|
|
||||||
command=["start.sh", "sudo", "which", "jupyter"],
|
|
||||||
)
|
|
||||||
assert logs.rstrip().endswith("/opt/conda/bin/jupyter")
|
|
||||||
|
|
||||||
|
|
||||||
def test_sudo_path_without_grant(container: TrackedContainer) -> None:
|
|
||||||
"""Container should include /opt/conda/bin in the sudo secure_path."""
|
|
||||||
logs = container.run_and_wait(
|
|
||||||
timeout=10,
|
|
||||||
tty=True,
|
|
||||||
user="root",
|
|
||||||
command=["start.sh", "which", "jupyter"],
|
|
||||||
)
|
|
||||||
assert logs.rstrip().endswith("/opt/conda/bin/jupyter")
|
|
||||||
|
|
||||||
|
|
||||||
def test_group_add(container: TrackedContainer) -> None:
|
|
||||||
"""Container should run with the specified uid, gid, and secondary
|
|
||||||
group. It won't be possible to modify /etc/passwd since gid is nonzero, so
|
|
||||||
additionally verify that setting gid=0 is suggested in a warning.
|
|
||||||
"""
|
|
||||||
logs = container.run_and_wait(
|
|
||||||
timeout=5,
|
|
||||||
no_warnings=False,
|
|
||||||
user="1010:1010",
|
|
||||||
group_add=["users"], # Ensures write access to /home/jovyan
|
|
||||||
command=["start.sh", "id"],
|
|
||||||
)
|
|
||||||
warnings = TrackedContainer.get_warnings(logs)
|
|
||||||
assert len(warnings) == 1
|
|
||||||
assert "Try setting gid=0" in warnings[0]
|
|
||||||
assert "uid=1010 gid=1010 groups=1010,100(users)" in logs
|
|
||||||
|
|
||||||
|
|
||||||
def test_set_uid(container: TrackedContainer) -> None:
|
|
||||||
"""Container should run with the specified uid and NB_USER.
|
|
||||||
The /home/jovyan directory will not be writable since it's owned by 1000:users.
|
|
||||||
Additionally verify that "--group-add=users" is suggested in a warning to restore
|
|
||||||
write access.
|
|
||||||
"""
|
|
||||||
logs = container.run_and_wait(
|
|
||||||
timeout=5,
|
|
||||||
no_warnings=False,
|
|
||||||
user="1010",
|
|
||||||
command=["start.sh", "id"],
|
|
||||||
)
|
|
||||||
assert "uid=1010(jovyan) gid=0(root)" in logs
|
|
||||||
warnings = TrackedContainer.get_warnings(logs)
|
|
||||||
assert len(warnings) == 1
|
|
||||||
assert "--group-add=users" in warnings[0]
|
|
||||||
|
|
||||||
|
|
||||||
def test_set_uid_and_nb_user(container: TrackedContainer) -> None:
|
|
||||||
"""Container should run with the specified uid and NB_USER."""
|
|
||||||
logs = container.run_and_wait(
|
|
||||||
timeout=5,
|
|
||||||
no_warnings=False,
|
|
||||||
user="1010",
|
|
||||||
environment=["NB_USER=kitten"],
|
|
||||||
group_add=["users"], # Ensures write access to /home/jovyan
|
|
||||||
command=["start.sh", "id"],
|
|
||||||
)
|
|
||||||
assert "uid=1010(kitten) gid=0(root)" in logs
|
|
||||||
warnings = TrackedContainer.get_warnings(logs)
|
|
||||||
assert len(warnings) == 1
|
|
||||||
assert "user is kitten but home is /home/jovyan" in warnings[0]
|
|
||||||
|
|
||||||
|
|
||||||
def test_container_not_delete_bind_mount(
|
|
||||||
container: TrackedContainer, tmp_path: pathlib.Path
|
|
||||||
) -> None:
|
|
||||||
"""Container should not delete host system files when using the (docker)
|
|
||||||
-v bind mount flag and mapping to /home/jovyan.
|
|
||||||
"""
|
|
||||||
d = tmp_path / "data"
|
|
||||||
d.mkdir()
|
|
||||||
p = d / "foo.txt"
|
|
||||||
p.write_text("some-content")
|
|
||||||
|
|
||||||
container.run_and_wait(
|
|
||||||
timeout=5,
|
|
||||||
tty=True,
|
|
||||||
user="root",
|
|
||||||
working_dir="/home/",
|
|
||||||
environment=[
|
|
||||||
"NB_USER=user",
|
|
||||||
"CHOWN_HOME=yes",
|
|
||||||
],
|
|
||||||
volumes={d: {"bind": "/home/jovyan/data", "mode": "rw"}},
|
|
||||||
command=["start.sh", "ls"],
|
|
||||||
)
|
|
||||||
assert p.read_text() == "some-content"
|
|
||||||
assert len(list(tmp_path.iterdir())) == 1
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("enable_root", [False, True])
|
|
||||||
def test_jupyter_env_vars_to_unset_as_root(
|
|
||||||
container: TrackedContainer, enable_root: bool
|
|
||||||
) -> None:
|
|
||||||
"""Environment variables names listed in JUPYTER_ENV_VARS_TO_UNSET
|
|
||||||
should be unset in the final environment."""
|
|
||||||
root_args = {"user": "root"} if enable_root else {}
|
|
||||||
logs = container.run_and_wait(
|
|
||||||
timeout=10,
|
|
||||||
tty=True,
|
|
||||||
environment=[
|
|
||||||
"JUPYTER_ENV_VARS_TO_UNSET=SECRET_ANIMAL,UNUSED_ENV,SECRET_FRUIT",
|
|
||||||
"FRUIT=bananas",
|
|
||||||
"SECRET_ANIMAL=cats",
|
|
||||||
"SECRET_FRUIT=mango",
|
|
||||||
],
|
|
||||||
command=[
|
|
||||||
"start.sh",
|
|
||||||
"bash",
|
|
||||||
"-c",
|
|
||||||
"echo I like $FRUIT and ${SECRET_FRUIT:-stuff}, and love ${SECRET_ANIMAL:-to keep secrets}!",
|
|
||||||
],
|
|
||||||
**root_args, # type: ignore
|
|
||||||
)
|
|
||||||
assert "I like bananas and stuff, and love to keep secrets!" in logs
|
|
||||||
|
|
||||||
|
|
||||||
def test_secure_path(container: TrackedContainer, tmp_path: pathlib.Path) -> None:
|
|
||||||
"""Make sure that the sudo command has conda's python (not system's) on path.
|
|
||||||
See <https://github.com/jupyter/docker-stacks/issues/1053>.
|
|
||||||
"""
|
|
||||||
d = tmp_path / "data"
|
|
||||||
d.mkdir()
|
|
||||||
p = d / "wrong_python.sh"
|
|
||||||
p.write_text('#!/bin/bash\necho "Wrong python executable invoked!"')
|
|
||||||
p.chmod(0o755)
|
|
||||||
|
|
||||||
logs = container.run_and_wait(
|
|
||||||
timeout=5,
|
|
||||||
tty=True,
|
|
||||||
user="root",
|
|
||||||
volumes={p: {"bind": "/usr/bin/python", "mode": "ro"}},
|
|
||||||
command=["start.sh", "python", "--version"],
|
|
||||||
)
|
|
||||||
assert "Wrong python" not in logs
|
|
||||||
assert "Python" in logs
|
|
||||||
|
14
tests/base-notebook/test_npm_package_manager.py
Normal file
14
tests/base-notebook/test_npm_package_manager.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Copyright (c) Jupyter Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from tests.conftest import TrackedContainer
|
||||||
|
from tests.package_helper import run_package_manager
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def test_npm_package_manager(container: TrackedContainer) -> None:
|
||||||
|
"""Test that npm is installed and runs."""
|
||||||
|
run_package_manager(container, "npm", "--version")
|
@@ -6,6 +6,7 @@ import logging
|
|||||||
import pytest # type: ignore
|
import pytest # type: ignore
|
||||||
|
|
||||||
from tests.conftest import TrackedContainer
|
from tests.conftest import TrackedContainer
|
||||||
|
from tests.package_helper import run_package_manager
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -16,19 +17,11 @@ LOGGER = logging.getLogger(__name__)
|
|||||||
("apt", "--version"),
|
("apt", "--version"),
|
||||||
("conda", "--version"),
|
("conda", "--version"),
|
||||||
("mamba", "--version"),
|
("mamba", "--version"),
|
||||||
("npm", "--version"),
|
|
||||||
("pip", "--version"),
|
("pip", "--version"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_package_manager(
|
def test_package_manager(
|
||||||
container: TrackedContainer, package_manager: str, version_arg: tuple[str, ...]
|
container: TrackedContainer, package_manager: str, version_arg: str
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the notebook start-notebook script"""
|
"""Test that package managers are installed and run."""
|
||||||
LOGGER.info(
|
run_package_manager(container, package_manager, version_arg)
|
||||||
f"Test that the package manager {package_manager} is working properly ..."
|
|
||||||
)
|
|
||||||
container.run_and_wait(
|
|
||||||
timeout=5,
|
|
||||||
tty=True,
|
|
||||||
command=["start.sh", "bash", "-c", f"{package_manager} {version_arg}"],
|
|
||||||
)
|
|
290
tests/docker-stacks-foundation/test_user_options.py
Normal file
290
tests/docker-stacks-foundation/test_user_options.py
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
# Copyright (c) Jupyter Development Team.
|
||||||
|
# Distributed under the terms of the Modified BSD License.
|
||||||
|
import logging
|
||||||
|
import pathlib
|
||||||
|
import time
|
||||||
|
|
||||||
|
import pytest # type: ignore
|
||||||
|
|
||||||
|
from tests.conftest import TrackedContainer
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def test_uid_change(container: TrackedContainer) -> None:
|
||||||
|
"""Container should change the UID of the default user."""
|
||||||
|
logs = container.run_and_wait(
|
||||||
|
timeout=120, # usermod is slow so give it some time
|
||||||
|
tty=True,
|
||||||
|
user="root",
|
||||||
|
environment=["NB_UID=1010"],
|
||||||
|
command=["start.sh", "bash", "-c", "id && touch /opt/conda/test-file"],
|
||||||
|
)
|
||||||
|
assert "uid=1010(jovyan)" in logs
|
||||||
|
|
||||||
|
|
||||||
|
def test_gid_change(container: TrackedContainer) -> None:
|
||||||
|
"""Container should change the GID of the default user."""
|
||||||
|
logs = container.run_and_wait(
|
||||||
|
timeout=10,
|
||||||
|
tty=True,
|
||||||
|
user="root",
|
||||||
|
environment=["NB_GID=110"],
|
||||||
|
command=["start.sh", "id"],
|
||||||
|
)
|
||||||
|
assert "gid=110(jovyan)" in logs
|
||||||
|
assert "groups=110(jovyan),100(users)" in logs
|
||||||
|
|
||||||
|
|
||||||
|
def test_nb_user_change(container: TrackedContainer) -> None:
|
||||||
|
"""Container should change the username (`NB_USER`) of the default user."""
|
||||||
|
nb_user = "nayvoj"
|
||||||
|
running_container = container.run_detached(
|
||||||
|
tty=True,
|
||||||
|
user="root",
|
||||||
|
environment=[f"NB_USER={nb_user}", "CHOWN_HOME=yes"],
|
||||||
|
command=["start.sh", "bash", "-c", "sleep infinity"],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Give the chown time to complete.
|
||||||
|
# Use sleep, not wait, because the container sleeps forever.
|
||||||
|
time.sleep(1)
|
||||||
|
LOGGER.info(f"Checking if the user is changed to {nb_user} by the start script ...")
|
||||||
|
output = running_container.logs().decode("utf-8")
|
||||||
|
assert "ERROR" not in output
|
||||||
|
assert "WARNING" not in output
|
||||||
|
assert (
|
||||||
|
f"username: jovyan -> {nb_user}" in output
|
||||||
|
), f"User is not changed to {nb_user}"
|
||||||
|
|
||||||
|
LOGGER.info(f"Checking {nb_user} id ...")
|
||||||
|
command = "id"
|
||||||
|
expected_output = f"uid=1000({nb_user}) gid=100(users) groups=100(users)"
|
||||||
|
cmd = running_container.exec_run(command, user=nb_user, workdir=f"/home/{nb_user}")
|
||||||
|
output = cmd.output.decode("utf-8").strip("\n")
|
||||||
|
assert output == expected_output, f"Bad user {output}, expected {expected_output}"
|
||||||
|
|
||||||
|
LOGGER.info(f"Checking if {nb_user} owns his home folder ...")
|
||||||
|
command = f'stat -c "%U %G" /home/{nb_user}/'
|
||||||
|
expected_output = f"{nb_user} users"
|
||||||
|
cmd = running_container.exec_run(command, workdir=f"/home/{nb_user}")
|
||||||
|
output = cmd.output.decode("utf-8").strip("\n")
|
||||||
|
assert (
|
||||||
|
output == expected_output
|
||||||
|
), f"Bad owner for the {nb_user} home folder {output}, expected {expected_output}"
|
||||||
|
|
||||||
|
LOGGER.info(
|
||||||
|
f"Checking if home folder of {nb_user} contains the 'work' folder with appropriate permissions ..."
|
||||||
|
)
|
||||||
|
command = f'stat -c "%F %U %G" /home/{nb_user}/work'
|
||||||
|
expected_output = f"directory {nb_user} users"
|
||||||
|
cmd = running_container.exec_run(command, workdir=f"/home/{nb_user}")
|
||||||
|
output = cmd.output.decode("utf-8").strip("\n")
|
||||||
|
assert (
|
||||||
|
output == expected_output
|
||||||
|
), f"Folder work was not copied properly to {nb_user} home folder. stat: {output}, expected {expected_output}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_chown_extra(container: TrackedContainer) -> None:
|
||||||
|
"""Container should change the UID/GID of a comma separated
|
||||||
|
CHOWN_EXTRA list of folders."""
|
||||||
|
logs = container.run_and_wait(
|
||||||
|
timeout=120, # chown is slow so give it some time
|
||||||
|
tty=True,
|
||||||
|
user="root",
|
||||||
|
environment=[
|
||||||
|
"NB_UID=1010",
|
||||||
|
"NB_GID=101",
|
||||||
|
"CHOWN_EXTRA=/home/jovyan,/opt/conda/bin",
|
||||||
|
"CHOWN_EXTRA_OPTS=-R",
|
||||||
|
],
|
||||||
|
command=[
|
||||||
|
"start.sh",
|
||||||
|
"bash",
|
||||||
|
"-c",
|
||||||
|
"stat -c '%n:%u:%g' /home/jovyan/.bashrc /opt/conda/bin/jupyter",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert "/home/jovyan/.bashrc:1010:101" in logs
|
||||||
|
assert "/opt/conda/bin/jupyter:1010:101" in logs
|
||||||
|
|
||||||
|
|
||||||
|
def test_chown_home(container: TrackedContainer) -> None:
|
||||||
|
"""Container should change the NB_USER home directory owner and
|
||||||
|
group to the current value of NB_UID and NB_GID."""
|
||||||
|
logs = container.run_and_wait(
|
||||||
|
timeout=120, # chown is slow so give it some time
|
||||||
|
tty=True,
|
||||||
|
user="root",
|
||||||
|
environment=[
|
||||||
|
"CHOWN_HOME=yes",
|
||||||
|
"CHOWN_HOME_OPTS=-R",
|
||||||
|
"NB_USER=kitten",
|
||||||
|
"NB_UID=1010",
|
||||||
|
"NB_GID=101",
|
||||||
|
],
|
||||||
|
command=["start.sh", "bash", "-c", "stat -c '%n:%u:%g' /home/kitten/.bashrc"],
|
||||||
|
)
|
||||||
|
assert "/home/kitten/.bashrc:1010:101" in logs
|
||||||
|
|
||||||
|
|
||||||
|
def test_sudo(container: TrackedContainer) -> None:
|
||||||
|
"""Container should grant passwordless sudo to the default user."""
|
||||||
|
logs = container.run_and_wait(
|
||||||
|
timeout=10,
|
||||||
|
tty=True,
|
||||||
|
user="root",
|
||||||
|
environment=["GRANT_SUDO=yes"],
|
||||||
|
command=["start.sh", "sudo", "id"],
|
||||||
|
)
|
||||||
|
assert "uid=0(root)" in logs
|
||||||
|
|
||||||
|
|
||||||
|
def test_sudo_path(container: TrackedContainer) -> None:
|
||||||
|
"""Container should include /opt/conda/bin in the sudo secure_path."""
|
||||||
|
logs = container.run_and_wait(
|
||||||
|
timeout=10,
|
||||||
|
tty=True,
|
||||||
|
user="root",
|
||||||
|
environment=["GRANT_SUDO=yes"],
|
||||||
|
command=["start.sh", "sudo", "which", "jupyter"],
|
||||||
|
)
|
||||||
|
assert logs.rstrip().endswith("/opt/conda/bin/jupyter")
|
||||||
|
|
||||||
|
|
||||||
|
def test_sudo_path_without_grant(container: TrackedContainer) -> None:
|
||||||
|
"""Container should include /opt/conda/bin in the sudo secure_path."""
|
||||||
|
logs = container.run_and_wait(
|
||||||
|
timeout=10,
|
||||||
|
tty=True,
|
||||||
|
user="root",
|
||||||
|
command=["start.sh", "which", "jupyter"],
|
||||||
|
)
|
||||||
|
assert logs.rstrip().endswith("/opt/conda/bin/jupyter")
|
||||||
|
|
||||||
|
|
||||||
|
def test_group_add(container: TrackedContainer) -> None:
|
||||||
|
"""Container should run with the specified uid, gid, and secondary
|
||||||
|
group. It won't be possible to modify /etc/passwd since gid is nonzero, so
|
||||||
|
additionally verify that setting gid=0 is suggested in a warning.
|
||||||
|
"""
|
||||||
|
logs = container.run_and_wait(
|
||||||
|
timeout=5,
|
||||||
|
no_warnings=False,
|
||||||
|
user="1010:1010",
|
||||||
|
group_add=["users"], # Ensures write access to /home/jovyan
|
||||||
|
command=["start.sh", "id"],
|
||||||
|
)
|
||||||
|
warnings = TrackedContainer.get_warnings(logs)
|
||||||
|
assert len(warnings) == 1
|
||||||
|
assert "Try setting gid=0" in warnings[0]
|
||||||
|
assert "uid=1010 gid=1010 groups=1010,100(users)" in logs
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_uid(container: TrackedContainer) -> None:
|
||||||
|
"""Container should run with the specified uid and NB_USER.
|
||||||
|
The /home/jovyan directory will not be writable since it's owned by 1000:users.
|
||||||
|
Additionally verify that "--group-add=users" is suggested in a warning to restore
|
||||||
|
write access.
|
||||||
|
"""
|
||||||
|
logs = container.run_and_wait(
|
||||||
|
timeout=5,
|
||||||
|
no_warnings=False,
|
||||||
|
user="1010",
|
||||||
|
command=["start.sh", "id"],
|
||||||
|
)
|
||||||
|
assert "uid=1010(jovyan) gid=0(root)" in logs
|
||||||
|
warnings = TrackedContainer.get_warnings(logs)
|
||||||
|
assert len(warnings) == 1
|
||||||
|
assert "--group-add=users" in warnings[0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_uid_and_nb_user(container: TrackedContainer) -> None:
|
||||||
|
"""Container should run with the specified uid and NB_USER."""
|
||||||
|
logs = container.run_and_wait(
|
||||||
|
timeout=5,
|
||||||
|
no_warnings=False,
|
||||||
|
user="1010",
|
||||||
|
environment=["NB_USER=kitten"],
|
||||||
|
group_add=["users"], # Ensures write access to /home/jovyan
|
||||||
|
command=["start.sh", "id"],
|
||||||
|
)
|
||||||
|
assert "uid=1010(kitten) gid=0(root)" in logs
|
||||||
|
warnings = TrackedContainer.get_warnings(logs)
|
||||||
|
assert len(warnings) == 1
|
||||||
|
assert "user is kitten but home is /home/jovyan" in warnings[0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_container_not_delete_bind_mount(
|
||||||
|
container: TrackedContainer, tmp_path: pathlib.Path
|
||||||
|
) -> None:
|
||||||
|
"""Container should not delete host system files when using the (docker)
|
||||||
|
-v bind mount flag and mapping to /home/jovyan.
|
||||||
|
"""
|
||||||
|
d = tmp_path / "data"
|
||||||
|
d.mkdir()
|
||||||
|
p = d / "foo.txt"
|
||||||
|
p.write_text("some-content")
|
||||||
|
|
||||||
|
container.run_and_wait(
|
||||||
|
timeout=5,
|
||||||
|
tty=True,
|
||||||
|
user="root",
|
||||||
|
working_dir="/home/",
|
||||||
|
environment=[
|
||||||
|
"NB_USER=user",
|
||||||
|
"CHOWN_HOME=yes",
|
||||||
|
],
|
||||||
|
volumes={d: {"bind": "/home/jovyan/data", "mode": "rw"}},
|
||||||
|
command=["start.sh", "ls"],
|
||||||
|
)
|
||||||
|
assert p.read_text() == "some-content"
|
||||||
|
assert len(list(tmp_path.iterdir())) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("enable_root", [False, True])
|
||||||
|
def test_jupyter_env_vars_to_unset_as_root(
|
||||||
|
container: TrackedContainer, enable_root: bool
|
||||||
|
) -> None:
|
||||||
|
"""Environment variables names listed in JUPYTER_ENV_VARS_TO_UNSET
|
||||||
|
should be unset in the final environment."""
|
||||||
|
root_args = {"user": "root"} if enable_root else {}
|
||||||
|
logs = container.run_and_wait(
|
||||||
|
timeout=10,
|
||||||
|
tty=True,
|
||||||
|
environment=[
|
||||||
|
"JUPYTER_ENV_VARS_TO_UNSET=SECRET_ANIMAL,UNUSED_ENV,SECRET_FRUIT",
|
||||||
|
"FRUIT=bananas",
|
||||||
|
"SECRET_ANIMAL=cats",
|
||||||
|
"SECRET_FRUIT=mango",
|
||||||
|
],
|
||||||
|
command=[
|
||||||
|
"start.sh",
|
||||||
|
"bash",
|
||||||
|
"-c",
|
||||||
|
"echo I like $FRUIT and ${SECRET_FRUIT:-stuff}, and love ${SECRET_ANIMAL:-to keep secrets}!",
|
||||||
|
],
|
||||||
|
**root_args, # type: ignore
|
||||||
|
)
|
||||||
|
assert "I like bananas and stuff, and love to keep secrets!" in logs
|
||||||
|
|
||||||
|
|
||||||
|
def test_secure_path(container: TrackedContainer, tmp_path: pathlib.Path) -> None:
|
||||||
|
"""Make sure that the sudo command has conda's python (not system's) on path.
|
||||||
|
See <https://github.com/jupyter/docker-stacks/issues/1053>.
|
||||||
|
"""
|
||||||
|
d = tmp_path / "data"
|
||||||
|
d.mkdir()
|
||||||
|
p = d / "wrong_python.sh"
|
||||||
|
p.write_text('#!/bin/bash\necho "Wrong python executable invoked!"')
|
||||||
|
p.chmod(0o755)
|
||||||
|
|
||||||
|
logs = container.run_and_wait(
|
||||||
|
timeout=5,
|
||||||
|
tty=True,
|
||||||
|
user="root",
|
||||||
|
volumes={p: {"bind": "/usr/bin/python", "mode": "ro"}},
|
||||||
|
command=["start.sh", "python", "--version"],
|
||||||
|
)
|
||||||
|
assert "Wrong python" not in logs
|
||||||
|
assert "Python" in logs
|
@@ -8,7 +8,8 @@ THIS_DIR = Path(__file__).parent.resolve()
|
|||||||
# Please, take a look at the hierarchy of the images here:
|
# Please, take a look at the hierarchy of the images here:
|
||||||
# https://jupyter-docker-stacks.readthedocs.io/en/latest/using/selecting.html#image-relationships
|
# https://jupyter-docker-stacks.readthedocs.io/en/latest/using/selecting.html#image-relationships
|
||||||
ALL_IMAGES = {
|
ALL_IMAGES = {
|
||||||
"base-notebook": None,
|
"docker-stacks-foundation": None,
|
||||||
|
"base-notebook": "docker-stacks-foundation",
|
||||||
"minimal-notebook": "base-notebook",
|
"minimal-notebook": "base-notebook",
|
||||||
"scipy-notebook": "minimal-notebook",
|
"scipy-notebook": "minimal-notebook",
|
||||||
"r-notebook": "minimal-notebook",
|
"r-notebook": "minimal-notebook",
|
||||||
|
@@ -37,6 +37,21 @@ from tests.conftest import TrackedContainer
|
|||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def run_package_manager(
|
||||||
|
container: TrackedContainer, package_manager: str, version_arg: str
|
||||||
|
) -> None:
|
||||||
|
"""Runs the given package manager with its version argument."""
|
||||||
|
|
||||||
|
LOGGER.info(
|
||||||
|
f"Test that the package manager {package_manager} is working properly ..."
|
||||||
|
)
|
||||||
|
container.run_and_wait(
|
||||||
|
timeout=5,
|
||||||
|
tty=True,
|
||||||
|
command=["start.sh", "bash", "-c", f"{package_manager} {version_arg}"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CondaPackageHelper:
|
class CondaPackageHelper:
|
||||||
"""Conda package helper permitting to get information about packages"""
|
"""Conda package helper permitting to get information about packages"""
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user