From ae0474f0af7c8ebe7794bbc95483cbb3bb043963 Mon Sep 17 00:00:00 2001 From: RJ Trujillo Date: Wed, 27 Mar 2024 10:55:01 -0600 Subject: [PATCH] feat(images): Add RStudio (#2410) Signed-off-by: RJ Sampson --- images/rstudio/README.md | 38 ++++++++++++ images/rstudio/config/main.tf | 89 ++++++++++++++++++++++++++++ images/rstudio/main.tf | 39 +++++++++++++ images/rstudio/metadata.yaml | 12 ++++ images/rstudio/tests/main.tf | 16 +++++ images/rstudio/tests/smoke.sh | 106 ++++++++++++++++++++++++++++++++++ main.tf | 5 ++ 7 files changed, 305 insertions(+) create mode 100644 images/rstudio/README.md create mode 100644 images/rstudio/config/main.tf create mode 100644 images/rstudio/main.tf create mode 100644 images/rstudio/metadata.yaml create mode 100755 images/rstudio/tests/main.tf create mode 100755 images/rstudio/tests/smoke.sh diff --git a/images/rstudio/README.md b/images/rstudio/README.md new file mode 100644 index 0000000000..014e73115f --- /dev/null +++ b/images/rstudio/README.md @@ -0,0 +1,38 @@ + +# rstudio +| | | +| - | - | +| **OCI Reference** | `cgr.dev/chainguard/rstudio` | + + +* [View Image in Chainguard Academy](https://edu.chainguard.dev/chainguard/chainguard-images/reference/rstudio/overview/) +* [View Image Catalog](https://console.enforce.dev/images/catalog) for a full list of available tags. +* [Contact Chainguard](https://www.chainguard.dev/chainguard-images) for enterprise support, SLAs, and access to older tags.* + +--- + + + +Minimal [RStudio](https://github.com/rstudio/rstudio) container image. + + + +## Download this Image +The image is available on `cgr.dev`: + +``` +docker pull cgr.dev/chainguard/rstudio:latest +``` + + + +## Running the Image +In order to run RStudio, execute the following command in a terminal: + +```bash +docker run -it -p 8787:8787 cgr.dev/chainguard/rstudio:latest +``` + +The server will now start and the IDE will be accessible at [localhost:8787](http://localhost:8787) in your browser of choice. + + diff --git a/images/rstudio/config/main.tf b/images/rstudio/config/main.tf new file mode 100644 index 0000000000..c448e8a50d --- /dev/null +++ b/images/rstudio/config/main.tf @@ -0,0 +1,89 @@ +terraform { + required_providers { + apko = { source = "chainguard-dev/apko" } + } +} + +variable "extra_packages" { + description = "Additional packages to install." + type = list(string) + default = ["rstudio"] +} + +variable "environment" { + default = {} +} + +module "accts" { + source = "../../../tflib/accts" + run-as = 65532 + uid = 65532 + gid = 65532 + name = "rstudio-server" +} + +output "config" { + value = jsonencode({ + contents = { + packages = var.extra_packages + } + accounts = module.accts.block + environment = merge({ + "CRAN" : "https://cloud.r-project.org" + "USER" : "rstudio-server" + "LANG" : "en_US.UTF-8" + "LANG_WHICH" : "en" + "LANG_WHERE" : "US" + "ENCODING" : "UTF-8" + "LANGUAGE" : "en_US.UTF-8" + "LC_CTYPE" : "en_US" + "TZ" : "UTC" + }) + entrypoint = { + command = "/usr/bin/rserver" + } + paths = [{ + path = "/etc/R" + type = "directory" + uid = module.accts.uid + gid = module.accts.gid + permissions = 509 + recursive = true + }, { + path = "/etc/rstudio" + type = "directory" + uid = module.accts.uid + gid = module.accts.gid + permissions = 509 + recursive = true + }, { + path = "/usr/lib/R" + type = "directory" + uid = module.accts.uid + gid = module.accts.gid + permissions = 509 + recursive = true + }, { + path = "/usr/share/doc/R" + type = "directory" + uid = module.accts.uid + gid = module.accts.gid + permissions = 509 + recursive = true + }, { + path = "/var/run/rstudio-server" + type = "directory" + uid = module.accts.uid + gid = module.accts.gid + permissions = 509 + recursive = true + }, { + path = "/var/lib/rstudio-server" + type = "directory" + uid = module.accts.uid + gid = module.accts.gid + permissions = 509 + recursive = true + }] + }) +} diff --git a/images/rstudio/main.tf b/images/rstudio/main.tf new file mode 100644 index 0000000000..a8cc60850f --- /dev/null +++ b/images/rstudio/main.tf @@ -0,0 +1,39 @@ +terraform { + required_providers { + oci = { source = "chainguard-dev/oci" } + } +} + +variable "target_repository" { + description = "The docker repo into which the image and attestations should be published." +} + +module "config" { + source = "./config" +} + +module "latest" { + source = "../../tflib/publisher" + name = basename(path.module) + target_repository = var.target_repository + config = module.config.config + build-dev = true +} + +module "test" { + source = "./tests" + + digest = module.latest.image_ref +} + +resource "oci_tag" "latest" { + depends_on = [module.test] + digest_ref = module.latest.image_ref + tag = "latest" +} + +resource "oci_tag" "latest-dev" { + depends_on = [module.test] + digest_ref = module.latest.dev_ref + tag = "latest-dev" +} diff --git a/images/rstudio/metadata.yaml b/images/rstudio/metadata.yaml new file mode 100644 index 0000000000..f4f71b7665 --- /dev/null +++ b/images/rstudio/metadata.yaml @@ -0,0 +1,12 @@ +name: rstudio +image: cgr.dev/chainguard/rstudio +logo: https://storage.googleapis.com/chainguard-academy/logos/rstudio.svg +endoflife: "" +console_summary: "" +short_description: Minimal [RStudio](https://github.com/rstudio/rstudio) container image. +compatibility_notes: "" +readme_file: README.md +upstream_url: https://github.com/rstudio/rstudio +keywords: + - application + - ide diff --git a/images/rstudio/tests/main.tf b/images/rstudio/tests/main.tf new file mode 100755 index 0000000000..cd9c084286 --- /dev/null +++ b/images/rstudio/tests/main.tf @@ -0,0 +1,16 @@ +terraform { + required_providers { + oci = { source = "chainguard-dev/oci" } + } +} + +variable "digest" { + description = "The image digest to run tests over." +} + +data "oci_string" "ref" { input = var.digest } + +data "oci_exec_test" "smoke" { + digest = var.digest + script = "${path.module}/smoke.sh" +} diff --git a/images/rstudio/tests/smoke.sh b/images/rstudio/tests/smoke.sh new file mode 100755 index 0000000000..c6ae3138cc --- /dev/null +++ b/images/rstudio/tests/smoke.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash + +set -o errexit -o nounset -o errtrace -o pipefail -x + +CONTAINER_NAME="rstudio-$(uuidgen)" + +RSTUDIO_PORT="${FREE_PORT}" + +REQUEST_RETRIES=10 +RETRY_DELAY=15 + +declare -a expected_logs=( + "Adding user to cache" + "Running as server user" + "Initialized file locks" + "Connecting to sqlite3 database" + "Creating database connection pool" + "Creating secure key file" + "Using secure key file" +) +declare -a missing_logs=() + +# Validate RStudio installation +# Must be ran before server starts +docker run \ + --rm \ + -p "${RSTUDIO_PORT}":"${RSTUDIO_PORT}" \ + -e "RS_LOG_LEVEL=debug" \ + -e "RS_LOGGER_TYPE=stderr" \ + --name "${CONTAINER_NAME}" \ + --entrypoint rstudio-server \ + "${IMAGE_NAME}" \ + verify-installation + +# Run RStudio server +docker run \ + -d --rm \ + -p "${RSTUDIO_PORT}":"${RSTUDIO_PORT}" \ + -e "RS_LOG_LEVEL=debug" \ + -e "RS_LOGGER_TYPE=stderr" \ + --name "${CONTAINER_NAME}" \ + "${IMAGE_NAME}" \ + --www-port "${RSTUDIO_PORT}" + +# Stop container when script exits +trap "docker stop ${CONTAINER_NAME}" EXIT + +# Validate that RStudio container logs contain expected log messages. +TEST_validate_container_logs() { + for ((i=1; i<=${REQUEST_RETRIES}; i++)); do + local logs=$(docker logs "${CONTAINER_NAME}" 2>&1) + local logs_found=true + + # Search the container logs for our expected log lines. + for log in "${expected_logs[@]}"; do + if ! echo "$logs" | grep -Fq "$log"; then + logs_found=false + fi + done + + if $logs_found; then + return 0 + elif [[ $i -lt ${REQUEST_RETRIES} ]]; then + echo "Some expected logs were missing. Retrying in ${RETRY_DELAY} seconds..." + sleep ${RETRY_DELAY} + fi + done + + # After all retries, record the missing logs + for log in "${expected_logs[@]}"; do + if ! echo "${logs}" | grep -Fq "$log"; then + missing_logs+=("${log}") + fi + done + + echo "FAILED: The following log lines where not found:" + printf '%s\n' "${missing_logs[@]}" + exit 1 +} + +# Check that RStudio responds 200 HTTP status code indicating it's operational. +TEST_http_response() { + for ((i=1; i<=${REQUEST_RETRIES}; i++)); do + if [ $(curl -o /dev/null -s -w "%{http_code}" "http://localhost:${RSTUDIO_PORT}/unsupported_browser.htm") -eq 200 ]; then + return 0 + fi + sleep ${RETRY_DELAY} + done + + echo "FAILED: Did not receive 200 HTTP response from RStudio after ${REQUEST_RETRIES} attempts." + exit 1 +} + +TEST_install_package() { + # Install R package + docker exec "${CONTAINER_NAME}" \ + Rscript -e 'install.packages("ggplot2", repos = "https://cloud.r-project.org")' + + # Import library + docker exec "${CONTAINER_NAME}" \ + Rscript -e 'library("ggplot2")' +} + +TEST_validate_container_logs +TEST_http_response +TEST_install_package diff --git a/main.tf b/main.tf index f473e3150b..f27d00bcc7 100644 --- a/main.tf +++ b/main.tf @@ -1224,6 +1224,11 @@ module "rqlite" { target_repository = "${var.target_repository}/rqlite" } +module "rstudio" { + source = "./images/rstudio" + target_repository = "${var.target_repository}/rstudio" +} + module "ruby" { source = "./images/ruby" target_repository = "${var.target_repository}/ruby"