diff --git a/charts/rstudio-connect/.helmignore b/charts/rstudio-connect/.helmignore index 93f437e0a..2abf3ce44 100644 --- a/charts/rstudio-connect/.helmignore +++ b/charts/rstudio-connect/.helmignore @@ -27,3 +27,6 @@ ci/ lint/ tests/ + +# local dev +local-dev/ diff --git a/charts/rstudio-connect/local-dev/.gitignore b/charts/rstudio-connect/local-dev/.gitignore new file mode 100644 index 000000000..b5c5edac3 --- /dev/null +++ b/charts/rstudio-connect/local-dev/.gitignore @@ -0,0 +1,2 @@ +*.lic +**netrc** diff --git a/charts/rstudio-connect/local-dev/bootstrap.sh b/charts/rstudio-connect/local-dev/bootstrap.sh new file mode 100755 index 000000000..a939ea75b --- /dev/null +++ b/charts/rstudio-connect/local-dev/bootstrap.sh @@ -0,0 +1,142 @@ +#!/bin/bash + +set -euo pipefail + +# Colors for output +RED='\033[0;31m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Configuration +REPO_ROOT="$(git rev-parse --show-toplevel)" +DEV_DIR="${REPO_ROOT}/charts/rstudio-connect/local-dev" +CONNECT_NAMESPACE="connect-dev" +CONNECT_VERSION="$(yq '.versionOverride' "${DEV_DIR}/values.yaml")" + +info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +check_requirements() { + info "Checking requirements..." + + local required_tools=("docker" "k3d" "kubectl" "helm" "yq") + + for tool in "${required_tools[@]}"; do + if ! command -v "$tool" &> /dev/null; then + error "$tool is not installed. Please install $tool." + exit 1 + fi + done + + if ! docker ps &> /dev/null; then + error "Docker is not accessible. Please ensure Docker is running." + exit 1 + fi +} + +setup_k3d_cluster() { + info "Creating k3d cluster..." + k3d cluster create --config "${DEV_DIR}/k3d-config.yaml" || true + + local cluster_name + cluster_name="$(yq '.metadata.name' "${DEV_DIR}/k3d-config.yaml")" + + k3d image import \ + "ghcr.io/rstudio/rstudio-connect:ubuntu2204-${CONNECT_VERSION}" \ + "ghcr.io/rstudio/rstudio-connect-content-init:ubuntu2204-${CONNECT_VERSION}" \ + "ghcr.io/rstudio/content-base:r4.2.3-py3.11.9-ubuntu2204" \ + --cluster "${cluster_name}" + + info "Waiting for k3d cluster to be ready..." + kubectl wait --for=condition=Ready nodes --all --timeout=300s +} + +install_cnpg_operator() { + info "Installing CloudNativePG operator..." + + # TODO: use latest version dynamically + kubectl apply --server-side -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.26/releases/cnpg-1.26.0.yaml + + info "Waiting for CNPG operator to be ready..." + kubectl wait --for=condition=Available deployment/cnpg-controller-manager -n cnpg-system --timeout=300s + + info "CloudNativePG operator installed" +} + +setup_nfs_server() { + info "Setting up NFS server..." + + helm repo add nfs-ganesha-server-and-external-provisioner \ + https://kubernetes-sigs.github.io/nfs-ganesha-server-and-external-provisioner > /dev/null 2>&1 || true + + helm repo update nfs-ganesha-server-and-external-provisioner + helm upgrade --install dev-nfs \ + nfs-ganesha-server-and-external-provisioner/nfs-server-provisioner \ + --set persistence.enabled=true \ + --set persistence.size="1Gi" \ + --set storageClass.name="nfs" \ + --set storageClass.mountOptions="{vers=4}" \ + --namespace nfs-server \ + --create-namespace + + info "NFS server setup complete" +} + +setup_postgres() { + info "Setting up PostgreSQL cluster..." + + kubectl apply -f "${DEV_DIR}/postgres-cluster.yaml" + + local pg_cluster_name + pg_cluster_name="$(yq '.metadata.name' "${DEV_DIR}/postgres-cluster.yaml")" + + info "Waiting for PostgreSQL cluster to be ready..." + kubectl wait --for=condition=Ready "cluster/${pg_cluster_name}" --namespace ${CONNECT_NAMESPACE} --timeout=600s + + info "PostgreSQL cluster ready" +} + +install_connect() { + info "Installing Posit Connect..." + + kubectl create secret generic connect-license --from-file=connect.lic --namespace ${CONNECT_NAMESPACE} || true + kubectl apply -f "${DEV_DIR}/connect-pvc.yaml" + + pushd "${REPO_ROOT}/charts/rstudio-connect" > /dev/null + helm dependency build + popd > /dev/null + + helm upgrade --install connect "${REPO_ROOT}/charts/rstudio-connect" \ + --namespace ${CONNECT_NAMESPACE} \ + --values "${DEV_DIR}/values.yaml" \ + --wait \ + --timeout=600s + + info "Posit Connect installed" +} + +main() { + info "Starting Posit Connect development cluster setup..." + + check_requirements + setup_k3d_cluster + install_cnpg_operator + setup_nfs_server + kubectl create namespace ${CONNECT_NAMESPACE} || true + setup_postgres + install_connect + + info "Setup complete! Connect is available at http://localhost:3939" +} + +main diff --git a/charts/rstudio-connect/local-dev/connect-pvc.yaml b/charts/rstudio-connect/local-dev/connect-pvc.yaml new file mode 100644 index 000000000..f9fc2fe1a --- /dev/null +++ b/charts/rstudio-connect/local-dev/connect-pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: connect-pvc + namespace: connect-dev +spec: + accessModes: + - ReadWriteMany + storageClassName: nfs + resources: + requests: + storage: "5Gi" diff --git a/charts/rstudio-connect/local-dev/justfile b/charts/rstudio-connect/local-dev/justfile new file mode 100644 index 000000000..a9cd7a083 --- /dev/null +++ b/charts/rstudio-connect/local-dev/justfile @@ -0,0 +1,23 @@ +template: + helm dependency build .. + helm template .. --values ./values.yaml + +template-debug: + helm dependency build .. + helm template --debug .. --values ./values.yaml + +up: + ./bootstrap.sh + +down: + k3d cluster delete --config ./k3d-config.yaml + +update-connect: + #!/usr/bin/env bash + set -euo pipefail + + helm upgrade --install connect ../ \ + --namespace connect-dev \ + --values "{{ justfile_directory() }}/values.yaml" \ + --wait \ + --timeout=600s diff --git a/charts/rstudio-connect/local-dev/k3d-config.yaml b/charts/rstudio-connect/local-dev/k3d-config.yaml new file mode 100644 index 000000000..5bd9d4ed2 --- /dev/null +++ b/charts/rstudio-connect/local-dev/k3d-config.yaml @@ -0,0 +1,16 @@ +apiVersion: k3d.io/v1alpha5 +kind: Simple +metadata: + name: connect-dev +servers: 1 +agents: 0 +options: + k3s: + extraArgs: + - arg: --disable=traefik + nodeFilters: + - server:* +ports: + - port: 3939:30080 + nodeFilters: + - server:* diff --git a/charts/rstudio-connect/local-dev/postgres-cluster.yaml b/charts/rstudio-connect/local-dev/postgres-cluster.yaml new file mode 100644 index 000000000..ad0163438 --- /dev/null +++ b/charts/rstudio-connect/local-dev/postgres-cluster.yaml @@ -0,0 +1,8 @@ +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: connect-dev-pg + namespace: connect-dev +spec: + storage: + size: 20Gi diff --git a/charts/rstudio-connect/local-dev/values.yaml b/charts/rstudio-connect/local-dev/values.yaml new file mode 100644 index 000000000..f1763f3de --- /dev/null +++ b/charts/rstudio-connect/local-dev/values.yaml @@ -0,0 +1,74 @@ +versionOverride: "2025.07.0" + +launcher: + enabled: true + useTemplates: true # Required to set a custom PyPI repo + templateValues: + pod: + volumes: + - name: pip-config-volume + configMap: + name: pip-config-connect + - name: netrc-config-volume + secret: + secretName: connect-netrc + volumeMounts: + - mountPath: /etc/pip.conf + name: pip-config-volume + subPath: pip.conf + - mountPath: /etc/netrc + name: netrc-config-volume + subPath: netrc + env: + - name: NETRC + value: /etc/netrc + +sharedStorage: + create: false + mount: true + name: connect-pvc + storageClassName: nfs + +license: + file: + secret: connect-license + secretKey: connect.lic + +config: + Authentication: + Provider: password + InsecureDefaultUserAPIKey: true + BasicAuth: true + Database: + Provider: "Postgres" + Postgres: + URL: "postgres://app@connect-dev-pg-rw.connect-dev:5432/app" + Launcher: + Kubernetes: true + DataDirPVCName: connect-pvc + +service: + type: NodePort + nodePort: 30080 + +pod: + env: + - name: CONNECT_POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: connect-dev-pg-app + key: password + +# Required to set a custom PyPI repo +extraObjects: + - apiVersion: v1 + kind: ConfigMap + metadata: + name: pip-config-connect + data: + # TODO: if using Posit Package Manager, edit the below to match your package manager python repo URL + pip.conf: | + [global] + timeout = 60 + index-url = https://packagemanager.posit.co/pypi/latest/simple + trusted-host = packagemanager.posit.co