From 5083d15b28f6b9ebd2f17ab04b48ac523a61a6cb Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Tue, 3 Sep 2024 15:54:17 +0100 Subject: [PATCH] Add rstudio-server to r image as rstudio image --- .gitignore | 10 ++++++++++ Dockerfile | 42 ++++++++++++++++++++++++++++++++++++++++ README.md | 20 +++++++++++++++++++ docker-compose.yaml | 10 +++++++++- justfile | 16 +++++++++++++++ rstudio-dependencies.txt | 5 +++++ rstudio-entrypoint.sh | 33 +++++++++++++++++++++++++++++++ rstudio-rprofile.R | 5 +++++ test-rstudio.sh | 39 +++++++++++++++++++++++++++++++++++++ 9 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 rstudio-dependencies.txt create mode 100755 rstudio-entrypoint.sh create mode 100644 rstudio-rprofile.R create mode 100755 test-rstudio.sh diff --git a/.gitignore b/.gitignore index 540c779..e0352eb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,13 @@ renv.lock.bak renv/ .tests.R + +# rstudio-server directories +*.config +*.local + +# R files +.Rhistory + +# metadata directory, ignored in research-template, ignore for rstudio test +metadata/ diff --git a/Dockerfile b/Dockerfile index 2106170..9750ba1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -94,3 +94,45 @@ WORKDIR /workspace COPY --from=builder /renv /renv # this will ensure the renv is activated by default RUN echo 'source("/renv/renv/activate.R")' >> /etc/R/Rprofile.site + +################################################# +# +# Add rstudio-server to r image - creating rstudio image +ARG RSTUDIO_BASE_URL="default-arg-to-silence-docker" +ARG RSTUDIO_DEB="default-arg-to-silence-docker" +FROM r as rstudio + +# Install rstudio-server (and a few dependencies) +COPY rstudio-dependencies.txt /root/rstudio-dependencies.txt +RUN --mount=type=cache,target=/var/cache/apt /root/docker-apt-install.sh /root/rstudio-dependencies.txt &&\ + test -f /var/cache/apt/"${RSTUDIO_DEB}" ||\ + /usr/lib/apt/apt-helper download-file "${RSTUDIO_BASE_URL}${RSTUDIO_DEB}" /var/cache/apt/"${RSTUDIO_DEB}" &&\ + apt-get install --no-install-recommends -y /var/cache/apt/"${RSTUDIO_DEB}" + +# Configuration +## Start by setting up rstudio user using approach in opensafely-core/research-template-docker +RUN useradd rstudio &&\ + # Disable rstudio-server authentication + echo "auth-none=1" >> /etc/rstudio/rserver.conf &&\ + # Run the server under the single user account + echo "server-user=rstudio" >> /etc/rstudio/rserver.conf &&\ + echo "USER=rstudio" >> /etc/environment &&\ + # Give the rstudio user sudo (aka root) permissions + usermod -aG sudo rstudio &&\ + echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers &&\ + # Add a home directory for the rstudio user + mkdir /home/rstudio &&\ + chown -R rstudio:rstudio /home/rstudio/ &&\ + # Use renv R packages + # Remember that the second renv library directory /renv/sandbox/R-4.0/x86_64-pc-linux-gnu/9a444a72 + # contains 14 symlinks to 14 of the 15 packages in ${R_HOME}/library which is /usr/lib/R/library/ + # so that is already setup + echo "R_LIBS_SITE=/renv/lib/R-4.0/x86_64-pc-linux-gnu" >> /usr/lib/R/etc/Renviron.site &&\ + # open RStudio in /workspace + echo "session-default-working-dir=/workspace" >> /etc/rstudio/rsession.conf + +COPY rstudio-entrypoint.sh /usr/local/bin/rstudio-entrypoint.sh +COPY rstudio-rprofile.R /home/rstudio/rstudio-rprofile.R + +ENV USER rstudio +ENTRYPOINT ["/usr/local/bin/rstudio-entrypoint.sh"] diff --git a/README.md b/README.md index 2c3ef96..eced32c 100644 --- a/README.md +++ b/README.md @@ -102,3 +102,23 @@ Find a previous version at `https://cran.r-project.org/src/contrib/Archive/{PACK ```sh just add-package PACKAGE@VERSION ``` + +## Building, testing, and publishing the rstudio image + +The rstudio image is based on the r image including rstudio-server. To build run + +```sh +just build-rstudio +``` + +To test that rstudio-server appears at `http://localhost:8787` run + +```sh +just test-rstudio +``` + +And then push the new rstudio image to the GitHub container registry with + +```sh +just publish-rstudio +``` diff --git a/docker-compose.yaml b/docker-compose.yaml index ada0ab0..681107c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -23,4 +23,12 @@ services: target: add-package args: - PACKAGE - + rstudio: + extends: r + image: rstudio + build: + target: rstudio + args: + # supplied by just + - RSTUDIO_BASE_URL + - RSTUDIO_DEB diff --git a/justfile b/justfile index 99a0bbb..2dea518 100644 --- a/justfile +++ b/justfile @@ -22,11 +22,23 @@ build: add-package package: bash ./add-package.sh {{ package }} +# r image containing rstudio-server +build-rstudio: + #!/usr/bin/env bash + set -euo pipefail + + # Set RStudio Server .deb filename + export RSTUDIO_BASE_URL=https://download2.rstudio.org/server/focal/amd64/ + export RSTUDIO_DEB=rstudio-server-2024.09.0-375-amd64.deb + docker-compose build --pull rstudio # test the locally built image test image="r": build bash ./test.sh "{{ image }}" +# test rstudio-server launches +test-rstudio: + bash ./test-rstudio.sh # lint source code lint: @@ -36,3 +48,7 @@ lint: publish: docker tag r ghcr.io/opensafely-core/r:latest docker push ghcr.io/opensafely-core/r:latest + +publish-rstudio: + docker tag rstudio ghcr.io/opensafely-core/rstudio:latest + docker push ghcr.io/opensafely-core/rstudio:latest diff --git a/rstudio-dependencies.txt b/rstudio-dependencies.txt new file mode 100644 index 0000000..2054785 --- /dev/null +++ b/rstudio-dependencies.txt @@ -0,0 +1,5 @@ +git +libclang-dev +lsb-release +psmisc +sudo diff --git a/rstudio-entrypoint.sh b/rstudio-entrypoint.sh new file mode 100755 index 0000000..fe79957 --- /dev/null +++ b/rstudio-entrypoint.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# On Linux set rstudio user id to same as id of host if rstudio user id not already the same +if [ "$HOSTPLATFORM" = "linux" -a "$(id -u rstudio)" != "$HOSTUID" ]; then + usermod -u $HOSTUID rstudio +fi + +# Check for 1 .Rproj file +if [ $(find /workspace -type f -name "*.Rproj" | wc -w) -eq 1 ]; then + + # Copy in Git user.name and user.email from copied local-gitconfig from additionally mounted volume + if test -f /home/rstudio/local-gitconfig; then + grep -e "\[user\]" -e "name = *" -e "email = *" /home/rstudio/local-gitconfig >> /home/rstudio/.gitconfig + fi + + # Avoid Git error: fatal detected dubious ownership of repository if using Git in container + # Without this the Git pane fails to open when RStudio project opened + echo -e "[safe]\n\tdirectory = \"*\"" >> /home/rstudio/.gitconfig + + # Open RStudio project on opening RStudio Server session using an rstudio hook in .Rprofile + cat /home/rstudio/rstudio-rprofile.R >> /home/rstudio/.Rprofile +fi + +# Set file line endings as crlf if docker run from Windows +if [ "$HOSTPLATFORM" = "win32" ]; then + echo -e "{\n\t\"line_ending_conversion\": \"windows\"\n}" >> /etc/rstudio/rstudio-prefs.json +fi + +# Start RStudio Server session in foreground +# Hence don't use `rstudio-server start` which runs in background +# and attempt to capture std out and err to a metadata log file +mkdir -p /workspace/metadata +exec /usr/lib/rstudio-server/bin/rserver --server-daemonize 0 >/workspace/metadata/rstudio.log 2>&1 diff --git a/rstudio-rprofile.R b/rstudio-rprofile.R new file mode 100644 index 0000000..d7a5097 --- /dev/null +++ b/rstudio-rprofile.R @@ -0,0 +1,5 @@ +setHook("rstudio.sessionInit", function(newSession) { + if (newSession && is.null(rstudioapi::getActiveProject())) { + rstudioapi::openProject(paste0("/workspace/", list.files(pattern = "Rproj"))) + } +}, action = "append") diff --git a/test-rstudio.sh b/test-rstudio.sh new file mode 100755 index 0000000..51cdadd --- /dev/null +++ b/test-rstudio.sh @@ -0,0 +1,39 @@ +#!/bin/bash +set -eu + +# Detect operating system for `docker run` call +OSTYPEFIRSTFIVE=$(echo "$OSTYPE" | cut -c1-5) +if [[ "$OSTYPEFIRSTFIVE" == "linux" ]]; then + PLATFORM="linux" +else + PLATFORM="somethingelse" +fi + +trap "docker stop test_rstudio > /dev/null 2>&1 || true" EXIT + +docker run \ + --rm \ + --init \ + --label=opensafely \ + --interactive \ + --user=0:0 --volume="/${PWD}://workspace" \ + --platform=linux/amd64 \ + -p=8787:8787 \ + --name=test_rstudio \ + --hostname=test_rstudio \ + --volume="/${HOME}/.gitconfig:/home/rstudio/local-gitconfig" \ + --env=HOSTPLATFORM=${PLATFORM} \ + --env=HOSTUID=$(id -u) \ + --detach \ + rstudio > /dev/null 2>&1 + +sleep 5 + +status_code=$(curl --write-out %{http_code} --silent --output /dev/null -L --retry 3 --max-time 10 http://localhost:8787) +if [[ "$status_code" -ne 200 ]] ; then + echo "200 response not received from http://localhost:8787" + exit 1 +else + echo "200 response successfully received from http://localhost:8787" + exit 0 +fi