diff --git a/.github/actions/build-test-scan-push/action.yaml b/.github/actions/build-test-scan-push/action.yaml index 333344b4..55d64603 100644 --- a/.github/actions/build-test-scan-push/action.yaml +++ b/.github/actions/build-test-scan-push/action.yaml @@ -45,6 +45,10 @@ inputs: description: Username for authentication with DockerHub required: true type: string + gcp-json: + description: JSON for authenticating Google Cloud Platform + default: "" + type: string runs: using: "composite" @@ -71,6 +75,19 @@ runs: username: ${{ inputs.dockerhub-username }} password: ${{ inputs.dockerhub-token }} + - name: Authenticate to Google Cloud + continue-on-error: true + uses: google-github-actions/auth@v1 + with: + credentials_json: '${{ inputs.gcp-json }}' + + - name: Authenticate GCAR + shell: bash + run: | + if [ ! -z "${{ inputs.gcp-json }}" ]; then + gcloud auth configure-docker -q us-central1-docker.pkg.dev + fi + - name: Build id: image-build uses: docker/build-push-action@v4 diff --git a/.github/workflows/build-release.yaml b/.github/workflows/build-release.yaml index 205c4f72..13295850 100644 --- a/.github/workflows/build-release.yaml +++ b/.github/workflows/build-release.yaml @@ -318,3 +318,86 @@ jobs: dockerhub-token: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} # End retry logic + + build-workbench-for-google-cloud-workstations: + needs: [ build-base, build-base-pro ] + runs-on: ubuntu-latest + name: build-workbench-for-google-cloud-workstations + + concurrency: + group: build-products-${{ matrix.config.product }}-${{ matrix.config.os }}-${{ github.ref }} + cancel-in-progress: true + + steps: + - name: Check Out Repo + uses: actions/checkout@v3 + + - name: Set up Just + uses: extractions/setup-just@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Get Version + id: get-version + run: | + VERSION=$(just workbench-for-google-cloud-workstations/get-version) + echo "VERSION=$VERSION" >> $GITHUB_OUTPUT + + - name: Get build args + id: get-build-args + run: | + EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) + BUILD_ARGS=$(just workbench-for-google-cloud-workstations/get-build-args) + echo "BUILD_ARGS<<$EOF" >> $GITHUB_OUTPUT + echo "$BUILD_ARGS" >> $GITHUB_OUTPUT + echo "$EOF" >> $GITHUB_OUTPUT + + - name: Get tags + id: get-tags + run: | + IMAGE_TAGS=$(just workbench-for-google-cloud-workstations/get-build-tags) + echo "IMAGE_TAGS=$IMAGE_TAGS" >> $GITHUB_OUTPUT + + - name: Build/Test/Scan/Push base pro image + id: build1 + uses: ./.github/actions/build-test-scan-push + continue-on-error: true + with: + context: ./workbench-for-google-cloud-workstations + os: ubuntu2004 + product: workbench-for-google-cloud-workstations + image-tags: ${{ steps.get-tags.outputs.IMAGE_TAGS }} + build-args: ${{ steps.get-build-args.outputs.BUILD_ARGS }} + push-image: ${{ github.ref == 'refs/heads/main' }} + snyk-token: ${{ secrets.SNYK_TOKEN }} + snyk-org-id: ${{ secrets.SNYK_ORG_ID }} + ghcr-token: ${{ secrets.BUILD_PAT }} + dockerhub-username: ${{ secrets.DOCKER_HUB_USERNAME }} + dockerhub-token: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + gcp-json: '${{ secrets.GCP_ARTIFACT_REGISTRY_JSON }}' + + # Begin retry logic + + - name: Wait 60s on failure before retrying + if: steps.build1.outcome == 'failure' + run: sleep 60 + + - name: Retry - Build/Test/Scan/Push base pro image + id: build2 + if: steps.build1.outcome == 'failure' + uses: ./.github/actions/build-test-scan-push + with: + context: ./workbench-for-google-cloud-workstations + os: ubuntu2004 + product: workbench-for-google-cloud-workstations + image-tags: ${{ steps.get-tags.outputs.IMAGE_TAGS }} + build-args: ${{ steps.get-build-args.outputs.BUILD_ARGS }} + push-image: ${{ github.ref == 'refs/heads/main' }} + snyk-token: ${{ secrets.SNYK_TOKEN }} + snyk-org-id: ${{ secrets.SNYK_ORG_ID }} + ghcr-token: ${{ secrets.BUILD_PAT }} + dockerhub-username: ${{ secrets.DOCKER_HUB_USERNAME }} + dockerhub-token: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + gcp-json: '${{ secrets.GCP_ARTIFACT_REGISTRY_JSON }}' + + # End retry logic diff --git a/workbench-for-google-cloud-workstations/.env b/workbench-for-google-cloud-workstations/.env new file mode 100644 index 00000000..1a28dfba --- /dev/null +++ b/workbench-for-google-cloud-workstations/.env @@ -0,0 +1,12 @@ +RSW_VERSION=2023.03.2+454.pro2 +RSW_TAG_VERSION=2023.03.2-454.pro2 +RSW_DOWNLOAD_URL=https://download2.rstudio.org/server/bionic/amd64 +RSW_NAME=rstudio-workbench +PYTHON_VERSION=3.10.12 +PYTHON_VERSION_ALT=3.9.17 +PYTHON_VERSION_JUPYTER=3.10.12 +R_VERSION=4.2.3 +R_VERSION_ALT=4.1.3 +DRIVERS_VERSION=2023.05.0 +QUARTO_VERSION=1.3.340 +IMAGE_REGISTRY_NAME=us-central1-docker.pkg.dev/posit-images/cloud-workstations/workbench diff --git a/workbench-for-google-cloud-workstations/.gitignore b/workbench-for-google-cloud-workstations/.gitignore new file mode 100644 index 00000000..1bdfee60 --- /dev/null +++ b/workbench-for-google-cloud-workstations/.gitignore @@ -0,0 +1,2 @@ +conf/launcher.pem +conf/launcher.pub diff --git a/workbench-for-google-cloud-workstations/Dockerfile.ubuntu2004 b/workbench-for-google-cloud-workstations/Dockerfile.ubuntu2004 new file mode 100644 index 00000000..23c8c64c --- /dev/null +++ b/workbench-for-google-cloud-workstations/Dockerfile.ubuntu2004 @@ -0,0 +1,175 @@ +FROM us-central1-docker.pkg.dev/cloud-workstations-images/predefined/base:public-image-current + +### ARG declarations ### +ARG DEBIAN_FRONTEND=noninteractive +ARG R_VERSION=4.2.3 +ARG R_VERSION_ALT=4.1.3 +ARG PYTHON_VERSION=3.10.12 +ARG PYTHON_VERSION_ALT=3.9.17 +ARG PYTHON_VERSION_JUPYTER=3.10.12 +ARG QUARTO_VERSION=1.3.340 +ARG DRIVERS_VERSION=2023.05.0 +ARG RSW_VERSION=2023.03.2+454.pro2 +ARG RSW_NAME=rstudio-workbench +ARG RSW_DOWNLOAD_URL=https://download2.rstudio.org/server/bionic/amd64 + +ENV RSW_LICENSE "" +ENV RSW_LICENSE_SERVER "" +ENV RSW_TESTUSER user +ENV RSW_TESTUSER_PASSWD rstudio +ENV RSW_TESTUSER_UID 10000 +ENV RSW_LAUNCHER true +ENV RSW_LAUNCHER_TIMEOUT 10 +ENV DIAGNOSTIC_DIR /var/log/rstudio +ENV DIAGNOSTIC_ENABLE false +ENV DIAGNOSTIC_ONLY false +ENV LICENSE_MANAGER_PATH /opt/rstudio-license +ENV WORKBENCH_JUPYTER_PATH=/usr/local/bin/jupyter + +### Copy package lists and install scripts ### +COPY deps/* / + +### Update/upgrade system packages ### +RUN apt-get update --fix-missing \ + && apt-get upgrade -yq \ + && xargs -a /apt_packages.txt apt-get install -yq --no-install-recommends \ + && rm /apt_packages.txt \ + && rm -rf /var/lib/apt/lists/* + +### Install R versions ### +RUN curl -O https://cdn.rstudio.com/r/ubuntu-2004/pkgs/r-${R_VERSION}_1_amd64.deb \ + && curl -O https://cdn.rstudio.com/r/ubuntu-2004/pkgs/r-${R_VERSION_ALT}_1_amd64.deb \ + && apt-get update \ + && apt-get install -yq --no-install-recommends ./r-${R_VERSION}_1_amd64.deb \ + && apt-get install -yq --no-install-recommends ./r-${R_VERSION_ALT}_1_amd64.deb \ + && rm -f ./r-${R_VERSION}_1_amd64.deb \ + && rm -f ./r-${R_VERSION_ALT}_1_amd64.deb \ + && ln -s /opt/R/${R_VERSION}/bin/R /usr/local/bin/R \ + && ln -s /opt/R/${R_VERSION}/bin/Rscript /usr/local/bin/Rscript \ + && rm -rf /var/lib/apt/lists/* + +### Install Python versions ### +RUN curl -O https://cdn.rstudio.com/python/ubuntu-2004/pkgs/python-${PYTHON_VERSION}_1_amd64.deb \ + && curl -O https://cdn.rstudio.com/python/ubuntu-2004/pkgs/python-${PYTHON_VERSION_ALT}_1_amd64.deb \ + && apt-get update \ + && apt-get install -yq --no-install-recommends ./python-${PYTHON_VERSION}_1_amd64.deb \ + && apt-get install -yq --no-install-recommends ./python-${PYTHON_VERSION_ALT}_1_amd64.deb \ + && rm -rf python-${PYTHON_VERSION}_1_amd64.deb \ + && rm -rf python-${PYTHON_VERSION_ALT}_1_amd64.deb \ + && /opt/python/${PYTHON_VERSION}/bin/python3 -m pip install 'virtualenv<20' \ + && /opt/python/${PYTHON_VERSION}/bin/python3 -m pip install --upgrade setuptools \ + && /opt/python/${PYTHON_VERSION}/bin/python3 -m pip install --upgrade pip \ + && /opt/python/${PYTHON_VERSION}/bin/python3 -m pip cache purge \ + && /opt/python/${PYTHON_VERSION_ALT}/bin/python3 -m pip install 'virtualenv<20' \ + && /opt/python/${PYTHON_VERSION_ALT}/bin/python3 -m pip install --upgrade setuptools \ + && /opt/python/${PYTHON_VERSION_ALT}/bin/python3 -m pip install --upgrade pip \ + && /opt/python/${PYTHON_VERSION_ALT}/bin/python3 -m pip cache purge \ + && rm -rf /var/lib/apt/lists/* + +### Install basic data science packages for Python and R ### +RUN /opt/python/${PYTHON_VERSION}/bin/python3 -m pip install -r /py_packages.txt \ + && /opt/python/${PYTHON_VERSION}/bin/python3 -m pip cache purge \ + && /opt/python/${PYTHON_VERSION_ALT}/bin/python3 -m pip install -r /py_packages.txt \ + && /opt/python/${PYTHON_VERSION_ALT}/bin/python3 -m pip cache purge \ + && ./install_r_packages.sh \ + && rm install_r_packages.sh py_packages.txt r_packages.txt + +### Locale configuration ### +RUN localedef -i en_US -f UTF-8 en_US.UTF-8 +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +### Install Quarto ### +RUN curl -o quarto-linux-amd64.deb -L https://github.com/quarto-dev/quarto-cli/releases/download/v${QUARTO_VERSION}/quarto-${QUARTO_VERSION}-linux-amd64.deb \ + && apt-get update \ + && apt-get install -yq ./quarto-linux-amd64.deb \ + && rm -rf /var/lib/apt/lists/* \ + && rm quarto-linux-amd64.deb + +### Install Pro Drivers ### +RUN apt-get update \ + && apt-get install -yq --no-install-recommends unixodbc unixodbc-dev \ + && curl -O https://cdn.rstudio.com/drivers/7C152C12/installer/rstudio-drivers_${DRIVERS_VERSION}_amd64.deb \ + && apt-get update \ + && apt-get install -yq --no-install-recommends ./rstudio-drivers_${DRIVERS_VERSION}_amd64.deb \ + && rm -f ./rstudio-drivers_${DRIVERS_VERSION}_amd64.deb \ + && rm -rf /var/lib/apt/lists/* \ + && cp /opt/rstudio-drivers/odbcinst.ini.sample /etc/odbcinst.ini \ + && /opt/R/${R_VERSION}/bin/R -e 'install.packages("odbc", repos="https://packagemanager.rstudio.com/cran/__linux__/focal/latest")' + +### Install Workbench ### +SHELL ["/bin/bash", "-o", "pipefail", "-c"] +RUN curl -o rstudio-workbench.deb "${RSW_DOWNLOAD_URL}/${RSW_NAME}-${RSW_VERSION//+/-}-amd64.deb" \ + && gpg --keyserver keyserver.ubuntu.com --recv-keys 3F32EE77E331692F \ + && dpkg-sig --verify ./rstudio-workbench.deb \ + && apt-get update \ + && apt-get install -y --no-install-recommends ./rstudio-workbench.deb \ + && rm ./rstudio-workbench.deb \ + && apt-get autoremove -y \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /var/lib/rstudio-server/r-versions + +### Install GCW License Manager ### +# TODO(ianpittwood): Replace monitor download with $RSW_VERSION after upgrading to 2023.06.0 +RUN mkdir -p /opt/rstudio-license/ \ + && mkdir -p /var/lib/rstudio-workbench \ + && curl -sL "https://s3.amazonaws.com/rstudio-ide-build/monitor/focal/rsp-monitor-workbench-gcpw-amd64-2023.06.0-419.pro1.tar.gz" | \ + tar xzvf - --strip 2 -C /opt/rstudio-license/ \ + && chmod 0755 /opt/rstudio-license/license-manager \ + && mv /opt/rstudio-license/license-manager /opt/rstudio-license/license-manager-orig \ + && rm -f /usr/lib/rstudio-server/bin/license-manager + +### Install Jupyter and extensions ### +RUN /opt/python/"${PYTHON_VERSION_JUPYTER}"/bin/python -m venv /opt/python/jupyter \ + && /opt/python/jupyter/bin/pip install \ + jupyter \ + jupyterlab \ + rsconnect_jupyter \ + rsconnect_python \ + rsp_jupyter \ + workbench_jupyterlab \ + && ln -s /opt/python/jupyter/bin/jupyter /usr/local/bin/jupyter \ + && /opt/python/jupyter/bin/jupyter-nbextension install --sys-prefix --py rsp_jupyter \ + && /opt/python/jupyter/bin/jupyter-nbextension enable --sys-prefix --py rsp_jupyter \ + && /opt/python/jupyter/bin/jupyter-nbextension install --sys-prefix --py rsconnect_jupyter \ + && /opt/python/jupyter/bin/jupyter-nbextension enable --sys-prefix --py rsconnect_jupyter \ + && /opt/python/jupyter/bin/jupyter-serverextension enable --sys-prefix --py rsconnect_jupyter \ + && /opt/python/jupyter/bin/python -m ipykernel install --name py${PYTHON_VERSION} --display-name "Python ${PYTHON_VERSION}" \ + && /opt/python/jupyter/bin/python -m ipykernel install --name py${PYTHON_VERSION_ALT} --display-name "Python ${PYTHON_VERSION_ALT}" \ + && /opt/python/${PYTHON_VERSION}/bin/python3 -m pip cache purge \ + && /opt/python/${PYTHON_VERSION_ALT}/bin/python3 -m pip cache purge + +RUN curl -L -o /usr/local/bin/wait-for-it.sh https://raw.githubusercontent.com/rstudio/wait-for-it/master/wait-for-it.sh \ + && chmod +x /usr/local/bin/wait-for-it.sh + +RUN mkdir -p /var/lib/rstudio-server/monitor/log \ + && chown -R rstudio-server:rstudio-server /var/lib/rstudio-server/monitor \ + && mkdir -p /startup/custom/ \ + && printf '\n# allow home directory creation\nsession required pam_mkhomedir.so skel=/etc/skel umask=0022' >> /etc/pam.d/common-session + +COPY --chmod=755 TurboActivate.dat /opt/rstudio-license/license-manager.conf +COPY --chmod=755 license-manager-shim /opt/rstudio-license/license-manager +COPY --chmod=0775 startup.sh /usr/local/bin/startup.sh +COPY startup-launcher/* /startup/launcher/ +COPY startup-user-provisioning/* /startup/user-provisioning/ +COPY startup/* /startup/base/ +COPY supervisord.conf /etc/supervisor/supervisord.conf +COPY --chmod=600 sssd.conf /etc/sssd/sssd.conf +COPY conf/* /etc/rstudio/ +COPY --chmod=600 conf/launcher.p* /etc/rstudio + +# GCW specific +COPY --chmod=755 workstation-startup/* /etc/workstation-startup.d/ +COPY --chmod=644 jupyter/jupyter_notebook_config.json /opt/python/jupyter/etc/jupyter/jupyter_notebook_config.json + +### Clean up ### +RUN apt-get remove -yq dpkg-sig \ + && apt-get install -yqf --no-install-recommends \ + && apt-get autoremove -yq \ + && apt-get clean -yq \ + && rm -rf /var/lib/apt/lists/* + +EXPOSE 80/tcp +EXPOSE 5559/tcp diff --git a/workbench-for-google-cloud-workstations/Justfile b/workbench-for-google-cloud-workstations/Justfile new file mode 100644 index 00000000..060f4f47 --- /dev/null +++ b/workbench-for-google-cloud-workstations/Justfile @@ -0,0 +1,109 @@ +set dotenv-load +set positional-arguments + +BUILDX_PATH := "" + +PRODUCT := "workbench" +IMAGE_OS := "ubuntu2004" + +RSW_LICENSE := "" +RSW_LICENSE_SERVER := "" + +_make-default-tag: + echo "${IMAGE_REGISTRY_NAME}:$(just -f ../Justfile _get-tag-safe-version "${RSW_VERSION}")" + +get-version: + echo "${RSW_VERSION}" + +get-build-args: + #!/usr/bin/env bash + printf "RSW_VERSION=${RSW_VERSION} + R_VERSION=${R_VERSION} + R_VERSION_ALT=${R_VERSION_ALT} + PYTHON_VERSION=${PYTHON_VERSION} + PYTHON_VERSION_ALT=${PYTHON_VERSION_ALT} + PYTHON_VERSION_JUPYTER=${PYTHON_VERSION_JUPYTER} + QUARTO_VERSION=${QUARTO_VERSION} + DRIVERS_VERSION=${DRIVERS_VERSION} + SRC_IMAGE_NAME=${SRC_IMAGE_NAME} + RSW_DOWNLOAD_URL=${RSW_DOWNLOAD_URL}" + +get-build-tags: + #!/usr/bin/env bash + echo ${IMAGE_REGISTRY_NAME}:${RSW_TAG_VERSION},\ + ${IMAGE_REGISTRY_NAME}:latest + +# Build Workbench image - just build 2022.07.2+576.pro12 rstudio/rstudio-workbench-gcw:2022.07.2 +build *TAGS="": + #!/usr/bin/env bash + set -euxo pipefail + CACHE_PATH=${GITHUB_WORKSPACE:-/tmp} + BUILDX_ARGS="" + if [[ "{{BUILDX_PATH}}" != "" ]]; then + BUILDX_ARGS="--cache-from=type=local,src=${CACHE_PATH}/.buildx-cache --cache-to=type=local,dest=${CACHE_PATH}/.buildx-cache-new,mode=max" + fi + + if [[ "{{TAGS}}" == "" ]]; then + raw_tag_array=($(just _make-default-tag)) + else + raw_tag_array=("{{TAGS}}") + fi + + tag_array=() + for tag in ${raw_tag_array[@]}; + do + tag_array+=("-t" $tag) + done + + docker buildx --builder="{{ BUILDX_PATH }}" build --load ${BUILDX_ARGS} \ + ${tag_array[@]} \ + --build-arg RSW_VERSION="${RSW_VERSION}" \ + --build-arg R_VERSION="${R_VERSION}" \ + --build-arg R_VERSION_ALT="${R_VERSION_ALT}" \ + --build-arg PYTHON_VERSION="${PYTHON_VERSION}" \ + --build-arg PYTHON_VERSION_ALT="${PYTHON_VERSION_ALT}" \ + --build-arg PYTHON_VERSION_JUPYTER="${PYTHON_VERSION_JUPYTER}" \ + --build-arg QUARTO_VERSION="${QUARTO_VERSION}" \ + --build-arg DRIVERS_VERSION="${DRIVERS_VERSION}" \ + --build-arg RSW_DOWNLOAD_URL="${RSW_DOWNLOAD_URL}" \ + --file=./Dockerfile.ubuntu2004 . + + echo ${raw_tag_array[@]} + +# Test Workbench image - just test rstudio/rstudio-workbench:ubuntu1804-2022.07.2-576.pro12 2022.07.2+576.pro12 +test TAG=`just _make-default-tag` CMD="": + #!/usr/bin/env bash + set -euxo pipefail + RSW_VERSION_CLEAN=$(sed "s/daily-/daily+/" <<<"${RSW_VERSION}") + IMAGE_NAME="{{ TAG }}" \ + RSW_VERSION="${RSW_VERSION_CLEAN}" \ + RSW_LICENSE="{{ RSW_LICENSE }}" \ + RSW_LICENSE_SERVER="{{ RSW_LICENSE_SERVER }}" \ + DRIVERS_VERSION="${DRIVERS_VERSION}" \ + R_VERSION="${R_VERSION}" \ + R_VERSION_ALT="${R_VERSION_ALT}" \ + PYTHON_VERSION="${PYTHON_VERSION}" \ + PYTHON_VERSION_ALT="${PYTHON_VERSION_ALT}" \ + PYTHON_VERSION_JUPYTER="${PYTHON_VERSION_JUPYTER}" \ + QUARTO_VERSION="${QUARTO_VERSION}" \ + docker-compose -f ./docker-compose.test.yml run sut {{ CMD }} + +# Test Workbench image interactively - just test-i rstudio/rstudio-workbench:ubuntu1804-2022.07.2-576.pro12 2022.07.2+576.pro12 +test-i TAG=`just _make-default-tag`: + just test {{ TAG }} bash + +# Run Workbench - just RSW_LICENSE="" run rstudio/r-session-complete:ubuntu1804-2022.07.2-576.pro12 +run TAG=`just _make-default-tag` CMD="": + #!/usr/bin/env bash + set -euo pipefail + if [ -z "{{ RSW_LICENSE }}" ] && [ -z "{{ RSW_LICENSE_SERVER }}" ]; then + echo "Please set RSW_LICENSE or RSW_LICENSE_SERVER before running." + exit 1 + fi + + docker run -it --privileged \ + ${volume_opts[@]} \ + -p 8787:80 \ + -e RSW_LICENSE="{{ RSW_LICENSE }}" \ + -e RSW_LICENSE_SERVER="{{ RSW_LICENSE_SERVER }}" \ + "{{ TAG }}" {{ CMD }} diff --git a/workbench-for-google-cloud-workstations/README.md b/workbench-for-google-cloud-workstations/README.md new file mode 100644 index 00000000..55eb644a --- /dev/null +++ b/workbench-for-google-cloud-workstations/README.md @@ -0,0 +1,89 @@ +_# Quick reference + +* Maintained by: [the Posit Docker team](https://github.com/rstudio/rstudio-docker-products) +* Where to get help: [our Github Issues page](https://github.com/rstudio/rstudio-docker-products/issues) +* RStudio Workbench image: [Docker Hub](https://hub.docker.com/r/rstudio/rstudio-workbench) +* RStudio r-session-complete image: [Docker Hub](https://hub.docker.com/r/rstudio/r-session-complete) +* Registry for this image: [Posit's Google Cloud Artifact Registry](https://console.cloud.google.com/artifacts/docker/posit-images/us-central1/cloud-workstations/workbench) + +# What is RStudio Workbench? + +Posit Workbench, formerly RStudio Workbench, is the preferred data analysis and integrated development experience for +professional R users and data science teams who use R and Python. Posit Workbench enables the collaboration, +centralized management, metrics, security, and commercial support that professional data science teams need to operate +at scale. + +Some of the functionality that Workbench provides is: + +* The ability to develop in Workbench and Jupyter +* Load balancing +* Tutorial API +* Data connectivity and Posit Professional Drivers (formerly RStudio Professional Drivers) +* Collaboration and project sharing +* Scale with Kubernetes and SLURM +* Authentication, access, & security +* Run multiple concurrent R and Python sessions +* Remote execution with Launcher +* Auditing and monitoring +* Advanced R and Python session management + +For more information on running RStudio Workbench in your organization please visit +https://www.rstudio.com/products/workbench/. + +# Notice for support + +1. This image is still in early development. Some bugs may be present, and we may introduce **BREAKING** changes in + order to improve your user experience; as such we recommend: + - Always read through the [NEWS](./NEWS.md) to understand the changes before updating or when encountering a bug. + - Use the `latest` or "version" tags. Avoid using `daily` tagged images unless advised to by Posit staff. +1. Outdated images will be removed periodically from GCAR as product version updates are made. Please make plans to + update at times, use the `latest` or version tag, or use your own build of the images. + +# How to use this image + +This image is designed for exclusive use with [Google Cloud Workstations](https://cloud.google.com/workstations). For +the generalized version of the Workbench image, go [here](../workbench). + +Using Google Cloud Workstations requires a Google Cloud Platform account. Click +[here](https://console.cloud.google.com/workstations/overview) to navigate to the Cloud Workstations console. Posit +Workbench is provided in the default list of "Code editors on base images" when creating a workstation configuration. +Alternatively, administrators can use the "Custom container image" option to reference a specific tag from +[Posit's public Google Cloud Artifact Registry](https://console.cloud.google.com/artifacts/docker/posit-images/us-central1/cloud-workstations/workbench). + +For more information, please see +[Cloud Workstation's official documentation](https://cloud.google.com/workstations/docs/develop-code-using-posit-workbench-rstudio). + +## Overview + +Note that running the RStudio Workbench Docker image requires a valid RStudio Workbench license for Google Cloud +Workstations. Licenses can be obtained by emailing sales@posit.co. + +This container includes: + +1. Two versions of R +2. Two versions of Python +3. RStudio Workbench + +### Licensing + +The RStudio Workbench Docker image requires a valid license, which can be set using the `RSW_LICENSE` environment +variable to a valid license key inside the container. + +### Environment variables + +| Variable | Description | Default | +|-----|---|-----------| +| `RSW_LICENSE` | License key for RStudio Workbench, format should be: `XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX` | None | +| `RSW_LAUNCHER` | Whether or not to use launcher locally / start the launcher process | true | +| `RSW_LAUNCHER_TIMEOUT` | The timeout, in seconds, to wait for launcher to start listening on the expected port before failing startup | 10 | + +# Licensing + +The license associated with the RStudio Docker Products repository is located [in LICENSE.md](https://github.com/rstudio/rstudio-docker-products/blob/main/LICENSE.md). + +As is the case with all container images, the images themselves also contain other software which may be under other +licenses (i.e. bash, linux, system libraries, etc., along with any other direct or indirect dependencies of the primary +software being contained). + +It is an image user's responsibility to ensure that use of this image (and any of its dependent layers) complies with +all relevant licenses for the software contained in the image._ diff --git a/workbench-for-google-cloud-workstations/TurboActivate.dat b/workbench-for-google-cloud-workstations/TurboActivate.dat new file mode 100644 index 00000000..d1b139fd Binary files /dev/null and b/workbench-for-google-cloud-workstations/TurboActivate.dat differ diff --git a/workbench-for-google-cloud-workstations/conf/jupyter.conf b/workbench-for-google-cloud-workstations/conf/jupyter.conf new file mode 100644 index 00000000..c38488e4 --- /dev/null +++ b/workbench-for-google-cloud-workstations/conf/jupyter.conf @@ -0,0 +1,7 @@ +notebooks-enabled=1 +labs-enabled=1 + +jupyter-exe=/usr/local/bin/jupyter +lab-args=--no-browser --allow-root --ip=0.0.0.0 --ServerApp.allow_origin="*" --ServerApp.allow_remote_access=True --LabApp.token="" --debug + +default-session-cluster=Local diff --git a/workbench-for-google-cloud-workstations/conf/launcher-env b/workbench-for-google-cloud-workstations/conf/launcher-env new file mode 100644 index 00000000..4a53e727 --- /dev/null +++ b/workbench-for-google-cloud-workstations/conf/launcher-env @@ -0,0 +1,6 @@ +JobType: session +Environment: LANG=en_US.UTF-8 + LANGUAGE=en_US:en + LC_ALL=en_US.UTF-8 +JobType: any +Environment: PATH=/opt/python/3.9.17/bin:$PATH diff --git a/workbench-for-google-cloud-workstations/conf/launcher.conf b/workbench-for-google-cloud-workstations/conf/launcher.conf new file mode 100644 index 00000000..dc48bd19 --- /dev/null +++ b/workbench-for-google-cloud-workstations/conf/launcher.conf @@ -0,0 +1,11 @@ +[server] +address=127.0.0.1 +port=5559 +server-user=rstudio-server +admin-group=rstudio-server +authorization-enabled=1 +enable-debug-logging=1 + +[cluster] +name=Local +type=Local diff --git a/workbench-for-google-cloud-workstations/conf/launcher.local.conf b/workbench-for-google-cloud-workstations/conf/launcher.local.conf new file mode 100644 index 00000000..051857e1 --- /dev/null +++ b/workbench-for-google-cloud-workstations/conf/launcher.local.conf @@ -0,0 +1 @@ +unprivileged=1 diff --git a/workbench-for-google-cloud-workstations/conf/logging.conf b/workbench-for-google-cloud-workstations/conf/logging.conf new file mode 100644 index 00000000..08a09174 --- /dev/null +++ b/workbench-for-google-cloud-workstations/conf/logging.conf @@ -0,0 +1,3 @@ +[*] +logger-type=stderr +log-level=info diff --git a/workbench-for-google-cloud-workstations/conf/nginx.site.conf b/workbench-for-google-cloud-workstations/conf/nginx.site.conf new file mode 100644 index 00000000..2d302a7d --- /dev/null +++ b/workbench-for-google-cloud-workstations/conf/nginx.site.conf @@ -0,0 +1,2 @@ +proxy_set_header X-CUSTOM-USER-NAME user/google; +proxy_set_header Host $http_x_forwarded_host; \ No newline at end of file diff --git a/workbench-for-google-cloud-workstations/conf/repos.conf b/workbench-for-google-cloud-workstations/conf/repos.conf new file mode 100644 index 00000000..de4785ea --- /dev/null +++ b/workbench-for-google-cloud-workstations/conf/repos.conf @@ -0,0 +1,2 @@ +CRAN=https://packagemanager.posit.co/cran/__linux__/focal/latest +RSPM=https://packagemanager.posit.co/cran/__linux__/focal/latest diff --git a/workbench-for-google-cloud-workstations/conf/rserver-float.conf b/workbench-for-google-cloud-workstations/conf/rserver-float.conf new file mode 100644 index 00000000..f04dd8c8 --- /dev/null +++ b/workbench-for-google-cloud-workstations/conf/rserver-float.conf @@ -0,0 +1 @@ +server-license-type=remote diff --git a/workbench-for-google-cloud-workstations/conf/rserver.conf b/workbench-for-google-cloud-workstations/conf/rserver.conf new file mode 100644 index 00000000..2bd60929 --- /dev/null +++ b/workbench-for-google-cloud-workstations/conf/rserver.conf @@ -0,0 +1,19 @@ +server-health-check-enabled=1 +admin-enabled=1 + +www-port=80 +server-project-sharing=0 +auth-pam-sessions-enabled=1 +rsession-no-profile=1 + +server-license-manager-path=/opt/rstudio-license/license-manager + +# Launcher Config +launcher-address=127.0.0.1 +launcher-port=5559 +launcher-sessions-enabled=1 +launcher-default-cluster=Local +launcher-sessions-callback-address=http://0.0.0.0:80 +auth-proxy=1 +auth-proxy-sign-in-url=http://localhost:80/ +auth-proxy-user-header=x-custom-user-name diff --git a/workbench-for-google-cloud-workstations/conf/vscode-user-settings.json b/workbench-for-google-cloud-workstations/conf/vscode-user-settings.json new file mode 100644 index 00000000..144b8c2a --- /dev/null +++ b/workbench-for-google-cloud-workstations/conf/vscode-user-settings.json @@ -0,0 +1,5 @@ +{ + "terminal.integrated.shell.linux": "/bin/bash", + "extensions.autoUpdate": false, + "extensions.autoCheckUpdates": false +} diff --git a/workbench-for-google-cloud-workstations/conf/vscode.conf b/workbench-for-google-cloud-workstations/conf/vscode.conf new file mode 100644 index 00000000..405ff90e --- /dev/null +++ b/workbench-for-google-cloud-workstations/conf/vscode.conf @@ -0,0 +1,4 @@ +enabled=1 +args=--verbose --host=0.0.0.0 + +# exe=/usr/lib/rstudio-server/bin/code-server/bin/code-server diff --git a/workbench-for-google-cloud-workstations/conf/vscode.extensions.conf b/workbench-for-google-cloud-workstations/conf/vscode.extensions.conf new file mode 100644 index 00000000..15f24f1f --- /dev/null +++ b/workbench-for-google-cloud-workstations/conf/vscode.extensions.conf @@ -0,0 +1,4 @@ +quarto.quarto +GoogleCloudTools.cloudcode +REditorSupport.r +ms-python.python diff --git a/workbench-for-google-cloud-workstations/deps/apt_packages.txt b/workbench-for-google-cloud-workstations/deps/apt_packages.txt new file mode 100644 index 00000000..bf6d9c10 --- /dev/null +++ b/workbench-for-google-cloud-workstations/deps/apt_packages.txt @@ -0,0 +1,32 @@ +apt-transport-https +build-essential +ca-certificates +default-jdk +dirmngr +dpkg-sig +gnupg +krb5-user +libcap2 +libcurl4-gnutls-dev +libglib2.0-0 +libicu-dev +libnss-sss +libpam-sss +libpq-dev +libpq5 +libsecret-1-dev +libsm6 +libssl-dev +libuser +libuser1-dev +libxext6 +libxrender1 +locales +oddjob-mkhomedir +rrdtool +sssd +supervisor +tcl +tk +tk-dev +tk-table diff --git a/workbench-for-google-cloud-workstations/deps/install_r_packages.sh b/workbench-for-google-cloud-workstations/deps/install_r_packages.sh new file mode 100755 index 00000000..885ee27f --- /dev/null +++ b/workbench-for-google-cloud-workstations/deps/install_r_packages.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +set -ex + +UBUNTU_CODENAME=$(lsb_release -cs) +CRAN_REPO="https://packagemanager.posit.co/cran/__linux__/${UBUNTU_CODENAME}/latest" + +r_packages=$(awk '{print "\"" $0 "\""}' r_packages.txt | paste -d',' -s -) +/opt/R/${R_VERSION}/bin/R --slave -e "install.packages(c(${r_packages}), repos = \"${CRAN_REPO}\")" +/opt/R/${R_VERSION_ALT}/bin/R --slave -e "install.packages(c(${r_packages}), repos = \"${CRAN_REPO}\")" diff --git a/workbench-for-google-cloud-workstations/deps/py_packages.txt b/workbench-for-google-cloud-workstations/deps/py_packages.txt new file mode 100644 index 00000000..cf9726a1 --- /dev/null +++ b/workbench-for-google-cloud-workstations/deps/py_packages.txt @@ -0,0 +1,4 @@ +numpy +scipy +pandas +matplotlib \ No newline at end of file diff --git a/workbench-for-google-cloud-workstations/deps/r_packages.txt b/workbench-for-google-cloud-workstations/deps/r_packages.txt new file mode 100644 index 00000000..401fa223 --- /dev/null +++ b/workbench-for-google-cloud-workstations/deps/r_packages.txt @@ -0,0 +1,67 @@ +DBI +R6 +RJDBC +RODBC +RSQLite +Rcpp +base64enc +checkmate +crayon +curl +devtools +digest +ellipsis +evaluate +fastmap +glue +haven +highr +htmltools +htmlwidgets +httpuv +jsonlite +keyring +knitr +later +learnr +magrittr +markdown +mime +miniUI +mongolite +odbc +openssl +packrat +plumber +png +profvis +promises +r2d3 +ragg +rappdirs +rJava +readr +readxl +renv +reticulate +rlang +rmarkdown +roxygen2 +rprojroot +rsconnect +rstan +rstudioapi +shiny +shinytest +sourcetools +stringi +stringr +testthat +tidyverse +tidymodels +tinytex +withr +xfun +xml2 +xtable +yaml diff --git a/workbench-for-google-cloud-workstations/docker-compose.test.yml b/workbench-for-google-cloud-workstations/docker-compose.test.yml new file mode 100644 index 00000000..8de098c6 --- /dev/null +++ b/workbench-for-google-cloud-workstations/docker-compose.test.yml @@ -0,0 +1,24 @@ +version: '2.3' +services: + + sut: + image: $IMAGE_NAME + command: /run_tests.sh + entrypoint: [] + environment: + # uses .env by default + - RSW_VERSION + - R_VERSION + - R_VERSION_ALT + - PYTHON_VERSION + - PYTHON_VERSION_ALT + - PYTHON_VERSION_JUPYTER + - QUARTO_VERSION + - DRIVERS_VERSION + - RSW_LICENSE + - RSW_LICENSE_SERVER + volumes: + - "./test/run_tests.sh:/run_tests.sh" + - "./test/goss.yaml:/tmp/goss.yaml" + - "./deps/py_packages.txt:/tmp/py_packages.txt" + - "./deps/r_packages.txt:/tmp/r_packages.txt" diff --git a/workbench-for-google-cloud-workstations/jupyter/jupyter_notebook_config.json b/workbench-for-google-cloud-workstations/jupyter/jupyter_notebook_config.json new file mode 100644 index 00000000..aec0a83e --- /dev/null +++ b/workbench-for-google-cloud-workstations/jupyter/jupyter_notebook_config.json @@ -0,0 +1,15 @@ +{ + "NotebookApp": { + "nbserver_extensions":{ + "rsconnect_jupyter": true + }, + "terminado_settings": { + "shell_command": ["/bin/bash"] + } + }, + "ServerApp": { + "terminado_settings": { + "shell_command": ["/bin/bash"] + } + } +} \ No newline at end of file diff --git a/workbench-for-google-cloud-workstations/license-manager-shim b/workbench-for-google-cloud-workstations/license-manager-shim new file mode 100755 index 00000000..4ad2633d --- /dev/null +++ b/workbench-for-google-cloud-workstations/license-manager-shim @@ -0,0 +1,7 @@ +#!/bin/sh + +# Copyright (C) 2023 by Posit, PBC. + +dir=$(dirname "$0") +export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/opt/rstudio-license" +exec "$dir/license-manager-orig" "$@" diff --git a/workbench-for-google-cloud-workstations/pam/rstudio b/workbench-for-google-cloud-workstations/pam/rstudio new file mode 100644 index 00000000..cf173d6d --- /dev/null +++ b/workbench-for-google-cloud-workstations/pam/rstudio @@ -0,0 +1,14 @@ +#%PAM-1.0 +auth sufficient pam_sss.so forward_pass +auth required pam_unix.so try_first_pass nullok +auth optional pam_permit.so +auth required pam_env.so + +account [default=bad success=ok user_unknown=ignore authinfo_unavail=ignore] pam_sss.so +account required pam_unix.so +account optional pam_permit.so +account required pam_time.so + +password sufficient pam_sss.so use_authtok +password required pam_unix.so try_first_pass nullok sha512 shadow +password optional pam_permit.so diff --git a/workbench-for-google-cloud-workstations/pam/rstudio-session b/workbench-for-google-cloud-workstations/pam/rstudio-session new file mode 100644 index 00000000..05894708 --- /dev/null +++ b/workbench-for-google-cloud-workstations/pam/rstudio-session @@ -0,0 +1,26 @@ +#%PAM-1.0 + +# This allows root to su without passwords (this is required) +# DO NOT use in the "rstudio" profile +auth sufficient pam_rootok.so +auth sufficient pam_sss.so forward_pass +auth required pam_unix.so try_first_pass nullok +auth optional pam_permit.so +auth required pam_env.so + +account [default=bad success=ok user_unknown=ignore authinfo_unavail=ignore] pam_sss.so +account required pam_unix.so +account optional pam_permit.so +account required pam_time.so + +password sufficient pam_sss.so use_authtok +password required pam_unix.so try_first_pass nullok sha512 shadow +password optional pam_permit.so + +session required pam_mkhomedir.so skel=/etc/skel umask=0022 +session required pam_env.so readenv=1 +session required pam_env.so readenv=1 envfile=/etc/default/locale +session required pam_limits.so +session required pam_unix.so +session optional pam_sss.so +session optional pam_permit.so diff --git a/workbench-for-google-cloud-workstations/sssd.conf b/workbench-for-google-cloud-workstations/sssd.conf new file mode 100644 index 00000000..7bbba4ab --- /dev/null +++ b/workbench-for-google-cloud-workstations/sssd.conf @@ -0,0 +1,9 @@ +[sssd] +config_file_version = 2 +domains = placeholder + +[domain/placeholder] +id_provider = none +auth_provider = none +chpass_provider = none +sudo_provider = none diff --git a/workbench-for-google-cloud-workstations/startup-launcher/rstudio-launcher.conf b/workbench-for-google-cloud-workstations/startup-launcher/rstudio-launcher.conf new file mode 100644 index 00000000..5fd8540d --- /dev/null +++ b/workbench-for-google-cloud-workstations/startup-launcher/rstudio-launcher.conf @@ -0,0 +1,8 @@ +[program:rstudio-launcher] +command=/usr/lib/rstudio-server/bin/rstudio-launcher +autorestart=false +numprocs=1 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/workbench-for-google-cloud-workstations/startup-user-provisioning/sssd.conf b/workbench-for-google-cloud-workstations/startup-user-provisioning/sssd.conf new file mode 100644 index 00000000..387913e5 --- /dev/null +++ b/workbench-for-google-cloud-workstations/startup-user-provisioning/sssd.conf @@ -0,0 +1,11 @@ +[program:sssd] +# TODO: a way to disable this easily...? +command=/usr/sbin/sssd -i -c /etc/sssd/sssd.conf --logger=stderr +autorestart=false +numprocs=1 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stdout_logfile_backups=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +stderr_logfile_backups=0 diff --git a/workbench-for-google-cloud-workstations/startup.sh b/workbench-for-google-cloud-workstations/startup.sh new file mode 100644 index 00000000..21fb32ce --- /dev/null +++ b/workbench-for-google-cloud-workstations/startup.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +export LICENSE_MANAGER_PATH=${LICENSE_MANAGER_PATH:-/opt/rstudio-license} + +set -ex + +# Uncomment to get a log for this script +#exec >/startup.log 2>&1 + +# Deactivate license when the process exits +deactivate() { + echo "== Exiting ==" + rstudio-server stop + echo "Deactivating license ..." + ${LICENSE_MANAGER_PATH}/license-manager deactivate >/dev/null 2>&1 + + echo "== Done ==" +} +trap deactivate EXIT + +verify_installation(){ + echo "==VERIFY INSTALLATION=="; + mkdir -p $DIAGNOSTIC_DIR + chmod 777 $DIAGNOSTIC_DIR + rstudio-server verify-installation --verify-user=$RSW_TESTUSER | tee $DIAGNOSTIC_DIR/verify.log +} + +set +x + +# Support RSP_ or RSW_ prefix +RSP_LICENSE=${RSP_LICENSE:-${RSW_LICENSE}} +RSP_LICENSE_SERVER=${RSP_LICENSE_SERVER:-${RSW_LICENSE_SERVER}} + +# Activate License +RSW_LICENSE_FILE_PATH=${RSW_LICENSE_FILE_PATH:-/etc/rstudio-server/license.lic} +if [ -n "$RSP_LICENSE" ]; then + ${LICENSE_MANAGER_PATH}/license-manager activate $RSP_LICENSE || true +elif [ -n "$RSP_LICENSE_SERVER" ]; then + ${LICENSE_MANAGER_PATH}/license-manager license-server $RSP_LICENSE_SERVER || true +elif test -f "$RSW_LICENSE_FILE_PATH"; then + ${LICENSE_MANAGER_PATH}/license-manager activate-file $RSW_LICENSE_FILE_PATH || true +fi + +# ensure these cannot be inherited by child processes +unset RSP_LICENSE +unset RSP_LICENSE_SERVER +unset RSW_LICENSE +unset RSW_LICENSE_SERVER + +set -x + +# Start Launcher +if [ "$RSW_LAUNCHER" == "true" ]; then + echo "Waiting for launcher to startup... to disable set RSW_LAUNCHER=false" + wait-for-it.sh localhost:5559 -t $RSW_LAUNCHER_TIMEOUT +fi + +# Check diagnostic configurations +if [ "$DIAGNOSTIC_ENABLE" == "true" ]; then + verify_installation + if [ "$DIAGNOSTIC_ONLY" == "true" ]; then + echo $(<$DIAGNOSTIC_DIR/verify.log); + echo "Exiting script because DIAGNOSTIC_ONLY=${DIAGNOSTIC_ONLY}"; + exit 0 + fi; +else + echo "not running verify installation because DIAGNOSTIC_ENABLE=${DIAGNOSTIC_ENABLE}"; +fi + +# the main container process +# cannot use "exec" or the "trap" will be lost +/usr/lib/rstudio-server/bin/rserver --server-daemonize 0 > /dev/stderr diff --git a/workbench-for-google-cloud-workstations/startup/rstudio-workbench.conf b/workbench-for-google-cloud-workstations/startup/rstudio-workbench.conf new file mode 100644 index 00000000..0acac704 --- /dev/null +++ b/workbench-for-google-cloud-workstations/startup/rstudio-workbench.conf @@ -0,0 +1,8 @@ +[program:rstudio-workbench] +command=/usr/local/bin/startup.sh +autorestart=false +numprocs=1 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 diff --git a/workbench-for-google-cloud-workstations/supervisord.conf b/workbench-for-google-cloud-workstations/supervisord.conf new file mode 100644 index 00000000..7180a826 --- /dev/null +++ b/workbench-for-google-cloud-workstations/supervisord.conf @@ -0,0 +1,47 @@ +; supervisor config file + +[unix_http_server] +file=/var/run/supervisor.sock ; (the path to the socket file) +chmod=0700 ; sockef file mode (default 0700) + +[supervisord] +logfile=/dev/stdout ; (main log file;default $CWD/supervisord.log) +user=root +pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid) +; should configure each program to use stdout/stderr +; childlogdir=/var/log/supervisor ; ('AUTO' child log dir, default $TEMP) +logfile_maxbytes=0 +logfile_backups=0 +loglevel=info +nodaemon=true + +; the below section must remain in the config file for RPC +; (supervisorctl/web interface) to work, additional interfaces may be +; added by defining them in separate rpcinterface: sections +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[supervisorctl] +serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL for a unix socket + +; The [include] section can just contain the "files" setting. This +; setting can list multiple files (separated by whitespace or +; newlines). It can also contain wildcards. The filenames are +; interpreted as relative to this file. Included files *cannot* +; include files themselves. + +[eventlistener:process-monitor] +command=bash -c "printf 'READY\n' && while read line; do kill -SIGQUIT $PPID; done < /dev/stdin" +events=PROCESS_STATE_STOPPED,PROCESS_STATE_EXITED,PROCESS_STATE_FATAL +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stdout_logfile_backups=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +stderr_logfile_backups=0 + +; beware possible race condition +; if one of these services exit before the process-monitor is up + +[include] +files = /startup/base/*.conf /startup/launcher/*.conf /startup/user-provisioning/*.conf /startup/custom/*.conf diff --git a/workbench-for-google-cloud-workstations/test/goss.yaml b/workbench-for-google-cloud-workstations/test/goss.yaml new file mode 100644 index 00000000..674665ef --- /dev/null +++ b/workbench-for-google-cloud-workstations/test/goss.yaml @@ -0,0 +1,233 @@ +user: + rstudio-server: + exists: true + uid: 999 + gid: 998 + +group: + rstudio-server: + exists: true + gid: 998 + +package: + rstudio-server: + installed: true + versions: + - {{ .Env.RSW_VERSION }} + r-{{.Env.R_VERSION}}: + installed: true + r-{{.Env.R_VERSION_ALT}}: + installed: true + python-{{.Env.PYTHON_VERSION}}: + installed: true + python-{{.Env.PYTHON_VERSION_ALT}}: + installed: true + quarto: + installed: true + versions: + - {{.Env.QUARTO_VERSION}} + rstudio-drivers: + installed: true + versions: + - {{.Env.DRIVERS_VERSION}} + +port: + tcp:80: + listening: true + ip: + - 0.0.0.0 + skip: false + tcp:5559: + listening: true + ip: + - 127.0.0.1 + skip: false + +process: + rserver: + running: true + skip: false + rstudio-launche: + running: true + skip: false + +file: + /etc/rstudio-server/license.lic: + exists: false + /usr/lib/rstudio-server: + exists: true + /opt/R/{{.Env.R_VERSION_ALT}}/bin/R: + exists: true + filetype: file + /opt/python/{{.Env.PYTHON_VERSION}}/bin/python: + exists: true + filetype: symlink + /opt/python/{{.Env.PYTHON_VERSION_ALT}}/bin/python: + exists: true + filetype: symlink + /opt/python/jupyter/bin/python: + exists: true + filetype: symlink + /opt/python/jupyter/bin/jupyter: + exists: true + /usr/local/bin/jupyter: + exists: true + /usr/lib/rstudio-server/bin/rserver: + exists: true + /usr/lib/rstudio-server/bin/rstudio-server: + exists: true + /usr/lib/rstudio-server/bin/rstudio-launcher: + exists: true + /var/lib/rstudio-server/monitor/log: + exists: true + owner: rstudio-server + group: rstudio-server + /usr/lib/rstudio-server/bin/code-server/bin/code-server: + exists: true + /etc/rstudio/vscode.conf: + exists: true + /etc/rstudio/jupyter.conf: + exists: true + contains: + - --LabApp.token="" + - --no-browser + - --allow-root + - --ip=0.0.0.0 + /etc/rstudio/rserver.conf: + exists: true + contains: + - www-port=80 + - launcher-sessions-callback-address=http://0.0.0.0:80 + - auth-proxy=1 + - auth-proxy-sign-in-url=http://localhost:80/ + - auth-proxy-user-header=x-custom-user-name + /etc/rstudio/nginx.site.conf: + exists: true + contains: + - proxy_set_header X-CUSTOM-USER-NAME user/google; + - proxy_set_header Host $http_x_forwarded_host; + /etc/rstudio/vscode.extensions.conf: + exists: true + contains: + - quarto.quarto + - GoogleCloudTools.cloudcode + /tmp/startup.log: + exists: true + contains: + - "!Error reading /etc/rstudio/rserver.conf:" + /etc/pam.d/common-session: + exists: true + contains: + - "/^session required pam_mkhomedir.so skel=/etc/skel umask=0022$/" + /etc/sssd/sssd.conf: + exists: true + owner: root + group: root + mode: "0600" + /usr/local/bin/wait-for-it.sh: + exists: true + owner: root + group: root + mode: "0755" + /etc/workstation-startup.d/010_add-user.sh: + exists: true + owner: root + group: root + mode: "0755" + filetype: file + contains: [ + "useradd -m user" + ] + /etc/workstation-startup.d/110_config-jupyter.sh: + exists: true + owner: root + group: root + mode: "0755" + filetype: file + contains: [ + # Checks that we're setting the Jupyter shell to /bin/bash + "echo \"c.ServerApp.terminado_settings = {'shell_command': ['/bin/bash']}\" > /home/user/.jupyter/jupyter_notebook_config.py" + ] + /etc/workstation-startup.d/120_start-workbench.sh: + exists: true + owner: root + group: root + mode: "0755" + filetype: file + contains: [ + "/usr/bin/supervisord -c /etc/supervisor/supervisord.conf" + ] + + +command: + # Check OS release version (early heads up in case the base image is modified) + Check OS version is "focal": + exec: "lsb_release -cs" + exit-status: 0 + stdout: [ + "focal" + ] + Test Workbench Monitor rstudio-server.log creation: + exec: su rstudio-server -c 'touch /var/lib/rstudio-server/monitor/log/rstudio-server.log' + exit-status: 0 + Test rstudio-server.log creation: + exec: touch /var/log/rstudio-server.log + exit-status: 0 + Check RStudio Workbench version is {{ .Env.RSW_VERSION }}: + exec: rstudio-server version + exit-status: 0 + stdout: [ + "{{ .Env.RSW_VERSION }}", + "Workbench" + ] + Test Jupyter works: + exec: "echo '{ \"cells\": [], \"metadata\": {}, \"nbformat\": 4, \"nbformat_minor\": 2}' | /opt/python/jupyter/bin/jupyter nbconvert --to notebook --stdin --stdout" + exit-status: 0 + Check primary Python is version {{.Env.PYTHON_VERSION}}: + exec: /opt/python/{{.Env.PYTHON_VERSION}}/bin/python --version + exit-status: 0 + stdout: [ + "{{.Env.PYTHON_VERSION}}" + ] + Check alternate Python is version {{.Env.PYTHON_VERSION_ALT}}: + exec: /opt/python/{{.Env.PYTHON_VERSION_ALT}}/bin/python --version + exit-status: 0 + stdout: [ + "{{.Env.PYTHON_VERSION_ALT}}" + ] + Check Jupyter venv uses Python {{.Env.PYTHON_VERSION_JUPYTER}}: + exec: /opt/python/jupyter/bin/python --version + exit-status: 0 + stdout: [ + "{{.Env.PYTHON_VERSION_JUPYTER}}" + ] + Check for OpenSSL: + exec: which openssl + exit-status: 0 + stdout: [ + "/usr/bin/openssl" + ] + {{ $python_version := .Env.PYTHON_VERSION }} + {{ $python_version_alt := .Env.PYTHON_VERSION_ALT }} + {{ $py_package_list := readFile "/tmp/py_packages.txt" | splitList "\n" }} + {{- range $py_package_list }} + Check Python {{ $python_version }} has "{{.}}" installed: + exec: /opt/python/{{$python_version}}/bin/pip show {{.}} + exit-status: 0 + Check Python {{ $python_version_alt }} has "{{.}}" installed: + exec: /opt/python/{{$python_version_alt}}/bin/pip show {{.}} + exit-status: 0 + {{end}} + {{ $r_version := .Env.R_VERSION }} + {{ $r_version_alt := .Env.R_VERSION_ALT }} + {{ $r_package_list := readFile "/tmp/r_packages.txt" | splitList "\n" }} + {{- range $r_package_list }} + Check R {{ $r_version }} has "{{.}}" installed: + exec: /opt/R/{{$r_version}}/bin/R --slave -e "library(\"{{.}}\")" + timeout: 60000 + exit-status: 0 + Check R {{ $r_version_alt }} has "{{.}}" installed: + exec: /opt/R/{{$r_version_alt}}/bin/R --slave -e "library(\"{{.}}\")" + timeout: 60000 + exit-status: 0 + {{end}} \ No newline at end of file diff --git a/workbench-for-google-cloud-workstations/test/run_tests.sh b/workbench-for-google-cloud-workstations/test/run_tests.sh new file mode 100755 index 00000000..02c2cd04 --- /dev/null +++ b/workbench-for-google-cloud-workstations/test/run_tests.sh @@ -0,0 +1,33 @@ +#!/bin/bash +set -xe + +RSW_TIMEOUT=${RSW_TIMEOUT:-60} + +touch /tmp/startup.log +trap 'err=$?; echo >&2 "run_tests.sh encountered an error: $err"; cat /tmp/startup.log; exit $err' ERR + +# start rstudio-server +echo "--> Starting RStudio Workbench" +/usr/bin/supervisord -c /etc/supervisor/supervisord.conf > /tmp/startup.log 2>&1 & + +echo "--> Waiting for workbench to startup... with RSW_TIMEOUT: $RSW_TIMEOUT" +wait-for-it.sh localhost:80 -t $RSW_TIMEOUT +wait-for-it.sh localhost:5559 -t $RSW_TIMEOUT +echo "--> Startup complete" + +GOSS_FILE=${GOSS_FILE:-/tmp/goss.yaml} +GOSS_VARS=${GOSS_VARS:-/tmp/goss_vars.yaml} +GOSS_VERSION=${GOSS_VERSION:-0.3.22} +GOSS_MAX_CONCURRENT=${GOSS_MAX_CONCURRENT:-50} + +# default to empty var file (since vars are not necessary) +if [ ! -f "$GOSS_VARS" ]; then + touch $GOSS_VARS +fi + +# install goss to tmp location and make executable +curl -sL https://github.com/aelsabbahy/goss/releases/download/v$GOSS_VERSION/goss-linux-amd64 -o /tmp/goss \ + && chmod +x /tmp/goss \ + && GOSS=/tmp/goss + +GOSS_FILE=$GOSS_FILE GOSS_VARS=$GOSS_VARS $GOSS v --format documentation --max-concurrent $GOSS_MAX_CONCURRENT diff --git a/workbench-for-google-cloud-workstations/workstation-startup/110_config-jupyter.sh b/workbench-for-google-cloud-workstations/workstation-startup/110_config-jupyter.sh new file mode 100755 index 00000000..6f97b81a --- /dev/null +++ b/workbench-for-google-cloud-workstations/workstation-startup/110_config-jupyter.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -ex + +mkdir -p /home/user/.jupyter +echo "c.ServerApp.terminado_settings = {'shell_command': ['/bin/bash']}" > /home/user/.jupyter/jupyter_notebook_config.py +chown -R user:user /home/user/.jupyter +chmod 644 /home/user/.jupyter/jupyter_notebook_config.py diff --git a/workbench-for-google-cloud-workstations/workstation-startup/120_start-workbench.sh b/workbench-for-google-cloud-workstations/workstation-startup/120_start-workbench.sh new file mode 100755 index 00000000..ad889455 --- /dev/null +++ b/workbench-for-google-cloud-workstations/workstation-startup/120_start-workbench.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -ex + +/usr/bin/supervisord -c /etc/supervisor/supervisord.conf