diff --git a/charts/rstudio-connect/Chart.yaml b/charts/rstudio-connect/Chart.yaml index c350945c..036b9bae 100644 --- a/charts/rstudio-connect/Chart.yaml +++ b/charts/rstudio-connect/Chart.yaml @@ -1,6 +1,6 @@ name: rstudio-connect description: Official Helm chart for Posit Connect -version: 0.8.34 +version: 0.8.35 apiVersion: v2 appVersion: 2026.03.0 icon: https://raw.githubusercontent.com/rstudio/helm/main/images/posit-icon-fullcolor.svg diff --git a/charts/rstudio-connect/NEWS.md b/charts/rstudio-connect/NEWS.md index 510b9c74..85761f59 100644 --- a/charts/rstudio-connect/NEWS.md +++ b/charts/rstudio-connect/NEWS.md @@ -1,5 +1,9 @@ # Changelog +## 0.8.35 + +- Add `executionEnvironments` value for [declarative management of execution environments](https://docs.posit.co/connect/admin/appendix/off-host/execution-environments/#declarative-management). Unlike `launcher.customRuntimeYaml`, changes take effect on every `helm upgrade` without requiring a pod restart or database reset. Requires Connect version 2026.03.0 or later. + ## 0.8.34 - Bump Connect version to 2026.03.0 diff --git a/charts/rstudio-connect/README.md b/charts/rstudio-connect/README.md index d92353e6..af4b1962 100644 --- a/charts/rstudio-connect/README.md +++ b/charts/rstudio-connect/README.md @@ -1,6 +1,6 @@ # Posit Connect -![Version: 0.8.34](https://img.shields.io/badge/Version-0.8.34-informational?style=flat-square) ![AppVersion: 2026.03.0](https://img.shields.io/badge/AppVersion-2026.03.0-informational?style=flat-square) +![Version: 0.8.35](https://img.shields.io/badge/Version-0.8.35-informational?style=flat-square) ![AppVersion: 2026.03.0](https://img.shields.io/badge/AppVersion-2026.03.0-informational?style=flat-square) #### _Official Helm chart for Posit Connect_ @@ -30,11 +30,11 @@ To ensure reproducibility in your environment and insulate yourself from future ## Installing the chart -To install the chart with the release name `my-release` at version 0.8.34: +To install the chart with the release name `my-release` at version 0.8.35: ```{.bash} helm repo add rstudio https://helm.rstudio.com -helm upgrade --install my-release rstudio/rstudio-connect --version=0.8.34 +helm upgrade --install my-release rstudio/rstudio-connect --version=0.8.35 ``` To explore other chart versions, look at: @@ -191,6 +191,46 @@ the API key unset for the Chronicle agent, deploy the chart, create an administr secret with the API key. Once the secret is created, the value of `chronicleAgent.connectApiKey.secretKeyRef` can be set and the release can be upgraded to include the new value. +## Execution environments + +This chart supports [declarative management of execution environments](https://docs.posit.co/connect/admin/appendix/off-host/execution-environments/#declarative-management) +via `ExecutionEnvironments.ConfigFilePath`. Requires Connect version 2026.03.0 or later. +Unlike the legacy `launcher.customRuntimeYaml`, changes to `executionEnvironments` +take effect on every `helm upgrade` without requiring a pod restart or database reset. + +When `executionEnvironments` is set, the chart renders the list into a dedicated +ConfigMap and mounts it into the Connect pod. Connect only manages the execution +environments defined in this file. You can still create and manage additional +execution environments separately through the Connect UI or API. + +By default, the chart sets `ExecutionEnvironments.ConfigFilePath` to +`/etc/rstudio-connect/execution-environments/environments.yaml` and mounts the +ConfigMap at `/etc/rstudio-connect/execution-environments/`. If you set +`config.ExecutionEnvironments.ConfigFilePath` to a custom path, the chart uses +that path instead and mounts the ConfigMap at its parent directory. + +The chart deliberately excludes this ConfigMap from the pod's checksum annotations, +so changes do not trigger a pod restart. The kubelet updates the mounted file +automatically when the ConfigMap changes (typically within 60-120 seconds), and +Connect detects the update automatically. + +Example `values.yaml`: + +```yaml +executionEnvironments: + - name: ghcr.io/my-org/connect-runtime:ubuntu22 + title: "Default Runtime" + matching: any + python: + installations: + - version: "3.11.3" + path: /opt/python/3.11.3/bin/python3 + r: + installations: + - version: "4.4.0" + path: /opt/R/4.4.0/bin/R +``` + ## General principles - In most places, we opt to pass Helm values over configmaps. We translate these into the valid `.gcfg` file format @@ -235,6 +275,7 @@ The Helm `config` values are converted into the `rstudio-connect.gcfg` service c | command | list | `[]` | The pod's run command. By default, it uses the container's default | | config | object | [Posit Connect Configuration Reference](https://docs.posit.co/connect/admin/appendix/off-host/helm-reference/) | A nested map of maps that generates the rstudio-connect.gcfg file | | deployment.annotations | object | `{}` | Additional annotations to add to the rstudio-connect deployment | +| executionEnvironments | list | `[]` (disabled) | Optional list of execution environments to manage declaratively. Requires Connect version 2026.03.0 or later. When set, the chart renders these into a ConfigMap, mounts it into the Connect pod, and sets ExecutionEnvironments.ConfigFilePath in the Connect configuration. Unlike launcher.customRuntimeYaml, changes take effect on every helm upgrade without requiring a pod restart or database reset. | | extraObjects | list | `[]` | Extra objects to deploy (value evaluated as a template) | | fullnameOverride | string | `""` | The full name of the release (can be overridden) | | image | object | `{"imagePullPolicy":"IfNotPresent","imagePullSecrets":[],"repository":"ghcr.io/rstudio/rstudio-connect","tag":"","tagPrefix":"ubuntu2204-"}` | Defines the Posit Connect image to deploy | diff --git a/charts/rstudio-connect/README.md.gotmpl b/charts/rstudio-connect/README.md.gotmpl index 7486a5db..0c3e7458 100644 --- a/charts/rstudio-connect/README.md.gotmpl +++ b/charts/rstudio-connect/README.md.gotmpl @@ -131,6 +131,46 @@ the API key unset for the Chronicle agent, deploy the chart, create an administr secret with the API key. Once the secret is created, the value of `chronicleAgent.connectApiKey.secretKeyRef` can be set and the release can be upgraded to include the new value. +## Execution environments + +This chart supports [declarative management of execution environments](https://docs.posit.co/connect/admin/appendix/off-host/execution-environments/#declarative-management) +via `ExecutionEnvironments.ConfigFilePath`. Requires Connect version 2026.03.0 or later. +Unlike the legacy `launcher.customRuntimeYaml`, changes to `executionEnvironments` +take effect on every `helm upgrade` without requiring a pod restart or database reset. + +When `executionEnvironments` is set, the chart renders the list into a dedicated +ConfigMap and mounts it into the Connect pod. Connect only manages the execution +environments defined in this file. You can still create and manage additional +execution environments separately through the Connect UI or API. + +By default, the chart sets `ExecutionEnvironments.ConfigFilePath` to +`/etc/rstudio-connect/execution-environments/environments.yaml` and mounts the +ConfigMap at `/etc/rstudio-connect/execution-environments/`. If you set +`config.ExecutionEnvironments.ConfigFilePath` to a custom path, the chart uses +that path instead and mounts the ConfigMap at its parent directory. + +The chart deliberately excludes this ConfigMap from the pod's checksum annotations, +so changes do not trigger a pod restart. The kubelet updates the mounted file +automatically when the ConfigMap changes (typically within 60-120 seconds), and +Connect detects the update automatically. + +Example `values.yaml`: + +```yaml +executionEnvironments: + - name: ghcr.io/my-org/connect-runtime:ubuntu22 + title: "Default Runtime" + matching: any + python: + installations: + - version: "3.11.3" + path: /opt/python/3.11.3/bin/python3 + r: + installations: + - version: "4.4.0" + path: /opt/R/4.4.0/bin/R +``` + ## General principles - In most places, we opt to pass Helm values over configmaps. We translate these into the valid `.gcfg` file format diff --git a/charts/rstudio-connect/templates/_helpers.tpl b/charts/rstudio-connect/templates/_helpers.tpl index 9f85659f..00b6b327 100644 --- a/charts/rstudio-connect/templates/_helpers.tpl +++ b/charts/rstudio-connect/templates/_helpers.tpl @@ -76,6 +76,11 @@ app.kubernetes.io/instance: {{ .Release.Name }} {{- $launcherDict := dict "Launcher" ( $launcherSettingsDict ) }} {{- $defaultConfig = merge $defaultConfig $launcherDict }} {{- end }} + {{- /* declarative execution environments configuration */}} + {{- if .Values.executionEnvironments }} + {{- $eeDict := dict "ExecutionEnvironments" (dict "ConfigFilePath" "/etc/rstudio-connect/execution-environments/environments.yaml") }} + {{- $defaultConfig = merge $defaultConfig $eeDict }} + {{- end }} {{- /* default licensing configuration */}} {{- if .Values.license.server }} {{- $licenseDict := dict "Licensing" ( dict "LicenseType" ("Remote") ) }} diff --git a/charts/rstudio-connect/templates/configmap-execution-environments.yaml b/charts/rstudio-connect/templates/configmap-execution-environments.yaml new file mode 100644 index 00000000..ef74d4ef --- /dev/null +++ b/charts/rstudio-connect/templates/configmap-execution-environments.yaml @@ -0,0 +1,18 @@ +--- +{{- if .Values.executionEnvironments }} +{{- range $i, $env := .Values.executionEnvironments }} +{{- if not $env.name }} +{{- fail (printf "executionEnvironments[%d].name is required" $i) }} +{{- end }} +{{- end }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "rstudio-connect.fullname" . }}-execution-environments + namespace: {{ $.Release.Namespace }} + labels: + {{- include "rstudio-connect.labels" . | nindent 4 }} +data: + environments.yaml: | + {{- toYaml .Values.executionEnvironments | nindent 4 }} +{{- end }} diff --git a/charts/rstudio-connect/templates/deployment.yaml b/charts/rstudio-connect/templates/deployment.yaml index 0e6341ec..a3a09eab 100644 --- a/charts/rstudio-connect/templates/deployment.yaml +++ b/charts/rstudio-connect/templates/deployment.yaml @@ -229,6 +229,11 @@ spec: subPath: "libnss_connect.conf" readOnly: true {{- end }} + {{- if .Values.executionEnvironments }} + - name: execution-environments + mountPath: {{ dir (default "/etc/rstudio-connect/execution-environments/environments.yaml" (dig "ExecutionEnvironments" "ConfigFilePath" "" .Values.config)) | quote }} + readOnly: true + {{- end }} {{- if .Values.pod.volumeMounts }} {{- toYaml .Values.pod.volumeMounts | nindent 10 }} {{- end }} @@ -308,6 +313,11 @@ spec: - key: libnss_connect.conf path: libnss_connect.conf {{- end }} + {{- if .Values.executionEnvironments }} + - name: execution-environments + configMap: + name: {{ include "rstudio-connect.fullname" . }}-execution-environments + {{- end }} {{- if .Values.launcher.enabled }} {{- if .Values.launcher.useTemplates }} - name: rstudio-connect-templates diff --git a/charts/rstudio-connect/tests/execution_environments_test.yaml b/charts/rstudio-connect/tests/execution_environments_test.yaml new file mode 100644 index 00000000..b32cd475 --- /dev/null +++ b/charts/rstudio-connect/tests/execution_environments_test.yaml @@ -0,0 +1,272 @@ +suite: Connect Execution Environments +templates: + - configmap-execution-environments.yaml + - deployment.yaml + - configmap.yaml + - configmap-prestart.yaml +tests: + # ============================================================================= + # configmap-execution-environments.yaml + # ============================================================================= + + - it: should not create ConfigMap when executionEnvironments is empty (default) + template: configmap-execution-environments.yaml + asserts: + - hasDocuments: + count: 0 + + - it: should create ConfigMap with correct content when executionEnvironments is set + template: configmap-execution-environments.yaml + set: + executionEnvironments: + - name: ghcr.io/rstudio/content-base:r4.4.0-py3.11.3-jammy + title: "Test Runtime" + matching: any + python: + installations: + - version: "3.11.3" + path: /opt/python/3.11.3/bin/python3 + r: + installations: + - version: "4.4.0" + path: /opt/R/4.4.0/bin/R + asserts: + - hasDocuments: + count: 1 + - isKind: + of: ConfigMap + - equal: + path: metadata.name + value: RELEASE-NAME-rstudio-connect-execution-environments + - equal: + path: metadata.labels["app.kubernetes.io/name"] + value: rstudio-connect + - equal: + path: metadata.labels["app.kubernetes.io/instance"] + value: RELEASE-NAME + - exists: + path: data["environments.yaml"] + + # ============================================================================= + # deployment.yaml - volumeMount + # ============================================================================= + + - it: should not have execution-environments volumeMount when executionEnvironments is empty + template: deployment.yaml + set: + launcher: + enabled: true + sharedStorage: + create: true + mount: true + asserts: + - notContains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: execution-environments + mountPath: "/etc/rstudio-connect/execution-environments" + readOnly: true + + - it: should have execution-environments volumeMount when executionEnvironments is set + template: deployment.yaml + set: + launcher: + enabled: true + sharedStorage: + create: true + mount: true + executionEnvironments: + - name: ghcr.io/rstudio/content-base:r4.4.0-py3.11.3-jammy + title: "Test Runtime" + matching: any + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: execution-environments + mountPath: "/etc/rstudio-connect/execution-environments" + readOnly: true + + # ============================================================================= + # deployment.yaml - volume + # ============================================================================= + + - it: should not have execution-environments volume when executionEnvironments is empty + template: deployment.yaml + set: + launcher: + enabled: true + sharedStorage: + create: true + mount: true + asserts: + - notContains: + path: spec.template.spec.volumes + content: + name: execution-environments + configMap: + name: RELEASE-NAME-rstudio-connect-execution-environments + + - it: should have execution-environments volume when executionEnvironments is set + template: deployment.yaml + set: + launcher: + enabled: true + sharedStorage: + create: true + mount: true + executionEnvironments: + - name: ghcr.io/rstudio/content-base:r4.4.0-py3.11.3-jammy + title: "Test Runtime" + matching: any + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: execution-environments + configMap: + name: RELEASE-NAME-rstudio-connect-execution-environments + + # ============================================================================= + # configmap.yaml - gcfg config injection + # ============================================================================= + + - it: should not include ExecutionEnvironments in gcfg when executionEnvironments is empty + template: configmap.yaml + documentIndex: 0 + asserts: + - notMatchRegex: + path: data["rstudio-connect.gcfg"] + pattern: "ExecutionEnvironments" + + - it: should include ExecutionEnvironments.ConfigFilePath in gcfg when executionEnvironments is set + template: configmap.yaml + documentIndex: 0 + set: + launcher: + enabled: false + executionEnvironments: + - name: ghcr.io/rstudio/content-base:r4.4.0-py3.11.3-jammy + title: "Test Runtime" + matching: any + asserts: + - matchRegex: + path: data["rstudio-connect.gcfg"] + pattern: "\\[ExecutionEnvironments\\]" + - matchRegex: + path: data["rstudio-connect.gcfg"] + pattern: "ConfigFilePath = /etc/rstudio-connect/execution-environments/environments.yaml" + + # ============================================================================= + # deployment.yaml - no checksum annotation for execution-environments ConfigMap + # ============================================================================= + + - it: should not have a checksum annotation for execution-environments ConfigMap + template: deployment.yaml + set: + launcher: + enabled: true + sharedStorage: + create: true + mount: true + executionEnvironments: + - name: ghcr.io/rstudio/content-base:r4.4.0-py3.11.3-jammy + title: "Test Runtime" + matching: any + asserts: + - notExists: + path: spec.template.metadata.annotations["checksum/config-execution-environments"] + + # ============================================================================= + # gcfg injection works alongside launcher configuration + # ============================================================================= + + - it: should include ExecutionEnvironments.ConfigFilePath alongside Launcher config when both are enabled + template: configmap.yaml + documentIndex: 0 + set: + launcher: + enabled: true + customRuntimeYaml: "pro" + sharedStorage: + create: true + mount: true + executionEnvironments: + - name: ghcr.io/rstudio/content-base:r4.4.0-py3.11.3-jammy + title: "Test Runtime" + matching: any + asserts: + - matchRegex: + path: data["rstudio-connect.gcfg"] + pattern: "\\[ExecutionEnvironments\\]" + - matchRegex: + path: data["rstudio-connect.gcfg"] + pattern: "ConfigFilePath = /etc/rstudio-connect/execution-environments/environments.yaml" + - matchRegex: + path: data["rstudio-connect.gcfg"] + pattern: "\\[Launcher\\]" + + # ============================================================================= + # User can override ExecutionEnvironments.ConfigFilePath via config + # ============================================================================= + + - it: should allow user to override ConfigFilePath via config + template: configmap.yaml + documentIndex: 0 + set: + launcher: + enabled: false + executionEnvironments: + - name: ghcr.io/rstudio/content-base:r4.4.0-py3.11.3-jammy + title: "Test Runtime" + matching: any + config: + ExecutionEnvironments: + ConfigFilePath: /custom/path/environments.yaml + asserts: + - matchRegex: + path: data["rstudio-connect.gcfg"] + pattern: "ConfigFilePath = /custom/path/environments.yaml" + + - it: should mount at custom directory when ConfigFilePath is overridden + template: deployment.yaml + set: + launcher: + enabled: true + sharedStorage: + create: true + mount: true + executionEnvironments: + - name: ghcr.io/rstudio/content-base:r4.4.0-py3.11.3-jammy + title: "Test Runtime" + matching: any + config: + ExecutionEnvironments: + ConfigFilePath: /custom/path/environments.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: execution-environments + mountPath: "/custom/path" + readOnly: true + - notContains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: execution-environments + mountPath: "/etc/rstudio-connect/execution-environments" + readOnly: true + + # ============================================================================= + # Validation + # ============================================================================= + + - it: should fail when an execution environment is missing a name + template: configmap-execution-environments.yaml + set: + executionEnvironments: + - title: "Missing Name" + matching: any + asserts: + - failedTemplate: + errorMessage: "executionEnvironments[0].name is required" diff --git a/charts/rstudio-connect/values.yaml b/charts/rstudio-connect/values.yaml index 0faef048..d50b7497 100644 --- a/charts/rstudio-connect/values.yaml +++ b/charts/rstudio-connect/values.yaml @@ -408,6 +408,36 @@ launcher: # -- The securityContext for the default initContainer securityContext: {} +# -- Optional list of execution environments to manage declaratively. +# Requires Connect version 2026.03.0 or later. +# When set, the chart renders these into a ConfigMap, mounts it into the Connect pod, +# and sets ExecutionEnvironments.ConfigFilePath in the Connect configuration. +# Unlike launcher.customRuntimeYaml, changes take effect on every helm upgrade +# without requiring a pod restart or database reset. +# @default -- `[]` (disabled) +executionEnvironments: [] + # - name: ghcr.io/my-org/connect-runtime:ubuntu22 + # title: "Default Runtime" + # description: "Runtime with R and Python" + # matching: any + # supervisor: "" + # python: + # installations: + # - version: "3.11.3" + # path: /opt/python/3.11.3/bin/python3 + # r: + # installations: + # - version: "4.4.0" + # path: /opt/R/4.4.0/bin/R + # quarto: + # installations: + # - version: "1.4.557" + # path: /opt/quarto/1.4.557/bin/quarto + # tensorflow: + # installations: + # - version: "2.17.1" + # path: /usr/bin/tensorflow_model_server + # -- Nameservice configuration for current user execution (RunAsCurrentUser). # This can only be enabled if using an SSO authentication provider (OAuth2, SAML, or LDAP). nameservice: