diff --git a/CLAUDE.md b/CLAUDE.md index ac0b07a6..575bdd3b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -48,7 +48,7 @@ charts// ### Documentation Generation - READMEs are auto-generated using helm-docs from `README.md.gotmpl` templates - Never edit `README.md` directly - edit the `.gotmpl` template -- CI auto-generates and commits docs back to PRs +- Run `just docs` locally before committing — CI will reject uncommitted changes ## Critical Workflow Requirements diff --git a/charts/rstudio-workbench/Chart.yaml b/charts/rstudio-workbench/Chart.yaml index 4888779c..9d46e019 100644 --- a/charts/rstudio-workbench/Chart.yaml +++ b/charts/rstudio-workbench/Chart.yaml @@ -1,6 +1,6 @@ name: rstudio-workbench description: Official Helm chart for Posit Workbench -version: 0.10.13 +version: 0.11.0 apiVersion: v2 appVersion: 2026.01.2 icon: diff --git a/charts/rstudio-workbench/NEWS.md b/charts/rstudio-workbench/NEWS.md index 90d9051f..a218377f 100644 --- a/charts/rstudio-workbench/NEWS.md +++ b/charts/rstudio-workbench/NEWS.md @@ -1,11 +1,20 @@ # Changelog +## 0.11.0 + +- **BREAKING**: Change default session image from `rstudio/r-session-complete` to `rstudio/workbench-session` and enable session component delivery via init containers by default. + Set `components.enabled: false` and `session.image.repository: rstudio/r-session-complete` to restore the previous behavior +- Add top-level `components` key for configuring session component init containers +- Add `components.positron` for updating the Positron IDE version independently + of a Workbench release. A default Positron version is already delivered by the + `workbench-session-init` container. Set `components.positron.version` to + override it with a specific version + ## 0.10.13 - Bump Workbench version to 2026.01.2 - ## 0.10.12 - Fix invalid JSON in default `positron-user-settings.json` diff --git a/charts/rstudio-workbench/README.md b/charts/rstudio-workbench/README.md index 5d0516a1..28950fc0 100644 --- a/charts/rstudio-workbench/README.md +++ b/charts/rstudio-workbench/README.md @@ -1,6 +1,6 @@ # Posit Workbench -![Version: 0.10.13](https://img.shields.io/badge/Version-0.10.13-informational?style=flat-square) ![AppVersion: 2026.01.2](https://img.shields.io/badge/AppVersion-2026.01.2-informational?style=flat-square) +![Version: 0.11.0](https://img.shields.io/badge/Version-0.11.0-informational?style=flat-square) ![AppVersion: 2026.01.2](https://img.shields.io/badge/AppVersion-2026.01.2-informational?style=flat-square) #### _Official Helm chart for Posit Workbench_ @@ -24,11 +24,11 @@ To ensure a stable production deployment: ## Installing the chart -To install the chart with the release name `my-release` at version 0.10.13: +To install the chart with the release name `my-release` at version 0.11.0: ```{.bash} helm repo add rstudio https://helm.rstudio.com -helm upgrade --install my-release rstudio/rstudio-workbench --version=0.10.13 +helm upgrade --install my-release rstudio/rstudio-workbench --version=0.11.0 ``` To explore other chart versions, look at: @@ -172,6 +172,35 @@ Alternatively, database passwords may be set during `helm install` with the foll `--set config.secret.'database\.conf'.password=""` +## Session images + +By default, session pods use the `rstudio/workbench-session` image with an init container +(`rstudio/workbench-session-init`) that delivers Workbench session components at pod startup. + +To use the classic all-in-one `r-session-complete` image instead (which bundles all components +into a single image), disable session components and change the session image: + +```yaml +session: + image: + repository: "rstudio/r-session-complete" +components: + enabled: false +``` + +### Positron + +To enable Positron IDE support, set a Positron version under `components.positron`: + +```yaml +components: + positron: + version: "2026.03.0" +``` + +This configures an init container that delivers Positron binaries and documentation to +session pods. The version determines both the image tag and the server-side mount path. + ## General principles - In most places, we opt to pass Helm values directly into ConfigMaps. We automatically translate these into the @@ -572,6 +601,13 @@ Use of [Sealed secrets](https://github.com/bitnami-labs/sealed-secrets) disables | chronicleAgent.workbenchApiKey.value | string | `""` | Workbench API key as a raw string to set as the `CHRONICLE_WORKBENCH_APIKEY` environment variable (not recommended) | | chronicleAgent.workbenchApiKey.valueFrom | object | `{}` | Workbench API key as a `valueFrom` reference (ex. a Kubernetes Secret reference) to set as the `CHRONICLE_WORKBENCH_APIKEY` environment variable (recommended) | | command | list | `[]` | command is the pod container's run command. By default, it uses the container's default. However, the chart expects a container using `supervisord` for startup | +| components | object | `{"enabled":true,"positron":{"image":{"repository":"rstudio/workbench-positron-init","tag":""},"version":""},"sessionInit":{"image":{"repository":"rstudio/workbench-session-init","tag":""}}}` | Session component delivery via init containers. When enabled (default), the chart configures rserver.conf so the launcher injects init containers into session pods at startup. Set `enabled: false` and change `session.image.repository` to `rstudio/r-session-complete` to use the classic all-in-one session image instead. | +| components.enabled | bool | `true` | Enable session component delivery via init containers. When false, no init containers are configured and session.image must be a self-contained image like r-session-complete. | +| components.positron.image.repository | string | `"rstudio/workbench-positron-init"` | The image repository for the Positron init container | +| components.positron.image.tag | string | `""` | A tag override for the Positron init container image. Defaults to the positron version | +| components.positron.version | string | `""` | A Positron version to enable the Positron init container for session pods. When set, configures rserver.conf with the Positron init container settings and attaches a Positron init container to the Workbench server pod. | +| components.sessionInit.image.repository | string | `"rstudio/workbench-session-init"` | The repository for the session init container image | +| components.sessionInit.image.tag | string | `""` | A tag override for the session init container. Default tag is the chart appVersion (or versionOverride) | | config.database | object | `{"conf":{"existingSecret":"","value":""}}` | a map of database connection config files. Mounted to `/mnt/secret-configmap/rstudio/database.conf` with 0600 permissions | | config.database.conf.existingSecret | string | `""` | Secret for database connection config. Will take precedence over `config.database.conf.value`. Key: 'database.conf' | | config.database.conf.value | string | `""` | Database connection config. Will only be used if `config.database.conf.existingSecret` is not set. | @@ -699,9 +735,9 @@ Use of [Sealed secrets](https://github.com/bitnami-labs/sealed-secrets) disables | session.defaultConfigMount | bool | `true` | Whether to automatically mount the config.session configuration into session pods. If launcher.namespace is different from Release Namespace, then the chart will duplicate the session configmap in both namespaces to facilitate this | | session.defaultHomeMount | bool | `true` | Whether to automatically add the homeStorage PVC to the session (i.e. via the `launcher-mounts` file) | | session.defaultSecretMountPath | string | `"/mnt/session-secret/"` | The path to mount the sessionSecret (from `config.sessionSecret`) onto the server and session pods | -| session.image.repository | string | `"rstudio/r-session-complete"` | The repository to use for the session image | +| session.image.repository | string | `"rstudio/workbench-session"` | The repository to use for the session image | | session.image.tag | string | `""` | A tag override for the session image. Overrides the "tagPrefix" above, if set. Default tag is `{{ tagPrefix }}{{ version }}` | -| session.image.tagPrefix | string | `"ubuntu2204-"` | A tag prefix for session images (common selections: ubuntu2204-, centos7-). Only used if tag is not defined | +| session.image.tagPrefix | string | `"ubuntu2204-"` | A tag prefix for session images (common selections: ubuntu2204-). Only used if tag is not defined | | shareProcessNamespace | bool | `false` | whether to provide `shareProcessNamespace` to the pod. | | sharedStorage.accessModes | list | `["ReadWriteMany"]` | accessModes defined for the storage PVC (represented as YAML) | | sharedStorage.annotations | object | `{"helm.sh/resource-policy":"keep"}` | Define the annotations for the Persistent Volume Claim resource | diff --git a/charts/rstudio-workbench/README.md.gotmpl b/charts/rstudio-workbench/README.md.gotmpl index ef29fd3c..f23ea7ef 100644 --- a/charts/rstudio-workbench/README.md.gotmpl +++ b/charts/rstudio-workbench/README.md.gotmpl @@ -118,6 +118,35 @@ Alternatively, database passwords may be set during `helm install` with the foll `--set config.secret.'database\.conf'.password=""` +## Session images + +By default, session pods use the `rstudio/workbench-session` image with an init container +(`rstudio/workbench-session-init`) that delivers Workbench session components at pod startup. + +To use the classic all-in-one `r-session-complete` image instead (which bundles all components +into a single image), disable session components and change the session image: + +```yaml +session: + image: + repository: "rstudio/r-session-complete" +components: + enabled: false +``` + +### Positron + +To enable Positron IDE support, set a Positron version under `components.positron`: + +```yaml +components: + positron: + version: "2026.03.0" +``` + +This configures an init container that delivers Positron binaries and documentation to +session pods. The version determines both the image tag and the server-side mount path. + ## General principles - In most places, we opt to pass Helm values directly into ConfigMaps. We automatically translate these into the diff --git a/charts/rstudio-workbench/templates/_helpers.tpl b/charts/rstudio-workbench/templates/_helpers.tpl index 5b785c0e..2574cb88 100644 --- a/charts/rstudio-workbench/templates/_helpers.tpl +++ b/charts/rstudio-workbench/templates/_helpers.tpl @@ -188,6 +188,10 @@ containers: mountPath: "/var/lib/rstudio-launcher/Kubernetes/service.tpl" subPath: "service.tpl" {{- end }} + {{- if and .Values.components.enabled .Values.components.positron.version }} + - name: positron-components + mountPath: {{ printf "/usr/lib/rstudio-server/bin/positron-server/%s" .Values.components.positron.version | quote }} + {{- end }} {{- if .Values.pod.volumeMounts }} {{- toYaml .Values.pod.volumeMounts | nindent 4 }} {{- end }} @@ -416,6 +420,10 @@ volumes: name: {{ include "rstudio-workbench.fullname" .}}-templates defaultMode: {{ .Values.config.defaultMode.server }} {{- end }} +{{- if and .Values.components.enabled .Values.components.positron.version }} +- name: positron-components + emptyDir: {} +{{- end }} {{- if .Values.pod.volumes }} {{ toYaml .Values.pod.volumes }} {{- end }} diff --git a/charts/rstudio-workbench/templates/configmap-general.yaml b/charts/rstudio-workbench/templates/configmap-general.yaml index fa736d57..a6ea20b1 100644 --- a/charts/rstudio-workbench/templates/configmap-general.yaml +++ b/charts/rstudio-workbench/templates/configmap-general.yaml @@ -49,6 +49,18 @@ {{- $defaultIDEServiceName := include "rstudio-workbench.fullname" . }} {{- $defaultIDEServiceURL := printf "http://%s.%s.svc.cluster.local:80" $defaultIDEServiceName $.Release.Namespace }} {{- $defaultRServerConfigValues := dict "launcher-sessions-callback-address" ($defaultIDEServiceURL) }} +{{- if and .Values.launcher.enabled .Values.components.enabled }} + {{- $initTag := .Values.components.sessionInit.image.tag | default $defaultVersion }} + {{- $_ := set $defaultRServerConfigValues "launcher-sessions-auto-update" 1 }} + {{- $_ := set $defaultRServerConfigValues "launcher-sessions-init-container-image-name" .Values.components.sessionInit.image.repository }} + {{- $_ := set $defaultRServerConfigValues "launcher-sessions-init-container-image-tag" $initTag }} + {{- if .Values.components.positron.version }} + {{- $positronTag := .Values.components.positron.image.tag | default .Values.components.positron.version }} + {{- $_ := set $defaultRServerConfigValues "launcher-positron-init-container-enabled" 1 }} + {{- $_ := set $defaultRServerConfigValues "launcher-positron-init-container-image-name" .Values.components.positron.image.repository }} + {{- $_ := set $defaultRServerConfigValues "launcher-positron-init-container-image-tag" $positronTag }} + {{- end }} +{{- end }} {{- $defaultRServerConfig := dict "rserver.conf" ($defaultRServerConfigValues) }} {{- $defaultLauncherK8sConfigValues := dict "kubernetes-namespace" (default $.Release.Namespace .Values.launcher.namespace) }} {{- if .Values.launcher.useTemplates }} diff --git a/charts/rstudio-workbench/templates/deployment.yaml b/charts/rstudio-workbench/templates/deployment.yaml index 4cf53751..d9da337d 100644 --- a/charts/rstudio-workbench/templates/deployment.yaml +++ b/charts/rstudio-workbench/templates/deployment.yaml @@ -101,11 +101,21 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} terminationGracePeriodSeconds: {{ .Values.pod.terminationGracePeriodSeconds }} - {{- if or .Values.initContainers .Values.chronicleAgent.enabled }} + {{- if or .Values.initContainers .Values.chronicleAgent.enabled (and .Values.components.enabled .Values.components.positron.version) }} initContainers: {{- with .Values.initContainers }} {{- toYaml . | nindent 8 }} {{- end }} + {{- if and .Values.components.enabled .Values.components.positron.version }} + - name: positron-init + image: "{{ .Values.components.positron.image.repository }}:{{ .Values.components.positron.image.tag | default .Values.components.positron.version }}" + env: + - name: PWB_POSITRON_TARGET + value: "positron,positron-docs" + volumeMounts: + - name: positron-components + mountPath: /mnt/init + {{- end }} {{- if .Values.chronicleAgent.enabled }} - name: chronicle-agent {{- if .Values.chronicleAgent.image.sha }} diff --git a/charts/rstudio-workbench/tests/configmap_test.yaml b/charts/rstudio-workbench/tests/configmap_test.yaml new file mode 100644 index 00000000..d29f3401 --- /dev/null +++ b/charts/rstudio-workbench/tests/configmap_test.yaml @@ -0,0 +1,190 @@ +suite: Workbench ConfigMap +templates: + - configmap-general.yaml + - configmap-prestart.yaml + - configmap-secret.yaml + - configmap-session.yaml +tests: + # -- Default behavior: components enabled + - it: should include session-init settings by default + template: configmap-general.yaml + documentIndex: 0 + asserts: + - matchRegex: + path: data["rserver.conf"] + pattern: "launcher-sessions-auto-update=1" + - matchRegex: + path: data["rserver.conf"] + pattern: "launcher-sessions-init-container-image-name=rstudio/workbench-session-init" + - matchRegex: + path: data["rserver.conf"] + pattern: "launcher-sessions-init-container-image-tag=\\d+\\.\\d+\\.\\d+" + + - it: should use workbench-session as default session image + template: configmap-general.yaml + documentIndex: 0 + asserts: + - matchRegex: + path: data["launcher.kubernetes.profiles.conf"] + pattern: "default-container-image=rstudio/workbench-session:" + + # -- Components disabled (classic mode) + - it: should not include session-init settings when components are disabled + template: configmap-general.yaml + documentIndex: 0 + set: + components: + enabled: false + asserts: + - notMatchRegex: + path: data["rserver.conf"] + pattern: "launcher-sessions-auto-update" + - notMatchRegex: + path: data["rserver.conf"] + pattern: "launcher-sessions-init-container" + + - it: should use r-session-complete when configured for classic mode + template: configmap-general.yaml + documentIndex: 0 + set: + session: + image: + repository: "rstudio/r-session-complete" + components: + enabled: false + asserts: + - matchRegex: + path: data["launcher.kubernetes.profiles.conf"] + pattern: "default-container-image=rstudio/r-session-complete:" + + # -- Custom session init container + - it: should use custom session init repository when overridden + template: configmap-general.yaml + documentIndex: 0 + set: + components: + sessionInit: + image: + repository: "custom-registry/session-init" + asserts: + - matchRegex: + path: data["rserver.conf"] + pattern: "launcher-sessions-init-container-image-name=custom-registry/session-init" + + - it: should use custom session init tag when overridden + template: configmap-general.yaml + documentIndex: 0 + set: + components: + sessionInit: + image: + tag: "jammy-2025.01.0" + asserts: + - matchRegex: + path: data["rserver.conf"] + pattern: "launcher-sessions-init-container-image-tag=jammy-2025.01.0" + + # -- versionOverride propagates to init container tag + - it: should use versionOverride for init container tag + template: configmap-general.yaml + documentIndex: 0 + set: + versionOverride: "2025.06.0" + asserts: + - matchRegex: + path: data["rserver.conf"] + pattern: "launcher-sessions-init-container-image-tag=2025.06.0" + + # -- Launcher disabled: components silently ignored + - it: should not include session-init settings when launcher is disabled + template: configmap-general.yaml + documentIndex: 0 + set: + launcher: + enabled: false + asserts: + - notMatchRegex: + path: data["rserver.conf"] + pattern: "launcher-sessions-init-container" + + # -- Positron: rserver.conf settings + - it: should include positron init container settings when version is set + template: configmap-general.yaml + documentIndex: 0 + set: + components: + positron: + version: "2026.03.0-213" + asserts: + - matchRegex: + path: data["rserver.conf"] + pattern: "launcher-positron-init-container-enabled=1" + - matchRegex: + path: data["rserver.conf"] + pattern: "launcher-positron-init-container-image-name=posit/workbench-positron-init" + - matchRegex: + path: data["rserver.conf"] + pattern: "launcher-positron-init-container-image-tag=2026.03.0-213" + + - it: should not include positron settings when version is empty + template: configmap-general.yaml + documentIndex: 0 + asserts: + - notMatchRegex: + path: data["rserver.conf"] + pattern: "launcher-positron-init-container" + + - it: should use custom positron image repository when overridden + template: configmap-general.yaml + documentIndex: 0 + set: + components: + positron: + version: "2026.03.0" + image: + repository: "my-registry/positron-init" + asserts: + - matchRegex: + path: data["rserver.conf"] + pattern: "launcher-positron-init-container-image-name=my-registry/positron-init" + + - it: should use custom positron image tag when overridden + template: configmap-general.yaml + documentIndex: 0 + set: + components: + positron: + version: "2026.03.0" + image: + tag: "custom-build-abc123" + asserts: + - matchRegex: + path: data["rserver.conf"] + pattern: "launcher-positron-init-container-image-tag=custom-build-abc123" + + - it: should not include positron settings when components are disabled + template: configmap-general.yaml + documentIndex: 0 + set: + components: + enabled: false + positron: + version: "2026.03.0" + asserts: + - notMatchRegex: + path: data["rserver.conf"] + pattern: "launcher-positron-init-container" + + - it: should not include positron settings when launcher is disabled + template: configmap-general.yaml + documentIndex: 0 + set: + launcher: + enabled: false + components: + positron: + version: "2026.03.0" + asserts: + - notMatchRegex: + path: data["rserver.conf"] + pattern: "launcher-positron-init-container" diff --git a/charts/rstudio-workbench/tests/deployment_test.yaml b/charts/rstudio-workbench/tests/deployment_test.yaml index 27cedfe3..84b76e7e 100644 --- a/charts/rstudio-workbench/tests/deployment_test.yaml +++ b/charts/rstudio-workbench/tests/deployment_test.yaml @@ -728,3 +728,108 @@ tests: asserts: - exists: path: 'spec.template.spec.containers[?(@.name=="sidecarTest")]' + + # -- Positron init container on server pod + - it: should add positron init container when version is set + template: deployment.yaml + set: + components: + positron: + version: "2026.03.0-213" + asserts: + - exists: + path: 'spec.template.spec.initContainers[?(@.name=="positron-init")]' + - equal: + path: 'spec.template.spec.initContainers[?(@.name=="positron-init")].image' + value: "posit/workbench-positron-init:2026.03.0-213" + - equal: + path: 'spec.template.spec.initContainers[?(@.name=="positron-init")].env[0].name' + value: "PWB_POSITRON_TARGET" + - equal: + path: 'spec.template.spec.initContainers[?(@.name=="positron-init")].env[0].value' + value: "positron,positron-docs" + + - it: should not add positron init container when version is empty + template: deployment.yaml + asserts: + - notExists: + path: 'spec.template.spec.initContainers[?(@.name=="positron-init")]' + + - it: should not add positron init container when components are disabled + template: deployment.yaml + set: + components: + enabled: false + positron: + version: "2026.03.0" + asserts: + - notExists: + path: 'spec.template.spec.initContainers[?(@.name=="positron-init")]' + + - it: should use custom positron image tag in init container + template: deployment.yaml + set: + components: + positron: + version: "2026.03.0" + image: + tag: "custom-build-abc123" + asserts: + - equal: + path: 'spec.template.spec.initContainers[?(@.name=="positron-init")].image' + value: "posit/workbench-positron-init:custom-build-abc123" + + - it: should use custom positron image repository in init container + template: deployment.yaml + set: + components: + positron: + version: "2026.03.0" + image: + repository: "my-registry/positron-init" + asserts: + - equal: + path: 'spec.template.spec.initContainers[?(@.name=="positron-init")].image' + value: "my-registry/positron-init:2026.03.0" + + - it: should add positron volume mount to server container + template: deployment.yaml + set: + components: + positron: + version: "2026.03.0" + asserts: + - contains: + path: 'spec.template.spec.containers[0].volumeMounts' + content: + name: "positron-components" + mountPath: "/usr/lib/rstudio-server/bin/positron-server/2026.03.0" + any: true + + - it: should add positron-components emptyDir volume + template: deployment.yaml + set: + components: + positron: + version: "2026.03.0" + asserts: + - contains: + path: 'spec.template.spec.volumes' + content: + name: "positron-components" + emptyDir: {} + any: true + + - it: should not add positron volume or volume mount when version is empty + template: deployment.yaml + asserts: + - notContains: + path: 'spec.template.spec.containers[0].volumeMounts' + content: + name: "positron-components" + any: true + - notContains: + path: 'spec.template.spec.volumes' + content: + name: "positron-components" + any: true diff --git a/charts/rstudio-workbench/values.yaml b/charts/rstudio-workbench/values.yaml index 29a040a5..3babd4e5 100644 --- a/charts/rstudio-workbench/values.yaml +++ b/charts/rstudio-workbench/values.yaml @@ -21,13 +21,38 @@ session: # -- The path to mount the sessionSecret (from `config.sessionSecret`) onto the server and session pods defaultSecretMountPath: /mnt/session-secret/ image: - # -- A tag prefix for session images (common selections: ubuntu2204-, centos7-). Only used if tag is not defined + # -- A tag prefix for session images (common selections: ubuntu2204-). Only used if tag is not defined tagPrefix: ubuntu2204- # -- The repository to use for the session image - repository: "rstudio/r-session-complete" + repository: "rstudio/workbench-session" # -- A tag override for the session image. Overrides the "tagPrefix" above, if set. Default tag is `{{ tagPrefix }}{{ version }}` tag: "" +# -- Session component delivery via init containers. When enabled (default), the chart +# configures rserver.conf so the launcher injects init containers into session pods at startup. +# Set `enabled: false` and change `session.image.repository` to `rstudio/r-session-complete` +# to use the classic all-in-one session image instead. +components: + # -- Enable session component delivery via init containers. When false, no init containers + # are configured and session.image must be a self-contained image like r-session-complete. + enabled: true + sessionInit: + image: + # -- The repository for the session init container image + repository: "rstudio/workbench-session-init" + # -- A tag override for the session init container. Default tag is the chart appVersion (or versionOverride) + tag: "" + positron: + # -- A Positron version to enable the Positron init container for session pods. + # When set, configures rserver.conf with the Positron init container settings + # and attaches a Positron init container to the Workbench server pod. + version: "" + image: + # -- The image repository for the Positron init container + repository: "rstudio/workbench-positron-init" + # -- A tag override for the Positron init container image. Defaults to the positron version + tag: "" + sharedStorage: # -- whether to create the persistentVolumeClaim for shared storage create: false