From 0ca266b4d0574133419151a4e141f51e044d2d59 Mon Sep 17 00:00:00 2001 From: kacey Date: Thu, 2 Apr 2026 19:07:04 -0700 Subject: [PATCH 01/49] feat: enable extraContainers --- helm/fiftyone-teams-app/templates/api-deployment.yaml | 3 +++ helm/fiftyone-teams-app/templates/app-deployment.yaml | 3 +++ .../templates/delegated-operator-instance-deployment.yaml | 3 +++ helm/fiftyone-teams-app/templates/plugins-deployment.yaml | 3 +++ 4 files changed, 12 insertions(+) diff --git a/helm/fiftyone-teams-app/templates/api-deployment.yaml b/helm/fiftyone-teams-app/templates/api-deployment.yaml index a0623130d..2db321500 100644 --- a/helm/fiftyone-teams-app/templates/api-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/api-deployment.yaml @@ -89,6 +89,9 @@ spec: {{- with .Values.apiSettings.volumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} + {{- with .Values.apiSettings.extraContainers }} + {{- toYaml . | nindent 8 }} + {{- end }} {{- if .Values.apiSettings.initContainers.enabled }} initContainers: {{- diff --git a/helm/fiftyone-teams-app/templates/app-deployment.yaml b/helm/fiftyone-teams-app/templates/app-deployment.yaml index 54d3309fe..9c2f776dc 100644 --- a/helm/fiftyone-teams-app/templates/app-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/app-deployment.yaml @@ -73,6 +73,9 @@ spec: volumeMounts: {{- toYaml . | nindent 12 }} {{- end }} + {{- with .Values.appSettings.extraContainers }} + {{- toYaml . | nindent 8 }} + {{- end }} {{- if .Values.appSettings.initContainers.enabled }} initContainers: {{- diff --git a/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml b/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml index ca93b52ce..67c7da4f6 100644 --- a/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml @@ -79,6 +79,9 @@ spec: volumeMounts: {{- toYaml . | nindent 12 }} {{- end }} + {{- with $v.extraContainers | default $baseTpl.extraContainers }} + {{- toYaml . | nindent 8 }} + {{- end }} livenessProbe: exec: command: diff --git a/helm/fiftyone-teams-app/templates/plugins-deployment.yaml b/helm/fiftyone-teams-app/templates/plugins-deployment.yaml index 85ef25c92..ecc0cce72 100644 --- a/helm/fiftyone-teams-app/templates/plugins-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/plugins-deployment.yaml @@ -74,6 +74,9 @@ spec: volumeMounts: {{- toYaml . | nindent 12 }} {{- end }} + {{- with .Values.pluginsSettings.extraContainers }} + {{- toYaml . | nindent 8 }} + {{- end }} {{- if .Values.pluginsSettings.initContainers.enabled }} initContainers: {{- From 2151e2e70c40557acbdae99829636c63296ff0a3 Mon Sep 17 00:00:00 2001 From: kacey Date: Fri, 3 Apr 2026 06:41:39 -0700 Subject: [PATCH 02/49] fix: share process namespace --- helm/fiftyone-teams-app/templates/api-deployment.yaml | 3 +++ helm/fiftyone-teams-app/templates/app-deployment.yaml | 3 +++ .../delegated-operator-instance-deployment.yaml | 9 ++++++--- .../fiftyone-teams-app/templates/plugins-deployment.yaml | 3 +++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/helm/fiftyone-teams-app/templates/api-deployment.yaml b/helm/fiftyone-teams-app/templates/api-deployment.yaml index 2db321500..2b0311753 100644 --- a/helm/fiftyone-teams-app/templates/api-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/api-deployment.yaml @@ -44,6 +44,9 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ $serviceAccountName }} + {{- if .Values.apiSettings.extraContainers }} + shareProcessNamespace: true + {{- end }} securityContext: {{- toYaml .Values.apiSettings.podSecurityContext | nindent 8 }} containers: diff --git a/helm/fiftyone-teams-app/templates/app-deployment.yaml b/helm/fiftyone-teams-app/templates/app-deployment.yaml index 9c2f776dc..4f18006d2 100644 --- a/helm/fiftyone-teams-app/templates/app-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/app-deployment.yaml @@ -35,6 +35,9 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" . }} + {{- if .Values.appSettings.extraContainers }} + shareProcessNamespace: true + {{- end }} securityContext: {{- toYaml .Values.appSettings.podSecurityContext | nindent 8 }} containers: diff --git a/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml b/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml index 67c7da4f6..1765e87c4 100644 --- a/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml @@ -50,6 +50,9 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" $ }} + {{- if ($v.extraContainers | default $baseTpl.extraContainers) }} + shareProcessNamespace: true + {{- end }} securityContext: {{- toYaml (merge (dict) ($v.podSecurityContext | default dict) ($baseTpl.podSecurityContext)) | nindent 8 }} containers: @@ -79,9 +82,6 @@ spec: volumeMounts: {{- toYaml . | nindent 12 }} {{- end }} - {{- with $v.extraContainers | default $baseTpl.extraContainers }} - {{- toYaml . | nindent 8 }} - {{- end }} livenessProbe: exec: command: @@ -109,6 +109,9 @@ spec: failureThreshold: {{ ($v.startup).failureThreshold | default $baseTpl.startup.failureThreshold }} periodSeconds: {{ ($v.startup).periodSeconds | default $baseTpl.startup.periodSeconds }} timeoutSeconds: {{ ($v.startup).timeoutSeconds | default $baseTpl.startup.timeoutSeconds }} + {{- with $v.extraContainers | default $baseTpl.extraContainers }} + {{- toYaml . | nindent 8 }} + {{- end }} {{- with (merge (dict) ($v.nodeSelector | default dict) ($baseTpl.nodeSelector)) }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/helm/fiftyone-teams-app/templates/plugins-deployment.yaml b/helm/fiftyone-teams-app/templates/plugins-deployment.yaml index ecc0cce72..9c3ca49ba 100644 --- a/helm/fiftyone-teams-app/templates/plugins-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/plugins-deployment.yaml @@ -36,6 +36,9 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" . }} + {{- if .Values.pluginsSettings.extraContainers }} + shareProcessNamespace: true + {{- end }} securityContext: {{- toYaml .Values.pluginsSettings.podSecurityContext | nindent 8 }} containers: From a46b8f05caceb51560e1196e4f8afde1198ff326 Mon Sep 17 00:00:00 2001 From: kacey Date: Fri, 3 Apr 2026 15:11:59 -0700 Subject: [PATCH 03/49] fix: add logs access --- .../templates/api-role.yaml | 3 ++ .../templates/telemetry-rbac.yaml | 36 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 helm/fiftyone-teams-app/templates/telemetry-rbac.yaml diff --git a/helm/fiftyone-teams-app/templates/api-role.yaml b/helm/fiftyone-teams-app/templates/api-role.yaml index def64ff6d..fe5bd00b6 100644 --- a/helm/fiftyone-teams-app/templates/api-role.yaml +++ b/helm/fiftyone-teams-app/templates/api-role.yaml @@ -17,4 +17,7 @@ rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch", "delete"] + - apiGroups: [""] + resources: ["pods/log"] + verbs: ["get"] {{- end }} diff --git a/helm/fiftyone-teams-app/templates/telemetry-rbac.yaml b/helm/fiftyone-teams-app/templates/telemetry-rbac.yaml new file mode 100644 index 000000000..71e18f155 --- /dev/null +++ b/helm/fiftyone-teams-app/templates/telemetry-rbac.yaml @@ -0,0 +1,36 @@ +{{- if .Values.telemetry }} +{{- if .Values.telemetry.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "fiftyone-teams-app.fullname" . }}-telemetry-logs + namespace: {{ .Values.namespace.name }} + labels: + {{- include "fiftyone-teams-app.commonLabels" . | nindent 4 }} +rules: + - apiGroups: [""] + resources: ["pods/log"] + verbs: ["get"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "fiftyone-teams-app.fullname" . }}-telemetry-logs + namespace: {{ .Values.namespace.name }} + labels: + {{- include "fiftyone-teams-app.commonLabels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "fiftyone-teams-app.fullname" . }}-telemetry-logs +subjects: + - kind: ServiceAccount + name: {{ include "fiftyone-teams-app.serviceAccountName" . }} + namespace: {{ .Values.namespace.name }} + {{- if .Values.apiSettings.rbac.create }} + - kind: ServiceAccount + name: {{ include "teams-api-rbac.service-account-name" . }} + namespace: {{ .Values.namespace.name }} + {{- end }} +{{- end }} +{{- end }} From 93359dcb193d60246242115fd65feb078ae6b1b3 Mon Sep 17 00:00:00 2001 From: kacey Date: Fri, 3 Apr 2026 15:55:45 -0700 Subject: [PATCH 04/49] chore: update tests --- tests/unit/helm/api-role_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/unit/helm/api-role_test.go b/tests/unit/helm/api-role_test.go index 13e1aeb8d..2b9fdc770 100644 --- a/tests/unit/helm/api-role_test.go +++ b/tests/unit/helm/api-role_test.go @@ -305,6 +305,11 @@ func (s *apiRoleTemplateTest) TestRules() { "apiGroups": [""], "resources": ["pods"], "verbs": ["get", "list", "watch", "delete"] + }, + { + "apiGroups": [""], + "resources": ["pods/log"], + "verbs": ["get"] } ]` var expectedRules []rbacv1.PolicyRule From 4c2865e03f2ce522fe003a235f217f9b0f273553 Mon Sep 17 00:00:00 2001 From: kacey Date: Sun, 5 Apr 2026 10:33:00 -0700 Subject: [PATCH 05/49] fix: rm unread rbac file --- .../templates/telemetry-rbac.yaml | 36 ------------------- 1 file changed, 36 deletions(-) delete mode 100644 helm/fiftyone-teams-app/templates/telemetry-rbac.yaml diff --git a/helm/fiftyone-teams-app/templates/telemetry-rbac.yaml b/helm/fiftyone-teams-app/templates/telemetry-rbac.yaml deleted file mode 100644 index 71e18f155..000000000 --- a/helm/fiftyone-teams-app/templates/telemetry-rbac.yaml +++ /dev/null @@ -1,36 +0,0 @@ -{{- if .Values.telemetry }} -{{- if .Values.telemetry.enabled }} -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: {{ include "fiftyone-teams-app.fullname" . }}-telemetry-logs - namespace: {{ .Values.namespace.name }} - labels: - {{- include "fiftyone-teams-app.commonLabels" . | nindent 4 }} -rules: - - apiGroups: [""] - resources: ["pods/log"] - verbs: ["get"] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: {{ include "fiftyone-teams-app.fullname" . }}-telemetry-logs - namespace: {{ .Values.namespace.name }} - labels: - {{- include "fiftyone-teams-app.commonLabels" . | nindent 4 }} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: {{ include "fiftyone-teams-app.fullname" . }}-telemetry-logs -subjects: - - kind: ServiceAccount - name: {{ include "fiftyone-teams-app.serviceAccountName" . }} - namespace: {{ .Values.namespace.name }} - {{- if .Values.apiSettings.rbac.create }} - - kind: ServiceAccount - name: {{ include "teams-api-rbac.service-account-name" . }} - namespace: {{ .Values.namespace.name }} - {{- end }} -{{- end }} -{{- end }} From adb3b9e43ab4cc62b6e368c452eb2ede92dde296 Mon Sep 17 00:00:00 2001 From: kacey Date: Wed, 22 Apr 2026 19:04:02 -0700 Subject: [PATCH 06/49] feat: add nativeSidecarContainers --- docker/README.md | 12 ++++ docker/internal-auth/env.template | 23 ++++++ docker/legacy-auth/env.template | 23 ++++++ .../templates/api-deployment.yaml | 9 ++- .../templates/app-deployment.yaml | 9 ++- ...elegated-operator-instance-deployment.yaml | 6 +- .../templates/plugins-deployment.yaml | 9 ++- .../docker-compose-internal-auth_test.go | 70 +++++++++++++++++++ .../docker-compose-legacy-auth_test.go | 70 +++++++++++++++++++ 9 files changed, 224 insertions(+), 7 deletions(-) diff --git a/docker/README.md b/docker/README.md index 7f231589b..d47c7c771 100644 --- a/docker/README.md +++ b/docker/README.md @@ -75,6 +75,7 @@ regarding FiftyOne Enterprise. - [Advanced Configuration](#advanced-configuration) - [Backup And Recovery](#backup-and-recovery) - [Secrets And Sensitive Data](#secrets-and-sensitive-data) + - [Telemetry](#telemetry) - [Snapshot Archival](#snapshot-archival) - [Static Banner Configuration](#static-banner-configuration) - [Storage Credentials and `FIFTYONE_ENCRYPTION_KEY`](#storage-credentials-and-fiftyone_encryption_key) @@ -656,6 +657,17 @@ and [adding secrets](https://docs.voxel51.com/enterprise/secrets.html#adding-secrets) for questions regarding storage and encryption. +### Telemetry + +FiftyOne Enterprise ships an optional telemetry overlay that exposes live +per-service metrics (CPU, memory, FDs, thread counts) and tailed logs on +the Settings → Metrics page. Enable by layering `compose.telemetry.yaml` +onto your existing compose invocation. + +Please refer to the +[telemetry configuration documentation](./docs/configuring-telemetry.md) +for full details. + ### Snapshot Archival Since version v1.5, FiftyOne Enterprise supports diff --git a/docker/internal-auth/env.template b/docker/internal-auth/env.template index 3933cdaf1..395848c92 100644 --- a/docker/internal-auth/env.template +++ b/docker/internal-auth/env.template @@ -98,3 +98,26 @@ FIFTYONE_APP_ENABLE_QUERY_PERFORMANCE=true # This should not exceed the value set in the deployment's license file for max concurrent delegated operators. # Set to 3 by default, meaning delegated operations can be executed without an external executor system. FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=3 + +# ─── Telemetry (optional; requires compose.telemetry.yaml overlay) ───── +# Redis URL used by the telemetry sidecars and the teams-api / teams-app +# services. Leave blank to use the default in-stack Redis defined by +# compose.telemetry.yaml (redis://telemetry-redis:6379). +# FIFTYONE_TELEMETRY_REDIS_URL= + +# Sidecar image. Default is voxel51/telemetry-sidecar:latest; pin to a +# specific tag in production. +# TELEMETRY_SIDECAR_IMAGE=voxel51/telemetry-sidecar:latest + +# Redis image for the in-stack telemetry backend. +# TELEMETRY_REDIS_IMAGE=redis:7-alpine +# TELEMETRY_REDIS_MAXMEMORY=400mb + +# Namespace label applied to all telemetry pods registered by sidecars. +# TELEMETRY_NAMESPACE=docker + +# TARGET_NAME overrides — substrings the sidecar matches against cmdlines +# to locate each target process. Defaults work for stock images. +# FIFTYONE_APP_TARGET_NAME=hypercorn +# TEAMS_API_TARGET_NAME=fiftyone.teams.api +# TEAMS_DO_TARGET_NAME=fiftyone delegated diff --git a/docker/legacy-auth/env.template b/docker/legacy-auth/env.template index 42c5cc963..7d8217c03 100644 --- a/docker/legacy-auth/env.template +++ b/docker/legacy-auth/env.template @@ -103,3 +103,26 @@ FIFTYONE_APP_ENABLE_QUERY_PERFORMANCE=true # This should not exceed the value set in the deployment's license file for max concurrent delegated operators. # Set to 3 by default, meaning delegated operations can be executed without an external executor system. FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=3 + +# ─── Telemetry (optional; requires compose.telemetry.yaml overlay) ───── +# Redis URL used by the telemetry sidecars and the teams-api / teams-app +# services. Leave blank to use the default in-stack Redis defined by +# compose.telemetry.yaml (redis://telemetry-redis:6379). +# FIFTYONE_TELEMETRY_REDIS_URL= + +# Sidecar image. Default is voxel51/telemetry-sidecar:latest; pin to a +# specific tag in production. +# TELEMETRY_SIDECAR_IMAGE=voxel51/telemetry-sidecar:latest + +# Redis image for the in-stack telemetry backend. +# TELEMETRY_REDIS_IMAGE=redis:7-alpine +# TELEMETRY_REDIS_MAXMEMORY=400mb + +# Namespace label applied to all telemetry pods registered by sidecars. +# TELEMETRY_NAMESPACE=docker + +# TARGET_NAME overrides — substrings the sidecar matches against cmdlines +# to locate each target process. Defaults work for stock images. +# FIFTYONE_APP_TARGET_NAME=hypercorn +# TEAMS_API_TARGET_NAME=fiftyone.teams.api +# TEAMS_DO_TARGET_NAME=fiftyone delegated diff --git a/helm/fiftyone-teams-app/templates/api-deployment.yaml b/helm/fiftyone-teams-app/templates/api-deployment.yaml index 2b0311753..002f6f660 100644 --- a/helm/fiftyone-teams-app/templates/api-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/api-deployment.yaml @@ -44,7 +44,7 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ $serviceAccountName }} - {{- if .Values.apiSettings.extraContainers }} + {{- if or .Values.apiSettings.extraContainers .Values.apiSettings.nativeSidecarContainers }} shareProcessNamespace: true {{- end }} securityContext: @@ -95,8 +95,9 @@ spec: {{- with .Values.apiSettings.extraContainers }} {{- toYaml . | nindent 8 }} {{- end }} - {{- if .Values.apiSettings.initContainers.enabled }} + {{- if or .Values.apiSettings.initContainers.enabled .Values.apiSettings.nativeSidecarContainers }} initContainers: + {{- if .Values.apiSettings.initContainers.enabled }} {{- include "fiftyone-teams-app.commonInitContainers" ( @@ -107,6 +108,10 @@ spec: "tag" .Values.apiSettings.initContainers.image.tag ) | nindent 8 }} + {{- end }} + {{- with .Values.apiSettings.nativeSidecarContainers }} + {{- toYaml . | nindent 8 }} + {{- end }} {{- end }} {{- with .Values.apiSettings.nodeSelector }} nodeSelector: diff --git a/helm/fiftyone-teams-app/templates/app-deployment.yaml b/helm/fiftyone-teams-app/templates/app-deployment.yaml index 4f18006d2..91d3f9d0c 100644 --- a/helm/fiftyone-teams-app/templates/app-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/app-deployment.yaml @@ -35,7 +35,7 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" . }} - {{- if .Values.appSettings.extraContainers }} + {{- if or .Values.appSettings.extraContainers .Values.appSettings.nativeSidecarContainers }} shareProcessNamespace: true {{- end }} securityContext: @@ -79,8 +79,9 @@ spec: {{- with .Values.appSettings.extraContainers }} {{- toYaml . | nindent 8 }} {{- end }} - {{- if .Values.appSettings.initContainers.enabled }} + {{- if or .Values.appSettings.initContainers.enabled .Values.appSettings.nativeSidecarContainers }} initContainers: + {{- if .Values.appSettings.initContainers.enabled }} {{- include "fiftyone-teams-app.commonInitContainers" ( @@ -91,6 +92,10 @@ spec: "tag" .Values.appSettings.initContainers.image.tag ) | nindent 8 }} + {{- end }} + {{- with .Values.appSettings.nativeSidecarContainers }} + {{- toYaml . | nindent 8 }} + {{- end }} {{- end }} {{- with .Values.appSettings.nodeSelector }} nodeSelector: diff --git a/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml b/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml index 1765e87c4..ab196e367 100644 --- a/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml @@ -50,7 +50,7 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" $ }} - {{- if ($v.extraContainers | default $baseTpl.extraContainers) }} + {{- if or ($v.extraContainers | default $baseTpl.extraContainers) ($v.nativeSidecarContainers | default $baseTpl.nativeSidecarContainers) }} shareProcessNamespace: true {{- end }} securityContext: @@ -112,6 +112,10 @@ spec: {{- with $v.extraContainers | default $baseTpl.extraContainers }} {{- toYaml . | nindent 8 }} {{- end }} + {{- with $v.nativeSidecarContainers | default $baseTpl.nativeSidecarContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} {{- with (merge (dict) ($v.nodeSelector | default dict) ($baseTpl.nodeSelector)) }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/helm/fiftyone-teams-app/templates/plugins-deployment.yaml b/helm/fiftyone-teams-app/templates/plugins-deployment.yaml index 9c3ca49ba..5524ae2ad 100644 --- a/helm/fiftyone-teams-app/templates/plugins-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/plugins-deployment.yaml @@ -36,7 +36,7 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" . }} - {{- if .Values.pluginsSettings.extraContainers }} + {{- if or .Values.pluginsSettings.extraContainers .Values.pluginsSettings.nativeSidecarContainers }} shareProcessNamespace: true {{- end }} securityContext: @@ -80,8 +80,9 @@ spec: {{- with .Values.pluginsSettings.extraContainers }} {{- toYaml . | nindent 8 }} {{- end }} - {{- if .Values.pluginsSettings.initContainers.enabled }} + {{- if or .Values.pluginsSettings.initContainers.enabled .Values.pluginsSettings.nativeSidecarContainers }} initContainers: + {{- if .Values.pluginsSettings.initContainers.enabled }} {{- include "fiftyone-teams-app.commonInitContainers" ( @@ -92,6 +93,10 @@ spec: "tag" .Values.pluginsSettings.initContainers.image.tag ) | nindent 8 }} + {{- end }} + {{- with .Values.pluginsSettings.nativeSidecarContainers }} + {{- toYaml . | nindent 8 }} + {{- end }} {{- end }} {{- with .Values.pluginsSettings.nodeSelector }} nodeSelector: diff --git a/tests/unit/compose/docker-compose-internal-auth_test.go b/tests/unit/compose/docker-compose-internal-auth_test.go index f96cccd51..7ca111390 100644 --- a/tests/unit/compose/docker-compose-internal-auth_test.go +++ b/tests/unit/compose/docker-compose-internal-auth_test.go @@ -28,6 +28,8 @@ var internalAuthComposeFile = filepath.Join(dockerInternalAuthDir, "compose.yaml var internalAuthComposePluginsFile = filepath.Join(dockerInternalAuthDir, "compose.plugins.yaml") var internalAuthComposeDedicatedPluginsFile = filepath.Join(dockerInternalAuthDir, "compose.dedicated-plugins.yaml") var internalAuthComposeDelegatedOperationsFile = filepath.Join(dockerInternalAuthDir, "compose.delegated-operators.yaml") +var internalAuthComposeTelemetryFile = filepath.Join(dockerInternalAuthDir, "compose.telemetry.yaml") +var internalAuthComposeTelemetryDelegatedOperatorsFile = filepath.Join(dockerInternalAuthDir, "compose.telemetry.delegated-operators.yaml") var internalAuthEnvTemplateFilePath = filepath.Join(dockerInternalAuthDir, "env.template") type commonServicesInternalAuthDockerComposeTest struct { @@ -103,6 +105,41 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServicesNames() { "teams-do", }, }, + { + "composeTelemetry", + []string{internalAuthComposeFile, internalAuthComposeTelemetryFile}, + s.dotEnvFiles, + []string{ + "fiftyone-app", + "fiftyone-app-telemetry", + "teams-api", + "teams-api-telemetry", + "teams-app", + "teams-cas", + "telemetry-redis", + }, + }, + { + "composeTelemetryDelegatedOperators", + []string{ + internalAuthComposeFile, + internalAuthComposeDelegatedOperationsFile, + internalAuthComposeTelemetryFile, + internalAuthComposeTelemetryDelegatedOperatorsFile, + }, + s.dotEnvFiles, + []string{ + "fiftyone-app", + "fiftyone-app-telemetry", + "teams-api", + "teams-api-telemetry", + "teams-app", + "teams-cas", + "teams-do", + "teams-do-telemetry", + "telemetry-redis", + }, + }, } for _, testCase := range testCases { @@ -187,6 +224,39 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceImage() { s.dotEnvFiles, "voxel51/fiftyone-teams-cv-full:v2.19.0", }, + { + "telemetryRedis", + "telemetry-redis", + []string{internalAuthComposeFile, internalAuthComposeTelemetryFile}, + s.dotEnvFiles, + "redis:7-alpine", + }, + { + "telemetrySidecarFiftyoneApp", + "fiftyone-app-telemetry", + []string{internalAuthComposeFile, internalAuthComposeTelemetryFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:latest", + }, + { + "telemetrySidecarTeamsApi", + "teams-api-telemetry", + []string{internalAuthComposeFile, internalAuthComposeTelemetryFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:latest", + }, + { + "telemetrySidecarTeamsDo", + "teams-do-telemetry", + []string{ + internalAuthComposeFile, + internalAuthComposeDelegatedOperationsFile, + internalAuthComposeTelemetryFile, + internalAuthComposeTelemetryDelegatedOperatorsFile, + }, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:latest", + }, } for _, testCase := range testCases { diff --git a/tests/unit/compose/docker-compose-legacy-auth_test.go b/tests/unit/compose/docker-compose-legacy-auth_test.go index 1a48c5c4d..28c3a90d0 100644 --- a/tests/unit/compose/docker-compose-legacy-auth_test.go +++ b/tests/unit/compose/docker-compose-legacy-auth_test.go @@ -28,6 +28,8 @@ var legacyAuthComposeFile = filepath.Join(dockerLegacyAuthDir, "compose.yaml") var legacyAuthComposePluginsFile = filepath.Join(dockerLegacyAuthDir, "compose.plugins.yaml") var legacyAuthComposeDedicatedPluginsFile = filepath.Join(dockerLegacyAuthDir, "compose.dedicated-plugins.yaml") var legacyAuthComposeDelegatedOperationsFile = filepath.Join(dockerLegacyAuthDir, "compose.delegated-operators.yaml") +var legacyAuthComposeTelemetryFile = filepath.Join(dockerLegacyAuthDir, "compose.telemetry.yaml") +var legacyAuthComposeTelemetryDelegatedOperatorsFile = filepath.Join(dockerLegacyAuthDir, "compose.telemetry.delegated-operators.yaml") var legacyAuthEnvTemplateFilePath = filepath.Join(dockerLegacyAuthDir, "env.template") type commonServicesLegacyAuthDockerComposeTest struct { @@ -103,6 +105,41 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { "teams-do", }, }, + { + "composeTelemetry", + []string{legacyAuthComposeFile, legacyAuthComposeTelemetryFile}, + s.dotEnvFiles, + []string{ + "fiftyone-app", + "fiftyone-app-telemetry", + "teams-api", + "teams-api-telemetry", + "teams-app", + "teams-cas", + "telemetry-redis", + }, + }, + { + "composeTelemetryDelegatedOperators", + []string{ + legacyAuthComposeFile, + legacyAuthComposeDelegatedOperationsFile, + legacyAuthComposeTelemetryFile, + legacyAuthComposeTelemetryDelegatedOperatorsFile, + }, + s.dotEnvFiles, + []string{ + "fiftyone-app", + "fiftyone-app-telemetry", + "teams-api", + "teams-api-telemetry", + "teams-app", + "teams-cas", + "teams-do", + "teams-do-telemetry", + "telemetry-redis", + }, + }, } for _, testCase := range testCases { @@ -187,6 +224,39 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceImage() { s.dotEnvFiles, "voxel51/fiftyone-teams-cv-full:v2.19.0", }, + { + "telemetryRedis", + "telemetry-redis", + []string{legacyAuthComposeFile, legacyAuthComposeTelemetryFile}, + s.dotEnvFiles, + "redis:7-alpine", + }, + { + "telemetrySidecarFiftyoneApp", + "fiftyone-app-telemetry", + []string{legacyAuthComposeFile, legacyAuthComposeTelemetryFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:latest", + }, + { + "telemetrySidecarTeamsApi", + "teams-api-telemetry", + []string{legacyAuthComposeFile, legacyAuthComposeTelemetryFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:latest", + }, + { + "telemetrySidecarTeamsDo", + "teams-do-telemetry", + []string{ + legacyAuthComposeFile, + legacyAuthComposeDelegatedOperationsFile, + legacyAuthComposeTelemetryFile, + legacyAuthComposeTelemetryDelegatedOperatorsFile, + }, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:latest", + }, } for _, testCase := range testCases { From 9578958a77add463dd1c36eef1aa1291078a72d9 Mon Sep 17 00:00:00 2001 From: kacey Date: Wed, 22 Apr 2026 19:22:40 -0700 Subject: [PATCH 07/49] fix: add missing files --- docker/docs/configuring-telemetry.md | 114 ++++++++++++++++++ ...compose.telemetry.delegated-operators.yaml | 57 +++++++++ docker/internal-auth/compose.telemetry.yaml | 84 +++++++++++++ ...compose.telemetry.delegated-operators.yaml | 57 +++++++++ docker/legacy-auth/compose.telemetry.yaml | 84 +++++++++++++ 5 files changed, 396 insertions(+) create mode 100644 docker/docs/configuring-telemetry.md create mode 100644 docker/internal-auth/compose.telemetry.delegated-operators.yaml create mode 100644 docker/internal-auth/compose.telemetry.yaml create mode 100644 docker/legacy-auth/compose.telemetry.delegated-operators.yaml create mode 100644 docker/legacy-auth/compose.telemetry.yaml diff --git a/docker/docs/configuring-telemetry.md b/docker/docs/configuring-telemetry.md new file mode 100644 index 000000000..93271429d --- /dev/null +++ b/docker/docs/configuring-telemetry.md @@ -0,0 +1,114 @@ + + +
+

+ +Voxel51 Logo   +Voxel51 FiftyOne + +

+
+ + +--- + +# Configuring FiftyOne Enterprise Telemetry + +The telemetry overlay adds a lightweight per-service metrics collector +(sidecar pattern) plus a Redis backend. Once enabled, the Settings → +Metrics page in teams-app displays live CPU / memory / thread / file-descriptor +samples and tailed stdout logs for each observed service. + +This overlay is optional and layered on top of the main compose file. + +## Enable + +Use the `compose.telemetry.yaml` overlay alongside your normal deployment: + +```shell +docker compose \ + -f compose.yaml \ + -f compose.telemetry.yaml \ + up -d +``` + +Can be combined with the plugins or delegated-operators overlays. For +delegated operators, add the companion `compose.telemetry.delegated-operators.yaml` +overlay so the teams-do executor is also observed: + +```shell +docker compose \ + -f compose.yaml \ + -f compose.dedicated-plugins.yaml \ + -f compose.delegated-operators.yaml \ + -f compose.telemetry.yaml \ + -f compose.telemetry.delegated-operators.yaml \ + up -d +``` + +## What it adds + +- `telemetry-redis` — Redis 7 container that holds metric streams and log + entries. Data is capped by a maxmemory policy (`allkeys-lru`) so disk usage + stays bounded. +- `fiftyone-app-telemetry`, `teams-api-telemetry` — sidecar containers, one + per observed service. Each joins the target's PID namespace via + `pid: "service:"` so it can read `/proc//fd/1` and use + psutil to sample CPU, memory, FDs, and thread counts. +- `teams-do-telemetry` (only with `compose.telemetry.delegated-operators.yaml`) — + sidecar in `EXECUTOR_SIDECAR=true` mode that watches the executor for + per-operation child processes and records per-op metrics back to the + `delegated_ops` MongoDB document. +- `FIFTYONE_TELEMETRY_REDIS_URL` injected on `fiftyone-app`, `teams-api`, + `teams-app`, and (when the DO overlay is used) `teams-do` so the in-app + telemetry blueprint and SSE endpoints can read from Redis. + +### Scaling teams-do with telemetry + +docker-compose's `pid: "service:"` only joins a single replica's +PID namespace. To keep the sidecar observation honest, the telemetry DO +overlay **forces `teams-do` replicas to 1**, overriding +`FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS`. + +If you need more than one delegated-operator worker observed at the same +time, either: + +1. Define additional explicit services in a compose override — e.g. + `teams-do-1`, `teams-do-2` — each with a paired `teams-do-N-telemetry` + sidecar using `pid: "service:teams-do-N"`. +2. Deploy via the helm chart, which automatically adds a telemetry sidecar + to every pod in the delegated-operator deployment. + +## Environment overrides + +All knobs live in your `.env` — see `env.template` for the full list: + +| Variable | Default | Purpose | +| ------------------------------ | ----------------------------------- | ---------------------------------------------------- | +| `FIFTYONE_TELEMETRY_REDIS_URL` | `redis://telemetry-redis:6379` | Override to point at an external Redis if desired | +| `TELEMETRY_SIDECAR_IMAGE` | `voxel51/telemetry-sidecar:latest` | Pin sidecar to a specific tag for production | +| `TELEMETRY_REDIS_IMAGE` | `redis:7-alpine` | Alternate redis image | +| `TELEMETRY_REDIS_MAXMEMORY` | `400mb` | Redis maxmemory budget | +| `TELEMETRY_NAMESPACE` | `docker` | Namespace label attached to each registered pod | +| `FIFTYONE_APP_TARGET_NAME` | `hypercorn` | Substring used to locate the fiftyone-app process | +| `TEAMS_API_TARGET_NAME` | `fiftyone.teams.api` | Substring used to locate the teams-api process | +| `TEAMS_DO_TARGET_NAME` | `fiftyone delegated` | Substring used to locate the teams-do process | + +## Access control + +The telemetry endpoints (`/telemetry/*` on teams-api; `/api/telemetry/stream` +and `/api/telemetry/logs` on teams-app) require an authenticated user with +the `ADMIN` role. Non-admin users and unauthenticated requests receive 401/403. + +## Verify + +```shell +docker compose exec telemetry-redis redis-cli HGETALL active_pods +docker compose exec telemetry-redis redis-cli XLEN metrics:docker:fiftyone-app +``` + +`XLEN` should increase over time. If it does not, check the sidecar logs: + +```shell +docker compose logs fiftyone-app-telemetry teams-api-telemetry --tail 20 +``` diff --git a/docker/internal-auth/compose.telemetry.delegated-operators.yaml b/docker/internal-auth/compose.telemetry.delegated-operators.yaml new file mode 100644 index 000000000..0a42f4c7c --- /dev/null +++ b/docker/internal-auth/compose.telemetry.delegated-operators.yaml @@ -0,0 +1,57 @@ +--- +# Telemetry overlay for the delegated operators (teams-do) service. +# Use alongside compose.telemetry.yaml and compose.delegated-operators.yaml: +# +# docker compose -f compose.yaml \ +# -f compose.delegated-operators.yaml \ +# -f compose.telemetry.yaml \ +# -f compose.telemetry.delegated-operators.yaml \ +# up -d +# +# The EXECUTOR_SIDECAR mode watches the teams-do executor for per-operation +# child processes and records per-op metrics back to the delegated_ops +# MongoDB document (via FIFTYONE_DATABASE_URI). +# +# This overlay FORCES `teams-do` replicas to 1 so the sidecar has a +# deterministic 1:1 pairing via the shared PID namespace. docker-compose's +# `pid: "service:"` only joins a single replica's PID namespace, +# so running multiple replicas with a single sidecar would silently miss +# the other replicas. If you need multiple DO workers observed, run one +# teams-do + sidecar pair per worker by defining additional services +# (teams-do-1, teams-do-2, ...), or switch to the helm chart which scales +# the sidecar alongside each pod automatically. +# +# For Proxy Server instructions please see +# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies +services: + teams-do: + environment: + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + TELEMETRY_SOCKET: /tmp/telemetry/agent.sock + deploy: + replicas: 1 + volumes: + - telemetry-socket:/tmp/telemetry + + teams-do-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-do" + environment: + POD_NAME: teams-do + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: delegated-operator + TARGET_NAME: ${TEAMS_DO_TARGET_NAME:-fiftyone delegated} + EXECUTOR_SIDECAR: "true" + TELEMETRY_SOCKET: /tmp/telemetry/agent.sock + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} + FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + volumes: + - telemetry-socket:/tmp/telemetry + depends_on: + - teams-do + - telemetry-redis + restart: always + +volumes: + telemetry-socket: diff --git a/docker/internal-auth/compose.telemetry.yaml b/docker/internal-auth/compose.telemetry.yaml new file mode 100644 index 000000000..e21212548 --- /dev/null +++ b/docker/internal-auth/compose.telemetry.yaml @@ -0,0 +1,84 @@ +--- +# Telemetry overlay. Adds a Redis backend + sidecar containers that observe +# each target service via a shared PID namespace, publishing metrics and +# log events to Redis. +# +# Layered on top of the main compose file, e.g.: +# docker compose -f compose.yaml -f compose.telemetry.yaml up +# +# Or with delegated operators and/or dedicated plugins: +# docker compose -f compose.yaml \ +# -f compose.delegated-operators.yaml \ +# -f compose.dedicated-plugins.yaml \ +# -f compose.telemetry.yaml up +# +# For Proxy Server instructions please see +# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies +services: + # Redis backend for telemetry samples and log streams. + telemetry-redis: + image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} + command: + - redis-server + - --save + - "60" + - "1" + - --dir + - /data + - --maxmemory + - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} + - --maxmemory-policy + - allkeys-lru + restart: always + volumes: + - telemetry-redis-data:/data + + # Inject the Redis URL onto the services that read/write telemetry. + # teams-api serves the /telemetry Sanic blueprint; teams-app serves + # /api/telemetry/stream + /api/telemetry/logs SSE endpoints. + fiftyone-app: + environment: + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + + teams-api: + environment: + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + + teams-app: + environment: + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + + # One sidecar per observed service. `pid: "service:"` joins the + # target's PID namespace so the sidecar can read /proc//fd/1 and + # psutil can see the target's process. + + fiftyone-app-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:fiftyone-app" + environment: + POD_NAME: fiftyone-app + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: fiftyone-app + TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + - fiftyone-app + - telemetry-redis + restart: always + + teams-api-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-api" + environment: + POD_NAME: teams-api + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: teams-api + TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone.teams.api} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + - teams-api + - telemetry-redis + restart: always + +volumes: + telemetry-redis-data: diff --git a/docker/legacy-auth/compose.telemetry.delegated-operators.yaml b/docker/legacy-auth/compose.telemetry.delegated-operators.yaml new file mode 100644 index 000000000..0a42f4c7c --- /dev/null +++ b/docker/legacy-auth/compose.telemetry.delegated-operators.yaml @@ -0,0 +1,57 @@ +--- +# Telemetry overlay for the delegated operators (teams-do) service. +# Use alongside compose.telemetry.yaml and compose.delegated-operators.yaml: +# +# docker compose -f compose.yaml \ +# -f compose.delegated-operators.yaml \ +# -f compose.telemetry.yaml \ +# -f compose.telemetry.delegated-operators.yaml \ +# up -d +# +# The EXECUTOR_SIDECAR mode watches the teams-do executor for per-operation +# child processes and records per-op metrics back to the delegated_ops +# MongoDB document (via FIFTYONE_DATABASE_URI). +# +# This overlay FORCES `teams-do` replicas to 1 so the sidecar has a +# deterministic 1:1 pairing via the shared PID namespace. docker-compose's +# `pid: "service:"` only joins a single replica's PID namespace, +# so running multiple replicas with a single sidecar would silently miss +# the other replicas. If you need multiple DO workers observed, run one +# teams-do + sidecar pair per worker by defining additional services +# (teams-do-1, teams-do-2, ...), or switch to the helm chart which scales +# the sidecar alongside each pod automatically. +# +# For Proxy Server instructions please see +# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies +services: + teams-do: + environment: + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + TELEMETRY_SOCKET: /tmp/telemetry/agent.sock + deploy: + replicas: 1 + volumes: + - telemetry-socket:/tmp/telemetry + + teams-do-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-do" + environment: + POD_NAME: teams-do + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: delegated-operator + TARGET_NAME: ${TEAMS_DO_TARGET_NAME:-fiftyone delegated} + EXECUTOR_SIDECAR: "true" + TELEMETRY_SOCKET: /tmp/telemetry/agent.sock + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} + FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + volumes: + - telemetry-socket:/tmp/telemetry + depends_on: + - teams-do + - telemetry-redis + restart: always + +volumes: + telemetry-socket: diff --git a/docker/legacy-auth/compose.telemetry.yaml b/docker/legacy-auth/compose.telemetry.yaml new file mode 100644 index 000000000..e21212548 --- /dev/null +++ b/docker/legacy-auth/compose.telemetry.yaml @@ -0,0 +1,84 @@ +--- +# Telemetry overlay. Adds a Redis backend + sidecar containers that observe +# each target service via a shared PID namespace, publishing metrics and +# log events to Redis. +# +# Layered on top of the main compose file, e.g.: +# docker compose -f compose.yaml -f compose.telemetry.yaml up +# +# Or with delegated operators and/or dedicated plugins: +# docker compose -f compose.yaml \ +# -f compose.delegated-operators.yaml \ +# -f compose.dedicated-plugins.yaml \ +# -f compose.telemetry.yaml up +# +# For Proxy Server instructions please see +# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies +services: + # Redis backend for telemetry samples and log streams. + telemetry-redis: + image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} + command: + - redis-server + - --save + - "60" + - "1" + - --dir + - /data + - --maxmemory + - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} + - --maxmemory-policy + - allkeys-lru + restart: always + volumes: + - telemetry-redis-data:/data + + # Inject the Redis URL onto the services that read/write telemetry. + # teams-api serves the /telemetry Sanic blueprint; teams-app serves + # /api/telemetry/stream + /api/telemetry/logs SSE endpoints. + fiftyone-app: + environment: + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + + teams-api: + environment: + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + + teams-app: + environment: + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + + # One sidecar per observed service. `pid: "service:"` joins the + # target's PID namespace so the sidecar can read /proc//fd/1 and + # psutil can see the target's process. + + fiftyone-app-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:fiftyone-app" + environment: + POD_NAME: fiftyone-app + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: fiftyone-app + TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + - fiftyone-app + - telemetry-redis + restart: always + + teams-api-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-api" + environment: + POD_NAME: teams-api + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: teams-api + TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone.teams.api} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + - teams-api + - telemetry-redis + restart: always + +volumes: + telemetry-redis-data: From e9e710a425771ca837b737451cd074a172fc376c Mon Sep 17 00:00:00 2001 From: kacey Date: Thu, 14 May 2026 22:22:29 -0700 Subject: [PATCH 08/49] chore: update compose files --- docker/docs/configuring-telemetry.md | 52 +++++- .../compose.delegated-operators.gpu.yaml | 87 ++++++++++ ...compose.telemetry.delegated-operators.yaml | 9 +- .../compose.telemetry.plugins.yaml | 37 +++++ docker/internal-auth/compose.telemetry.yaml | 24 ++- docker/internal-auth/env.template | 3 +- .../compose.delegated-operators.gpu.yaml | 87 ++++++++++ ...compose.telemetry.delegated-operators.yaml | 9 +- .../compose.telemetry.plugins.yaml | 37 +++++ docker/legacy-auth/compose.telemetry.yaml | 24 ++- docker/legacy-auth/env.template | 3 +- .../configuring-kubernetes-orchestrator.md | 155 ++++++++++++++++++ helm/fiftyone-teams-app/README.md | 15 +- helm/fiftyone-teams-app/README.md.gotmpl | 1 + .../delegated-operator-job-configmap.yaml | 12 ++ helm/fiftyone-teams-app/values.schema.json | 90 ++++++++++ helm/fiftyone-teams-app/values.yaml | 45 +++++ .../docker-compose-internal-auth_test.go | 46 ++++++ .../docker-compose-legacy-auth_test.go | 46 ++++++ 19 files changed, 751 insertions(+), 31 deletions(-) create mode 100644 docker/internal-auth/compose.delegated-operators.gpu.yaml create mode 100644 docker/internal-auth/compose.telemetry.plugins.yaml create mode 100644 docker/legacy-auth/compose.delegated-operators.gpu.yaml create mode 100644 docker/legacy-auth/compose.telemetry.plugins.yaml diff --git a/docker/docs/configuring-telemetry.md b/docker/docs/configuring-telemetry.md index 93271429d..664f8211c 100644 --- a/docker/docs/configuring-telemetry.md +++ b/docker/docs/configuring-telemetry.md @@ -32,9 +32,9 @@ docker compose \ up -d ``` -Can be combined with the plugins or delegated-operators overlays. For -delegated operators, add the companion `compose.telemetry.delegated-operators.yaml` -overlay so the teams-do executor is also observed: +Can be combined with the plugins or delegated-operators overlays. Each +optional service has its own companion telemetry overlay so its sidecar +is added only when that service is enabled: ```shell docker compose \ @@ -42,6 +42,21 @@ docker compose \ -f compose.dedicated-plugins.yaml \ -f compose.delegated-operators.yaml \ -f compose.telemetry.yaml \ + -f compose.telemetry.plugins.yaml \ + -f compose.telemetry.delegated-operators.yaml \ + up -d +``` + +For GPU-enabled delegated operators, layer +`compose.delegated-operators.gpu.yaml` (which includes its own bundled +telemetry sidecar) and activate the `gpu` profile: + +```shell +docker compose --profile gpu \ + -f compose.yaml \ + -f compose.delegated-operators.yaml \ + -f compose.delegated-operators.gpu.yaml \ + -f compose.telemetry.yaml \ -f compose.telemetry.delegated-operators.yaml \ up -d ``` @@ -54,11 +69,19 @@ docker compose \ - `fiftyone-app-telemetry`, `teams-api-telemetry` — sidecar containers, one per observed service. Each joins the target's PID namespace via `pid: "service:"` so it can read `/proc//fd/1` and use - psutil to sample CPU, memory, FDs, and thread counts. + psutil to sample CPU, memory, FDs, and thread counts. Sidecars run + with the `SYS_PTRACE` capability so py-spy can attach to the target. +- `teams-plugins-telemetry` (only with `compose.telemetry.plugins.yaml`) — + sidecar for the dedicated `teams-plugins` service. - `teams-do-telemetry` (only with `compose.telemetry.delegated-operators.yaml`) — sidecar in `EXECUTOR_SIDECAR=true` mode that watches the executor for per-operation child processes and records per-op metrics back to the `delegated_ops` MongoDB document. +- `teams-do-gpu` + `teams-do-gpu-telemetry` (only with + `compose.delegated-operators.gpu.yaml` and `--profile gpu`) — a GPU- + enabled delegated-operator worker registered as a distinct + orchestrator (`-n teams-do-gpu`) plus its paired sidecar. Sidecar + reads GPU metrics via NVML and requires its own GPU reservation. - `FIFTYONE_TELEMETRY_REDIS_URL` injected on `fiftyone-app`, `teams-api`, `teams-app`, and (when the DO overlay is used) `teams-do` so the in-app telemetry blueprint and SSE endpoints can read from Redis. @@ -79,6 +102,21 @@ time, either: 2. Deploy via the helm chart, which automatically adds a telemetry sidecar to every pod in the delegated-operator deployment. +### Sidecar lifecycle on workload restart + +Each sidecar joins its workload's PID namespace at container-create +time. If the workload is recreated (force-recreate, image upgrade, +configuration change) the namespace reference goes stale and Docker +cannot re-attach the sidecar — it stays in `Exited (137)` until +manually recreated. + +The overlays mitigate this with `depends_on..restart: true` +(compose v2.17+), which tells compose to recreate the sidecar in +lockstep with the workload. `docker compose version` must report +v2.17 or newer for this to take effect. If the workload itself crash- +loops, the sidecar follows it into the crash loop, matching the +Kubernetes pod-restart behavior. + ## Environment overrides All knobs live in your `.env` — see `env.template` for the full list: @@ -91,8 +129,12 @@ All knobs live in your `.env` — see `env.template` for the full list: | `TELEMETRY_REDIS_MAXMEMORY` | `400mb` | Redis maxmemory budget | | `TELEMETRY_NAMESPACE` | `docker` | Namespace label attached to each registered pod | | `FIFTYONE_APP_TARGET_NAME` | `hypercorn` | Substring used to locate the fiftyone-app process | -| `TEAMS_API_TARGET_NAME` | `fiftyone.teams.api` | Substring used to locate the teams-api process | +| `TEAMS_API_TARGET_NAME` | `fiftyone-teams-api` | Substring used to locate the teams-api process | +| `TEAMS_PLUGINS_TARGET_NAME` | `hypercorn` | Substring used to locate the teams-plugins process | | `TEAMS_DO_TARGET_NAME` | `fiftyone delegated` | Substring used to locate the teams-do process | +| `NVIDIA_GPU_COUNT` | `1` | GPU reservation for the GPU DO worker + sidecar | +| `NVIDIA_VISIBLE_DEVICES` | `all` | Pass-through to teams-do-gpu / sidecar | +| `NVIDIA_DRIVER_CAPABILITIES` | `compute,utility` | Must include `utility` so NVML is available | ## Access control diff --git a/docker/internal-auth/compose.delegated-operators.gpu.yaml b/docker/internal-auth/compose.delegated-operators.gpu.yaml new file mode 100644 index 000000000..6f38f07bb --- /dev/null +++ b/docker/internal-auth/compose.delegated-operators.gpu.yaml @@ -0,0 +1,87 @@ +--- +# GPU-enabled delegated-operator worker + telemetry sidecar. Requires a +# Linux host with the NVIDIA driver, nvidia-container-toolkit, and the +# `nvidia` runtime configured in dockerd. Docker Desktop on macOS / +# Windows does not support GPU passthrough. +# +# Gated behind the `gpu` profile. Activate with: +# +# docker compose --profile gpu \ +# -f compose.yaml \ +# -f compose.delegated-operators.yaml \ +# -f compose.delegated-operators.gpu.yaml \ +# -f compose.telemetry.yaml \ +# -f compose.telemetry.delegated-operators.yaml \ +# up -d +# +# For Proxy Server instructions please see +# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies +services: + teams-do-gpu: + image: voxel51/fiftyone-teams-cv-full:v2.17.1 + profiles: ["gpu"] + shm_size: "8g" + command: > + /bin/sh -c "fiftyone delegated launch -t remote -m -n teams-do-gpu" + environment: + API_URL: ${API_URL} + FIFTYONE_DATABASE_ADMIN: "false" + FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} + FIFTYONE_ENCRYPTION_KEY: ${FIFTYONE_ENCRYPTION_KEY} + FIFTYONE_INTERNAL_SERVICE: "true" + FIFTYONE_MEDIA_CACHE_SIZE_BYTES: -1 + FIFTYONE_PLUGINS_DIR: /opt/plugins + NVIDIA_VISIBLE_DEVICES: ${NVIDIA_VISIBLE_DEVICES:-all} + NVIDIA_DRIVER_CAPABILITIES: ${NVIDIA_DRIVER_CAPABILITIES:-compute,utility} + deploy: + replicas: 1 + resources: + reservations: + devices: + - driver: nvidia + count: ${NVIDIA_GPU_COUNT:-1} + capabilities: ["gpu"] + volumes: + - plugins-vol:/opt/plugins:ro + - telemetry-socket-gpu:/tmp/telemetry + restart: always + + teams-do-gpu-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-do-gpu" + profiles: ["gpu"] + cap_add: + - SYS_PTRACE + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: ${NVIDIA_GPU_COUNT:-1} + capabilities: ["gpu"] + environment: + NVIDIA_VISIBLE_DEVICES: ${NVIDIA_VISIBLE_DEVICES:-all} + NVIDIA_DRIVER_CAPABILITIES: ${NVIDIA_DRIVER_CAPABILITIES:-compute,utility} + POD_NAME: teams-do-gpu + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: delegated-operator + TARGET_NAME: ${TEAMS_DO_TARGET_NAME:-fiftyone delegated} + EXECUTOR_SIDECAR: "true" + TELEMETRY_SOCKET: /tmp/telemetry/agent.sock + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} + FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + volumes: + - telemetry-socket-gpu:/tmp/telemetry + depends_on: + teams-do-gpu: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + +volumes: + plugins-vol: + telemetry-socket-gpu: diff --git a/docker/internal-auth/compose.telemetry.delegated-operators.yaml b/docker/internal-auth/compose.telemetry.delegated-operators.yaml index 0a42f4c7c..f6a1c7d51 100644 --- a/docker/internal-auth/compose.telemetry.delegated-operators.yaml +++ b/docker/internal-auth/compose.telemetry.delegated-operators.yaml @@ -36,6 +36,8 @@ services: teams-do-telemetry: image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} pid: "service:teams-do" + cap_add: + - SYS_PTRACE environment: POD_NAME: teams-do POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} @@ -49,8 +51,11 @@ services: volumes: - telemetry-socket:/tmp/telemetry depends_on: - - teams-do - - telemetry-redis + teams-do: + condition: service_started + restart: true + telemetry-redis: + condition: service_started restart: always volumes: diff --git a/docker/internal-auth/compose.telemetry.plugins.yaml b/docker/internal-auth/compose.telemetry.plugins.yaml new file mode 100644 index 000000000..ac1de8bf2 --- /dev/null +++ b/docker/internal-auth/compose.telemetry.plugins.yaml @@ -0,0 +1,37 @@ +--- +# Telemetry overlay for the dedicated teams-plugins service. The sidecar +# shares the target's PID namespace via `pid: "service:teams-plugins"`. +# +# Layered after compose.dedicated-plugins.yaml + compose.telemetry.yaml: +# +# docker compose -f compose.yaml \ +# -f compose.dedicated-plugins.yaml \ +# -f compose.telemetry.yaml \ +# -f compose.telemetry.plugins.yaml \ +# up -d +# +# For Proxy Server instructions please see +# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies +services: + teams-plugins: + environment: + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + + teams-plugins-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-plugins" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: teams-plugins + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: teams-plugins + TARGET_NAME: ${TEAMS_PLUGINS_TARGET_NAME:-hypercorn} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + teams-plugins: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always diff --git a/docker/internal-auth/compose.telemetry.yaml b/docker/internal-auth/compose.telemetry.yaml index e21212548..89b9d57a4 100644 --- a/docker/internal-auth/compose.telemetry.yaml +++ b/docker/internal-auth/compose.telemetry.yaml @@ -15,7 +15,6 @@ # For Proxy Server instructions please see # https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies services: - # Redis backend for telemetry samples and log streams. telemetry-redis: image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} command: @@ -33,9 +32,6 @@ services: volumes: - telemetry-redis-data:/data - # Inject the Redis URL onto the services that read/write telemetry. - # teams-api serves the /telemetry Sanic blueprint; teams-app serves - # /api/telemetry/stream + /api/telemetry/logs SSE endpoints. fiftyone-app: environment: FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} @@ -55,6 +51,8 @@ services: fiftyone-app-telemetry: image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} pid: "service:fiftyone-app" + cap_add: + - SYS_PTRACE environment: POD_NAME: fiftyone-app POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} @@ -62,22 +60,30 @@ services: TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} depends_on: - - fiftyone-app - - telemetry-redis + fiftyone-app: + condition: service_started + restart: true + telemetry-redis: + condition: service_started restart: always teams-api-telemetry: image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} pid: "service:teams-api" + cap_add: + - SYS_PTRACE environment: POD_NAME: teams-api POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} SERVICE_TYPE: teams-api - TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone.teams.api} + TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} depends_on: - - teams-api - - telemetry-redis + teams-api: + condition: service_started + restart: true + telemetry-redis: + condition: service_started restart: always volumes: diff --git a/docker/internal-auth/env.template b/docker/internal-auth/env.template index 395848c92..270780446 100644 --- a/docker/internal-auth/env.template +++ b/docker/internal-auth/env.template @@ -119,5 +119,6 @@ FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=3 # TARGET_NAME overrides — substrings the sidecar matches against cmdlines # to locate each target process. Defaults work for stock images. # FIFTYONE_APP_TARGET_NAME=hypercorn -# TEAMS_API_TARGET_NAME=fiftyone.teams.api +# TEAMS_API_TARGET_NAME=fiftyone-teams-api +# TEAMS_PLUGINS_TARGET_NAME=hypercorn # TEAMS_DO_TARGET_NAME=fiftyone delegated diff --git a/docker/legacy-auth/compose.delegated-operators.gpu.yaml b/docker/legacy-auth/compose.delegated-operators.gpu.yaml new file mode 100644 index 000000000..6f38f07bb --- /dev/null +++ b/docker/legacy-auth/compose.delegated-operators.gpu.yaml @@ -0,0 +1,87 @@ +--- +# GPU-enabled delegated-operator worker + telemetry sidecar. Requires a +# Linux host with the NVIDIA driver, nvidia-container-toolkit, and the +# `nvidia` runtime configured in dockerd. Docker Desktop on macOS / +# Windows does not support GPU passthrough. +# +# Gated behind the `gpu` profile. Activate with: +# +# docker compose --profile gpu \ +# -f compose.yaml \ +# -f compose.delegated-operators.yaml \ +# -f compose.delegated-operators.gpu.yaml \ +# -f compose.telemetry.yaml \ +# -f compose.telemetry.delegated-operators.yaml \ +# up -d +# +# For Proxy Server instructions please see +# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies +services: + teams-do-gpu: + image: voxel51/fiftyone-teams-cv-full:v2.17.1 + profiles: ["gpu"] + shm_size: "8g" + command: > + /bin/sh -c "fiftyone delegated launch -t remote -m -n teams-do-gpu" + environment: + API_URL: ${API_URL} + FIFTYONE_DATABASE_ADMIN: "false" + FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} + FIFTYONE_ENCRYPTION_KEY: ${FIFTYONE_ENCRYPTION_KEY} + FIFTYONE_INTERNAL_SERVICE: "true" + FIFTYONE_MEDIA_CACHE_SIZE_BYTES: -1 + FIFTYONE_PLUGINS_DIR: /opt/plugins + NVIDIA_VISIBLE_DEVICES: ${NVIDIA_VISIBLE_DEVICES:-all} + NVIDIA_DRIVER_CAPABILITIES: ${NVIDIA_DRIVER_CAPABILITIES:-compute,utility} + deploy: + replicas: 1 + resources: + reservations: + devices: + - driver: nvidia + count: ${NVIDIA_GPU_COUNT:-1} + capabilities: ["gpu"] + volumes: + - plugins-vol:/opt/plugins:ro + - telemetry-socket-gpu:/tmp/telemetry + restart: always + + teams-do-gpu-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-do-gpu" + profiles: ["gpu"] + cap_add: + - SYS_PTRACE + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: ${NVIDIA_GPU_COUNT:-1} + capabilities: ["gpu"] + environment: + NVIDIA_VISIBLE_DEVICES: ${NVIDIA_VISIBLE_DEVICES:-all} + NVIDIA_DRIVER_CAPABILITIES: ${NVIDIA_DRIVER_CAPABILITIES:-compute,utility} + POD_NAME: teams-do-gpu + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: delegated-operator + TARGET_NAME: ${TEAMS_DO_TARGET_NAME:-fiftyone delegated} + EXECUTOR_SIDECAR: "true" + TELEMETRY_SOCKET: /tmp/telemetry/agent.sock + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} + FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + volumes: + - telemetry-socket-gpu:/tmp/telemetry + depends_on: + teams-do-gpu: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + +volumes: + plugins-vol: + telemetry-socket-gpu: diff --git a/docker/legacy-auth/compose.telemetry.delegated-operators.yaml b/docker/legacy-auth/compose.telemetry.delegated-operators.yaml index 0a42f4c7c..f6a1c7d51 100644 --- a/docker/legacy-auth/compose.telemetry.delegated-operators.yaml +++ b/docker/legacy-auth/compose.telemetry.delegated-operators.yaml @@ -36,6 +36,8 @@ services: teams-do-telemetry: image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} pid: "service:teams-do" + cap_add: + - SYS_PTRACE environment: POD_NAME: teams-do POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} @@ -49,8 +51,11 @@ services: volumes: - telemetry-socket:/tmp/telemetry depends_on: - - teams-do - - telemetry-redis + teams-do: + condition: service_started + restart: true + telemetry-redis: + condition: service_started restart: always volumes: diff --git a/docker/legacy-auth/compose.telemetry.plugins.yaml b/docker/legacy-auth/compose.telemetry.plugins.yaml new file mode 100644 index 000000000..ac1de8bf2 --- /dev/null +++ b/docker/legacy-auth/compose.telemetry.plugins.yaml @@ -0,0 +1,37 @@ +--- +# Telemetry overlay for the dedicated teams-plugins service. The sidecar +# shares the target's PID namespace via `pid: "service:teams-plugins"`. +# +# Layered after compose.dedicated-plugins.yaml + compose.telemetry.yaml: +# +# docker compose -f compose.yaml \ +# -f compose.dedicated-plugins.yaml \ +# -f compose.telemetry.yaml \ +# -f compose.telemetry.plugins.yaml \ +# up -d +# +# For Proxy Server instructions please see +# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies +services: + teams-plugins: + environment: + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + + teams-plugins-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-plugins" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: teams-plugins + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: teams-plugins + TARGET_NAME: ${TEAMS_PLUGINS_TARGET_NAME:-hypercorn} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + teams-plugins: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always diff --git a/docker/legacy-auth/compose.telemetry.yaml b/docker/legacy-auth/compose.telemetry.yaml index e21212548..89b9d57a4 100644 --- a/docker/legacy-auth/compose.telemetry.yaml +++ b/docker/legacy-auth/compose.telemetry.yaml @@ -15,7 +15,6 @@ # For Proxy Server instructions please see # https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies services: - # Redis backend for telemetry samples and log streams. telemetry-redis: image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} command: @@ -33,9 +32,6 @@ services: volumes: - telemetry-redis-data:/data - # Inject the Redis URL onto the services that read/write telemetry. - # teams-api serves the /telemetry Sanic blueprint; teams-app serves - # /api/telemetry/stream + /api/telemetry/logs SSE endpoints. fiftyone-app: environment: FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} @@ -55,6 +51,8 @@ services: fiftyone-app-telemetry: image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} pid: "service:fiftyone-app" + cap_add: + - SYS_PTRACE environment: POD_NAME: fiftyone-app POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} @@ -62,22 +60,30 @@ services: TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} depends_on: - - fiftyone-app - - telemetry-redis + fiftyone-app: + condition: service_started + restart: true + telemetry-redis: + condition: service_started restart: always teams-api-telemetry: image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} pid: "service:teams-api" + cap_add: + - SYS_PTRACE environment: POD_NAME: teams-api POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} SERVICE_TYPE: teams-api - TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone.teams.api} + TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} depends_on: - - teams-api - - telemetry-redis + teams-api: + condition: service_started + restart: true + telemetry-redis: + condition: service_started restart: always volumes: diff --git a/docker/legacy-auth/env.template b/docker/legacy-auth/env.template index 7d8217c03..ef62237a1 100644 --- a/docker/legacy-auth/env.template +++ b/docker/legacy-auth/env.template @@ -124,5 +124,6 @@ FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=3 # TARGET_NAME overrides — substrings the sidecar matches against cmdlines # to locate each target process. Defaults work for stock images. # FIFTYONE_APP_TARGET_NAME=hypercorn -# TEAMS_API_TARGET_NAME=fiftyone.teams.api +# TEAMS_API_TARGET_NAME=fiftyone-teams-api +# TEAMS_PLUGINS_TARGET_NAME=hypercorn # TEAMS_DO_TARGET_NAME=fiftyone delegated diff --git a/docs/orchestrators/configuring-kubernetes-orchestrator.md b/docs/orchestrators/configuring-kubernetes-orchestrator.md index ce1317c9a..a6f8d6bd8 100644 --- a/docs/orchestrators/configuring-kubernetes-orchestrator.md +++ b/docs/orchestrators/configuring-kubernetes-orchestrator.md @@ -25,6 +25,7 @@ - [Template Storage Options](#template-storage-options) - [Secrets Options](#secrets-options) - [Separate CPU and GPU Templates](#separate-cpu-and-gpu-templates) +- [Optional: Telemetry Sidecar](#optional-telemetry-sidecar) - [Refresh Orchestrator Operators](#refresh-orchestrator-operators) - [Additional Considerations](#additional-considerations) - [Credential Rotation](#credential-rotation) @@ -459,6 +460,99 @@ fom.register_orchestrator( ) ``` +## Optional: Telemetry Sidecar + +If your deployment runs the telemetry overlay (see the helm chart's +`extraContainers` / `nativeSidecarContainers` options or the docker +`compose.telemetry.yaml`), you can attach a per-Job telemetry sidecar to +on-demand Kubernetes orchestrators as well. This emits per-operation +metrics back to the same Redis backend so the Settings → Metrics page +sees individual delegated runs. + +Use a Kubernetes +[native sidecar](https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/) +(an `initContainer` with `restartPolicy: Always`, requires Kubernetes +1.29+). A regular +sidecar container would block Job completion — the Job stays in +`Running` until every container exits. Native sidecars are +auto-terminated by the kubelet when all non-sidecar containers +complete, so the Job finalizes cleanly. + +Add the following to your Job template's Pod spec: + +```yaml +spec: + shareProcessNamespace: true + initContainers: + - name: telemetry-sidecar + image: voxel51/telemetry-sidecar:latest + restartPolicy: Always + securityContext: + capabilities: + add: [SYS_PTRACE] + env: + - name: TARGET_NAME + value: delegated + - name: SERVICE_TYPE + value: delegated-operator + - name: EXECUTOR_SIDECAR + value: "true" + - name: TELEMETRY_SOCKET + value: /tmp/telemetry/agent.sock + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: FIFTYONE_TELEMETRY_REDIS_URL + value: redis://telemetry-redis.fiftyone.svc.cluster.local:6379 + - name: FIFTYONE_DATABASE_URI + valueFrom: + secretKeyRef: + name: fiftyone-secrets + key: database-uri + - name: FIFTYONE_DATABASE_NAME + valueFrom: + secretKeyRef: + name: fiftyone-secrets + key: database-name + volumeMounts: + - mountPath: /tmp/telemetry + name: telemetry-socket + containers: + - name: task-worker + # ... existing container config ... + env: + # ... existing env, plus: + - name: TELEMETRY_SOCKET + value: /tmp/telemetry/agent.sock + - name: FIFTYONE_TELEMETRY_REDIS_URL + value: redis://telemetry-redis.fiftyone.svc.cluster.local:6379 + volumeMounts: + # ... existing mounts, plus: + - mountPath: /tmp/telemetry + name: telemetry-socket + volumes: + - name: telemetry-socket + emptyDir: {} +``` + +Notes: + +- `shareProcessNamespace: true` lets the sidecar's psutil call see the + worker process via `/proc/` in the shared PID namespace. +- `SYS_PTRACE` is required so py-spy can attach to the worker. +- `EXECUTOR_SIDECAR=true` switches the sidecar into per-operation mode; + the worker writes execution metadata to `TELEMETRY_SOCKET` and the + sidecar records per-op metrics into the `delegated_ops` MongoDB + document. +- The `FIFTYONE_TELEMETRY_REDIS_URL` must be reachable from wherever + the Job runs. For same-cluster Jobs use the in-cluster service DNS + name. For remote clusters, use a routable hostname or load balancer. + ## Refresh Orchestrator Operators This step is only required if you've added a plugin directory with custom @@ -542,6 +636,53 @@ spec: serviceAccountName: your-org-fiftyone-teams podSecurityContext: runAsNonRoot: false + shareProcessNamespace: true + initContainers: + - name: telemetry-sidecar + image: voxel51/telemetry-sidecar:latest + restartPolicy: Always + securityContext: + capabilities: + add: [SYS_PTRACE] + env: + - name: TARGET_NAME + value: delegated + - name: SERVICE_TYPE + value: delegated-operator + - name: EXECUTOR_SIDECAR + value: "true" + - name: TELEMETRY_SOCKET + value: /tmp/telemetry/agent.sock + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: FIFTYONE_TELEMETRY_REDIS_URL + value: redis://telemetry-redis.your-org-fiftyone-ai.svc.cluster.local:6379 + - name: FIFTYONE_DATABASE_URI + valueFrom: + secretKeyRef: + key: mongodbConnectionString + name: your-org-teams-secrets + - name: FIFTYONE_DATABASE_NAME + valueFrom: + secretKeyRef: + key: fiftyoneDatabaseName + name: your-org-teams-secrets + resources: + limits: + cpu: 50m + memory: 512Mi + requests: + cpu: 50m + memory: 512Mi + volumeMounts: + - mountPath: /tmp/telemetry + name: telemetry-socket containers: - name: task-worker image: registry/image:tag @@ -585,8 +726,12 @@ spec: value: "true" - name: FIFTYONE_PLUGINS_DIR value: /opt/plugins + - name: FIFTYONE_TELEMETRY_REDIS_URL + value: redis://telemetry-redis.your-org-fiftyone-ai.svc.cluster.local:6379 - name: NUMBA_CACHE_DIR value: /tmp/numba + - name: TELEMETRY_SOCKET + value: /tmp/telemetry/agent.sock - name: TORCH_HOME value: /opt/fiftyone_zoo/your-org/torch resources: @@ -618,6 +763,8 @@ spec: name: memory-media-cache-vol - mountPath: /dev/shm name: shm-vol + - mountPath: /tmp/telemetry + name: telemetry-socket volumes: - name: nfs-plugins-ro-vol persistentVolumeClaim: @@ -652,5 +799,13 @@ spec: medium: Memory sizeLimit: 2Gi name: shm-vol + - emptyDir: {} + name: telemetry-socket restartPolicy: Never ``` + +The `telemetry-sidecar` init container above is optional. Remove it +(plus `shareProcessNamespace: true`, the `TELEMETRY_SOCKET` / +`FIFTYONE_TELEMETRY_REDIS_URL` env vars on `task-worker`, and the +`telemetry-socket` volume + mount) if you are not running the +telemetry overlay. diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 8b066e31c..a578c9f5e 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -728,6 +728,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | apiSettings.env.FIFTYONE_LOGGING_FORMAT | string | `"text"` | The format to use for log messages. Can be one of `json` or `text`. | | apiSettings.env.GRAPHQL_DEFAULT_LIMIT | int | `10` | Default number of returned items when listing in GraphQL queries. Can be overridden in the request. | | apiSettings.env.LOGGING_LEVEL | string | `"INFO"` | Logging level. Overrides the value of `FIFTYONE_ENV`. Can be one of "DEBUG", "INFO", "WARN", "ERROR", or "CRITICAL". | +| apiSettings.extraContainers | list | `[]` | Additional containers to run in the `teams-api` pod alongside the main container. When non-empty, the pod is configured with `shareProcessNamespace: true`. [Reference][sidecar-containers]. | | apiSettings.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | | apiSettings.image.repository | string | `"voxel51/fiftyone-teams-api"` | Container image for the `teams-api`. | | apiSettings.image.tag | string | `""` | Image tag for `teams-api`. Defaults to the chart version. | @@ -740,6 +741,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | apiSettings.liveness.failureThreshold | int | `5` | Number of times to retry the liveness probe for the `teams-api`. [Reference][probes]. | | apiSettings.liveness.periodSeconds | int | `15` | How often (in seconds) to perform the liveness probe for `teams-api`. [Reference][probes]. | | apiSettings.liveness.timeoutSeconds | int | `5` | Number of seconds after which the liveness probe times out for the `teams-api`. [Reference][probes]. | +| apiSettings.nativeSidecarContainers | list | `[]` | Native sidecar containers for the `teams-api` pod (Kubernetes 1.29+). Entries are rendered under `initContainers`; set `restartPolicy: Always` on each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers]. | | apiSettings.nodeSelector | object | `{}` | nodeSelector for `teams-api`. [Reference][node-selector]. | | apiSettings.podAnnotations | object | `{}` | Annotations for pods for `teams-api`. [Reference][annotations]. | | apiSettings.podDisruptionBudget | object | `{"enabled":false,"minAvailable":null}` | Pod Disruption Budget for pods for `teams-api`. [Reference][pod-disruption-budget]. | @@ -795,6 +797,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | appSettings.env.FIFTYONE_MEDIA_CACHE_APP_IMAGES | bool | `false` | Controls whether cloud media images will be downloaded and added to the local cache upon viewing media in the app. | | appSettings.env.FIFTYONE_MEDIA_CACHE_SIZE_BYTES | int | `-1` | Set the media cache size (in bytes) for the local FiftyOne App processes. When not set, the app's default value is 32 GiB. `-1` disables the media cache garbage collection (and cache size is unlimited). | | appSettings.env.FIFTYONE_SIGNED_URL_EXPIRATION | int | `24` | Set the time-to-live for signed URLs generated by the application in hours | +| appSettings.extraContainers | list | `[]` | Additional containers to run in the `fiftyone-app` pod alongside the main container. When non-empty, the pod is configured with `shareProcessNamespace: true`. [Reference][sidecar-containers]. | | appSettings.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | | appSettings.image.repository | string | `"voxel51/fiftyone-app"` | Container image for `fiftyone-app`. | | appSettings.image.tag | string | `""` | Image tag for `fiftyone-app`. Defaults to the chart version. | @@ -807,6 +810,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | appSettings.liveness.failureThreshold | int | `5` | Number of times to retry the liveness probe for the `fiftyone-app`. [Reference][probes]. | | appSettings.liveness.periodSeconds | int | `15` | How often (in seconds) to perform the liveness probe for `fiftyone-app`. [Reference][probes]. | | appSettings.liveness.timeoutSeconds | int | `5` | Timeout for the liveness probe for the `fiftyone-app`. [Reference][probes]. | +| appSettings.nativeSidecarContainers | list | `[]` | Native sidecar containers for the `fiftyone-app` pod (Kubernetes 1.29+). Entries are rendered under `initContainers`; set `restartPolicy: Always` on each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers]. | | appSettings.nodeSelector | object | `{}` | nodeSelector for `fiftyone-app`. [Reference][node-selector]. | | appSettings.podAnnotations | object | `{}` | Annotations for pods for `fiftyone-app`. [Reference][annotations]. | | appSettings.podDisruptionBudget | object | `{"enabled":false,"minAvailable":null}` | Pod Disruption Budget for pods for `fiftyone-app`. [Reference][pod-disruption-budget]. | @@ -885,13 +889,14 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | casSettings.volumes | list | `[]` | Volumes for `teams-cas`. [Reference][volumes]. | | delegatedOperatorDeployments.deployments | object | `{"teamsDoCpuDefault":{"enabled":false,"env":{"FIFTYONE_MEDIA_CACHE_DIR":"/opt/media_cache","FIFTYONE_MEDIA_CACHE_SIZE_BYTES":"2147483648"},"replicaCount":1,"resources":{"limits":{"cpu":4,"ephemeral-storage":"1Gi","memory":"16Gi"},"requests":{"cpu":4,"ephemeral-storage":"1Gi","memory":"16Gi"}},"volumeMounts":[{"mountPath":"/dev/shm","name":"shm-vol"},{"mountPath":"/opt/media_cache","name":"memory-media-cache-vol"}],"volumes":[{"emptyDir":{"medium":"Memory","sizeLimit":"2Gi"},"name":"shm-vol"},{"emptyDir":{"medium":"Memory","sizeLimit":"2.5Gi"},"name":"memory-media-cache-vol"}]}}` | Additional deployments to configure. Each template will use .Values.delegatedOperatorDeployments.template as a base. Each template value may be overridden. Maps/dictionaries will be merged key-wise, with the deployment instance taking precedence. List values will not be merged, but be overridden completely by the deployment instance. | | delegatedOperatorDeployments.deployments.teamsDoCpuDefault | object | `{"enabled":false,"env":{"FIFTYONE_MEDIA_CACHE_DIR":"/opt/media_cache","FIFTYONE_MEDIA_CACHE_SIZE_BYTES":"2147483648"},"replicaCount":1,"resources":{"limits":{"cpu":4,"ephemeral-storage":"1Gi","memory":"16Gi"},"requests":{"cpu":4,"ephemeral-storage":"1Gi","memory":"16Gi"}},"volumeMounts":[{"mountPath":"/dev/shm","name":"shm-vol"},{"mountPath":"/opt/media_cache","name":"memory-media-cache-vol"}],"volumes":[{"emptyDir":{"medium":"Memory","sizeLimit":"2Gi"},"name":"shm-vol"},{"emptyDir":{"medium":"Memory","sizeLimit":"2.5Gi"},"name":"memory-media-cache-vol"}]}` | Default (CPU-only) delegated operator runner. Defaults to an 4vCPU, 16Gi RAM runner. The deployment is backed with a 2.5Gi in-memory volume for caching media and an extra 2Gi of shared memory. | -| delegatedOperatorDeployments.template | object | `{"affinity":{},"deploymentAnnotations":{},"description":"","env":{"FIFTYONE_DELEGATED_OPERATION_LOG_PATH":"","FIFTYONE_INTERNAL_SERVICE":true,"FIFTYONE_MEDIA_CACHE_SIZE_BYTES":-1},"image":{"pullPolicy":"Always","repository":"voxel51/fiftyone-teams-cv-full","tag":""},"labels":{},"liveness":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"nodeSelector":{},"podAnnotations":{},"podDisruptionBudget":{"enabled":false,"minAvailable":null},"podSecurityContext":{},"readiness":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"replicaCount":3,"resources":{"limits":{},"requests":{}},"secretEnv":{},"securityContext":{},"startup":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"tolerations":[],"topologySpreadConstraints":[],"updateStrategy":{"type":"RollingUpdate"},"volumeMounts":[],"volumes":[]}` | A common template applied to all deployments. Each deployment can then override individual fields as needed by the operator. | +| delegatedOperatorDeployments.template | object | `{"affinity":{},"deploymentAnnotations":{},"description":"","env":{"FIFTYONE_DELEGATED_OPERATION_LOG_PATH":"","FIFTYONE_INTERNAL_SERVICE":true,"FIFTYONE_MEDIA_CACHE_SIZE_BYTES":-1},"extraContainers":[],"image":{"pullPolicy":"Always","repository":"voxel51/fiftyone-teams-cv-full","tag":""},"labels":{},"liveness":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"nativeSidecarContainers":[],"nodeSelector":{},"podAnnotations":{},"podDisruptionBudget":{"enabled":false,"minAvailable":null},"podSecurityContext":{},"readiness":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"replicaCount":3,"resources":{"limits":{},"requests":{}},"secretEnv":{},"securityContext":{},"startup":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"tolerations":[],"topologySpreadConstraints":[],"updateStrategy":{"type":"RollingUpdate"},"volumeMounts":[],"volumes":[]}` | A common template applied to all deployments. Each deployment can then override individual fields as needed by the operator. | | delegatedOperatorDeployments.template.affinity | object | `{}` | Affinity and anti-affinity for `delegated-operator-executor`. [Reference][affinity]. | | delegatedOperatorDeployments.template.deploymentAnnotations | object | `{}` | Annotations for the `teams-do` deployment. [Reference][annotations]. | | delegatedOperatorDeployments.template.description | string | `""` | A description for the delegated operator instance. This is unused in the template context. Each operator should either set their own description or, optionally, use the default. If unset at the operator context, it will be defaulted to `Long running operations delegated to $name` where `$name` is the name of the Deployment object. | | delegatedOperatorDeployments.template.env.FIFTYONE_DELEGATED_OPERATION_LOG_PATH | string | `""` | Full path to a network-mounted file system or a cloud storage path to use for storing logs generated by delegated operation runs, one file per job. The default `""` means log upload is disabled. | | delegatedOperatorDeployments.template.env.FIFTYONE_INTERNAL_SERVICE | bool | `true` | Whether the SDK is running in an internal service context. When running in FiftyOne Enterprise, set to `true`. | | delegatedOperatorDeployments.template.env.FIFTYONE_MEDIA_CACHE_SIZE_BYTES | int | `-1` | Set the media cache size (in bytes) for the local FiftyOne Delegated Operator Executor processes. When not set, the app's default value is 32 GiB. `-1` disables the media cache garbage collection (and cache size is unlimited). | +| delegatedOperatorDeployments.template.extraContainers | list | `[]` | Additional containers to run in each delegated-operator-executor pod alongside the main container. When non-empty, the pod is configured with `shareProcessNamespace: true`. [Reference][sidecar-containers]. | | delegatedOperatorDeployments.template.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | | delegatedOperatorDeployments.template.image.repository | string | `"voxel51/fiftyone-teams-cv-full"` | Container image for `delegated-operator-executor`. | | delegatedOperatorDeployments.template.image.tag | string | `""` | Image tag for `delegated-operator-executor`. Defaults to the chart version. | @@ -899,6 +904,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | delegatedOperatorDeployments.template.liveness.failureThreshold | int | `5` | Number of times to retry the liveness probe for the `teams-do`. [Reference][probes]. | | delegatedOperatorDeployments.template.liveness.periodSeconds | int | `30` | How often (in seconds) to perform the liveness probe for `teams-do`. [Reference][probes]. | | delegatedOperatorDeployments.template.liveness.timeoutSeconds | int | `30` | Timeout for the liveness probe for the `teams-do`. [Reference][probes]. | +| delegatedOperatorDeployments.template.nativeSidecarContainers | list | `[]` | Native sidecar containers for delegated-operator-executor pods (Kubernetes 1.29+). Entries are rendered under `initContainers`; set `restartPolicy: Always` on each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers]. | | delegatedOperatorDeployments.template.nodeSelector | object | `{}` | nodeSelector for `delegated-operator-executor`. [Reference][node-selector]. | | delegatedOperatorDeployments.template.podAnnotations | object | `{}` | Annotations for delegated-operator-executor pods. [Reference][annotations]. | | delegatedOperatorDeployments.template.podDisruptionBudget.enabled | bool | `false` | Whether a pod disruption budget is enabled for `teams-plugins`. | @@ -924,7 +930,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | delegatedOperatorJobTemplates.configMap.labels | object | `{}` | Additional labels for the generated `ConfigMap`. [Reference][labels-and-selectors]. | | delegatedOperatorJobTemplates.configMap.name | string | `""` | Name of the `ConfigMap` (existing or to be created) in the namespace `namespace.name` used for DO templates. Defaults to `release-name-fiftyone-teams-app-do-templates`. | | delegatedOperatorJobTemplates.jobs | object | `{}` | On-Demand Delegated Operator Jobs. | -| delegatedOperatorJobTemplates.template | object | `{"activeDeadlineSeconds":null,"affinity":{},"backoffLimit":null,"completions":null,"containerSecurityContext":{},"env":{"FIFTYONE_DELEGATED_OPERATION_LOG_PATH":"","FIFTYONE_MEDIA_CACHE_SIZE_BYTES":-1},"image":{"pullPolicy":"Always","repository":"voxel51/fiftyone-teams-cv-full","tag":""},"jobAnnotations":{},"labels":{},"nodeSelector":{},"podAnnotations":{},"podSecurityContext":{},"resources":{"limits":{},"requests":{}},"secretEnv":{},"tolerations":[],"ttlSecondsAfterFinished":null,"volumeMounts":[],"volumes":[]}` | A common template applied to all deployments. Each deployment can then override individual fields as needed by the operator. | +| delegatedOperatorJobTemplates.template | object | `{"activeDeadlineSeconds":null,"affinity":{},"backoffLimit":null,"completions":null,"containerSecurityContext":{},"env":{"FIFTYONE_DELEGATED_OPERATION_LOG_PATH":"","FIFTYONE_MEDIA_CACHE_SIZE_BYTES":-1},"extraContainers":[],"image":{"pullPolicy":"Always","repository":"voxel51/fiftyone-teams-cv-full","tag":""},"jobAnnotations":{},"labels":{},"nativeSidecarContainers":[],"nodeSelector":{},"podAnnotations":{},"podSecurityContext":{},"resources":{"limits":{},"requests":{}},"secretEnv":{},"tolerations":[],"ttlSecondsAfterFinished":null,"volumeMounts":[],"volumes":[]}` | A common template applied to all deployments. Each deployment can then override individual fields as needed by the operator. | | delegatedOperatorJobTemplates.template.activeDeadlineSeconds | optional | `nil` | Maximum of seconds a job should be able to run. [Reference][job-termination-and-cleanup]. | | delegatedOperatorJobTemplates.template.affinity | object | `{}` | Affinity and anti-affinity for `delegated-operator-executor`. [Reference][affinity]. | | delegatedOperatorJobTemplates.template.backoffLimit | optional | `nil` | Amount of retries for the job to try before being marked as "Failed". [Reference][job-backoff-failure-policy]. | @@ -932,11 +938,13 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | delegatedOperatorJobTemplates.template.containerSecurityContext | object | `{}` | Container security configuration for generated delegated-operator containers. [Reference][container-security-context]. | | delegatedOperatorJobTemplates.template.env.FIFTYONE_DELEGATED_OPERATION_LOG_PATH | string | `""` | Full path to a network-mounted file system or a cloud storage path to use for storing logs generated by delegated operation runs, one file per job. The default `""` means log upload is disabled. | | delegatedOperatorJobTemplates.template.env.FIFTYONE_MEDIA_CACHE_SIZE_BYTES | int | `-1` | Set the media cache size (in bytes) for the local FiftyOne Delegated Operator Executor processes. When not set, the app's default value is 32 GiB. `-1` disables the media cache garbage collection (and cache size is unlimited). | +| delegatedOperatorJobTemplates.template.extraContainers | list | `[]` | Additional containers to run in each on-demand delegated-operator Job pod alongside the executor container. When non-empty, the pod is configured with `shareProcessNamespace: true`. Note: a regular container that does not exit will block Job completion; for telemetry sidecars use `nativeSidecarContainers` instead. [Reference][sidecar-containers]. | | delegatedOperatorJobTemplates.template.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | | delegatedOperatorJobTemplates.template.image.repository | string | `"voxel51/fiftyone-teams-cv-full"` | Container image for `delegated-operator-executor`. | | delegatedOperatorJobTemplates.template.image.tag | string | `""` | Image tag for `delegated-operator-executor`. Defaults to the chart version. | | delegatedOperatorJobTemplates.template.jobAnnotations | object | `{}` | Annotations for the `teams-do` deployment. [Reference][annotations]. | | delegatedOperatorJobTemplates.template.labels | object | `{}` | Additional labels for the `delegated-operator-executor` related objects. [Reference][labels-and-selectors]. | +| delegatedOperatorJobTemplates.template.nativeSidecarContainers | list | `[]` | Native sidecar containers for on-demand delegated-operator Job pods (Kubernetes 1.29+). Entries are rendered under `initContainers`; set `restartPolicy: Always` on each entry to enable the native sidecar lifecycle. Kubelet auto-terminates these when the executor container finishes, so the Job completes cleanly. [Reference][sidecar-containers]. | | delegatedOperatorJobTemplates.template.nodeSelector | object | `{}` | nodeSelector for `delegated-operator-executor`. [Reference][node-selector]. | | delegatedOperatorJobTemplates.template.podAnnotations | object | `{}` | Annotations for delegated-operator-executor pods. [Reference][annotations]. | | delegatedOperatorJobTemplates.template.podSecurityContext | object | `{}` | Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context]. | @@ -977,6 +985,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | pluginsSettings.env.FIFTYONE_INTERNAL_SERVICE | bool | `true` | Whether the SDK is running in an internal service context. When running in FiftyOne Enterprise, set to `true`. | | pluginsSettings.env.FIFTYONE_MEDIA_CACHE_APP_IMAGES | bool | `false` | Controls whether cloud media images will be downloaded and added to the local cache upon viewing media in the app. | | pluginsSettings.env.FIFTYONE_MEDIA_CACHE_SIZE_BYTES | int | `-1` | Set the media cache size (in bytes) for the local FiftyOne Plugins processes. When not set, the app's default value is 32 GiB. `-1` disables the media cache garbage collection (and cache size is unlimited). | +| pluginsSettings.extraContainers | list | `[]` | Additional containers to run in the `teams-plugins` pod alongside the main container. When non-empty, the pod is configured with `shareProcessNamespace: true`. [Reference][sidecar-containers]. | | pluginsSettings.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | | pluginsSettings.image.repository | string | `"voxel51/fiftyone-app"` | Container image for `teams-plugins`. | | pluginsSettings.image.tag | string | `""` | Image tag for `teams-plugins`. Defaults to the chart version. | @@ -989,6 +998,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | pluginsSettings.liveness.failureThreshold | int | `5` | Number of times to retry the liveness probe for the `teams-plugins`. [Reference][probes]. | | pluginsSettings.liveness.periodSeconds | int | `15` | How often (in seconds) to perform the liveness probe for `teams-plugins`. [Reference][probes]. | | pluginsSettings.liveness.timeoutSeconds | int | `5` | Timeout for the liveness probe for the `teams-plugins`. [Reference][probes]. | +| pluginsSettings.nativeSidecarContainers | list | `[]` | Native sidecar containers for the `teams-plugins` pod (Kubernetes 1.29+). Entries are rendered under `initContainers`; set `restartPolicy: Always` on each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers]. | | pluginsSettings.nodeSelector | object | `{}` | nodeSelector for `teams-plugins`. [Reference][node-selector]. | | pluginsSettings.podAnnotations | object | `{}` | Annotations for `teams-plugins` pods. [Reference][annotations]. | | pluginsSettings.podDisruptionBudget | object | `{"enabled":false,"minAvailable":null}` | Pod Disruption Budget for pods for `teams-plugins`. [Reference][pod-disruption-budget]. | @@ -1119,6 +1129,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): [security-context]: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ [service-account]: https://kubernetes.io/docs/concepts/security/service-accounts/ [service-type]: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types +[sidecar-containers]: https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/ [taints-and-tolerations]: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ [topology-spread-constraints]: https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ [upgrade-strategies]: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy diff --git a/helm/fiftyone-teams-app/README.md.gotmpl b/helm/fiftyone-teams-app/README.md.gotmpl index cbf38123b..c350c8138 100644 --- a/helm/fiftyone-teams-app/README.md.gotmpl +++ b/helm/fiftyone-teams-app/README.md.gotmpl @@ -754,6 +754,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): [security-context]: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ [service-account]: https://kubernetes.io/docs/concepts/security/service-accounts/ [service-type]: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types +[sidecar-containers]: https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/ [taints-and-tolerations]: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ [topology-spread-constraints]: https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ [upgrade-strategies]: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy diff --git a/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml b/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml index 3ce459bdc..36ea5241b 100644 --- a/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml +++ b/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml @@ -42,6 +42,8 @@ data: {{- $mergedTolerations := $jobConfig.tolerations | default $baseTpl.tolerations }} {{- $mergedVolumeMounts := $jobConfig.volumeMounts | default $baseTpl.volumeMounts }} {{- $mergedVolumes := $jobConfig.volumes | default $baseTpl.volumes }} + {{- $mergedExtraContainers := $jobConfig.extraContainers | default $baseTpl.extraContainers }} + {{- $mergedNativeSidecarContainers := $jobConfig.nativeSidecarContainers | default $baseTpl.nativeSidecarContainers }} {{- /* Trunc at 48 for the random ID to be generated via API */ -}} {{- $metadataName := kebabcase $jobName | trunc 48 }} @@ -87,6 +89,9 @@ data: spec: restartPolicy: {{ $jobConfig.restartPolicy | default $baseTpl.restartPolicy | default "Never" }} serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" $ }} + {{- if or $mergedExtraContainers $mergedNativeSidecarContainers }} + shareProcessNamespace: true + {{- end }} {{- with $mergedPodSecurityContext }} securityContext: {{- toYaml . | nindent 12 }} @@ -119,6 +124,13 @@ data: volumeMounts: {{- toYaml . | nindent 16 }} {{- end }} + {{- with $mergedExtraContainers }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with $mergedNativeSidecarContainers }} + initContainers: + {{- toYaml . | nindent 12 }} + {{- end }} {{- with $mergedNodeSelector }} nodeSelector: {{- toYaml . | nindent 12 }} diff --git a/helm/fiftyone-teams-app/values.schema.json b/helm/fiftyone-teams-app/values.schema.json index 0e25880c7..099a33f9c 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -102,6 +102,15 @@ "title": "env", "type": "object" }, + "extraContainers": { + "description": "Additional containers to run in the `teams-api` pod alongside the\nmain container. When non-empty, the pod is configured with\n`shareProcessNamespace: true`. [Reference][sidecar-containers].", + "items": { + "required": [] + }, + "required": [], + "title": "extraContainers", + "type": "array" + }, "image": { "properties": { "pullPolicy": { @@ -328,6 +337,15 @@ "title": "liveness", "type": "object" }, + "nativeSidecarContainers": { + "description": "Native sidecar containers for the `teams-api` pod (Kubernetes 1.29+).\nEntries are rendered under `initContainers`; set `restartPolicy: Always`\non each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers].", + "items": { + "required": [] + }, + "required": [], + "title": "nativeSidecarContainers", + "type": "array" + }, "nodeSelector": { "description": "nodeSelector for `teams-api`. [Reference][node-selector].", "required": [], @@ -794,6 +812,15 @@ "title": "env", "type": "object" }, + "extraContainers": { + "description": "Additional containers to run in the `fiftyone-app` pod alongside the\nmain container. When non-empty, the pod is configured with\n`shareProcessNamespace: true`. [Reference][sidecar-containers].", + "items": { + "required": [] + }, + "required": [], + "title": "extraContainers", + "type": "array" + }, "image": { "properties": { "pullPolicy": { @@ -1016,6 +1043,15 @@ "title": "liveness", "type": "object" }, + "nativeSidecarContainers": { + "description": "Native sidecar containers for the `fiftyone-app` pod (Kubernetes 1.29+).\nEntries are rendered under `initContainers`; set `restartPolicy: Always`\non each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers].", + "items": { + "required": [] + }, + "required": [], + "title": "nativeSidecarContainers", + "type": "array" + }, "nodeSelector": { "description": "nodeSelector for `fiftyone-app`. [Reference][node-selector].", "required": [], @@ -2025,6 +2061,15 @@ "title": "env", "type": "object" }, + "extraContainers": { + "description": "Additional containers to run in each delegated-operator-executor pod\nalongside the main container. When non-empty, the pod is configured\nwith `shareProcessNamespace: true`. [Reference][sidecar-containers].", + "items": { + "required": [] + }, + "required": [], + "title": "extraContainers", + "type": "array" + }, "image": { "properties": { "pullPolicy": { @@ -2091,6 +2136,15 @@ "title": "liveness", "type": "object" }, + "nativeSidecarContainers": { + "description": "Native sidecar containers for delegated-operator-executor pods\n(Kubernetes 1.29+). Entries are rendered under `initContainers`; set\n`restartPolicy: Always` on each entry to enable the native sidecar\nlifecycle. [Reference][sidecar-containers].", + "items": { + "required": [] + }, + "required": [], + "title": "nativeSidecarContainers", + "type": "array" + }, "nodeSelector": { "description": "nodeSelector for `delegated-operator-executor`. [Reference][node-selector].", "required": [], @@ -2385,6 +2439,15 @@ "title": "env", "type": "object" }, + "extraContainers": { + "description": "Additional containers to run in each on-demand delegated-operator Job\npod alongside the executor container. When non-empty, the pod is\nconfigured with `shareProcessNamespace: true`. Note: a regular\ncontainer that does not exit will block Job completion; for telemetry\nsidecars use `nativeSidecarContainers` instead. [Reference][sidecar-containers].", + "items": { + "required": [] + }, + "required": [], + "title": "extraContainers", + "type": "array" + }, "image": { "properties": { "pullPolicy": { @@ -2432,6 +2495,15 @@ "title": "labels", "type": "object" }, + "nativeSidecarContainers": { + "description": "Native sidecar containers for on-demand delegated-operator Job pods\n(Kubernetes 1.29+). Entries are rendered under `initContainers`; set\n`restartPolicy: Always` on each entry to enable the native sidecar\nlifecycle. Kubelet auto-terminates these when the executor container\nfinishes, so the Job completes cleanly. [Reference][sidecar-containers].", + "items": { + "required": [] + }, + "required": [], + "title": "nativeSidecarContainers", + "type": "array" + }, "nodeSelector": { "description": "nodeSelector for `delegated-operator-executor`. [Reference][node-selector].", "required": [], @@ -2791,6 +2863,15 @@ "title": "env", "type": "object" }, + "extraContainers": { + "description": "Additional containers to run in the `teams-plugins` pod alongside the\nmain container. When non-empty, the pod is configured with\n`shareProcessNamespace: true`. [Reference][sidecar-containers].", + "items": { + "required": [] + }, + "required": [], + "title": "extraContainers", + "type": "array" + }, "image": { "properties": { "pullPolicy": { @@ -3013,6 +3094,15 @@ "title": "liveness", "type": "object" }, + "nativeSidecarContainers": { + "description": "Native sidecar containers for the `teams-plugins` pod (Kubernetes 1.29+).\nEntries are rendered under `initContainers`; set `restartPolicy: Always`\non each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers].", + "items": { + "required": [] + }, + "required": [], + "title": "nativeSidecarContainers", + "type": "array" + }, "nodeSelector": { "description": "nodeSelector for `teams-plugins`. [Reference][node-selector].", "required": [], diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index 890eb6032..4107806aa 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -120,6 +120,14 @@ apiSettings: # -- Affinity and anti-affinity for `teams-api`. [Reference][affinity]. affinity: {} + # -- Additional containers to run in the `teams-api` pod alongside the + # main container. When non-empty, the pod is configured with + # `shareProcessNamespace: true`. [Reference][sidecar-containers]. + extraContainers: [] + # -- Native sidecar containers for the `teams-api` pod (Kubernetes 1.29+). + # Entries are rendered under `initContainers`; set `restartPolicy: Always` + # on each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers]. + nativeSidecarContainers: [] initContainers: # -- Container security configuration for `teams-api` `initContainers`. [Reference][container-security-context]. containerSecurityContext: @@ -390,6 +398,14 @@ appSettings: # -- Affinity and anti-affinity for `fiftyone-app`. [Reference][affinity]. affinity: {} + # -- Additional containers to run in the `fiftyone-app` pod alongside the + # main container. When non-empty, the pod is configured with + # `shareProcessNamespace: true`. [Reference][sidecar-containers]. + extraContainers: [] + # -- Native sidecar containers for the `fiftyone-app` pod (Kubernetes 1.29+). + # Entries are rendered under `initContainers`; set `restartPolicy: Always` + # on each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers]. + nativeSidecarContainers: [] initContainers: # -- Container security configuration for `fiftyone-app` `initContainers`. [Reference][container-security-context]. containerSecurityContext: @@ -820,6 +836,15 @@ delegatedOperatorDeployments: volumeMounts: [] # -- Volumes for `delegated-operator-executor`. [Reference][volumes]. volumes: [] + # -- Additional containers to run in each delegated-operator-executor pod + # alongside the main container. When non-empty, the pod is configured + # with `shareProcessNamespace: true`. [Reference][sidecar-containers]. + extraContainers: [] + # -- Native sidecar containers for delegated-operator-executor pods + # (Kubernetes 1.29+). Entries are rendered under `initContainers`; set + # `restartPolicy: Always` on each entry to enable the native sidecar + # lifecycle. [Reference][sidecar-containers]. + nativeSidecarContainers: [] # -- Additional deployments to configure. Each template # will use .Values.delegatedOperatorDeployments.template as a base. @@ -996,6 +1021,18 @@ delegatedOperatorJobTemplates: volumeMounts: [] # -- Volumes for `delegated-operator-executor`. [Reference][volumes]. volumes: [] + # -- Additional containers to run in each on-demand delegated-operator Job + # pod alongside the executor container. When non-empty, the pod is + # configured with `shareProcessNamespace: true`. Note: a regular + # container that does not exit will block Job completion; for telemetry + # sidecars use `nativeSidecarContainers` instead. [Reference][sidecar-containers]. + extraContainers: [] + # -- Native sidecar containers for on-demand delegated-operator Job pods + # (Kubernetes 1.29+). Entries are rendered under `initContainers`; set + # `restartPolicy: Always` on each entry to enable the native sidecar + # lifecycle. Kubelet auto-terminates these when the executor container + # finishes, so the Job completes cleanly. [Reference][sidecar-containers]. + nativeSidecarContainers: [] # -- On-Demand Delegated Operator Jobs. jobs: {} # Default CPU-only batch job @@ -1227,6 +1264,14 @@ pluginsSettings: # -- Affinity and anti-affinity for `teams-plugins`. [Reference][affinity]. affinity: {} + # -- Additional containers to run in the `teams-plugins` pod alongside the + # main container. When non-empty, the pod is configured with + # `shareProcessNamespace: true`. [Reference][sidecar-containers]. + extraContainers: [] + # -- Native sidecar containers for the `teams-plugins` pod (Kubernetes 1.29+). + # Entries are rendered under `initContainers`; set `restartPolicy: Always` + # on each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers]. + nativeSidecarContainers: [] initContainers: # -- Container security configuration for `teams-plugins` `initContainers`. [Reference][container-security-context]. containerSecurityContext: diff --git a/tests/unit/compose/docker-compose-internal-auth_test.go b/tests/unit/compose/docker-compose-internal-auth_test.go index 7ca111390..265aaf411 100644 --- a/tests/unit/compose/docker-compose-internal-auth_test.go +++ b/tests/unit/compose/docker-compose-internal-auth_test.go @@ -28,7 +28,9 @@ var internalAuthComposeFile = filepath.Join(dockerInternalAuthDir, "compose.yaml var internalAuthComposePluginsFile = filepath.Join(dockerInternalAuthDir, "compose.plugins.yaml") var internalAuthComposeDedicatedPluginsFile = filepath.Join(dockerInternalAuthDir, "compose.dedicated-plugins.yaml") var internalAuthComposeDelegatedOperationsFile = filepath.Join(dockerInternalAuthDir, "compose.delegated-operators.yaml") +var internalAuthComposeDelegatedOperationsGpuFile = filepath.Join(dockerInternalAuthDir, "compose.delegated-operators.gpu.yaml") var internalAuthComposeTelemetryFile = filepath.Join(dockerInternalAuthDir, "compose.telemetry.yaml") +var internalAuthComposeTelemetryPluginsFile = filepath.Join(dockerInternalAuthDir, "compose.telemetry.plugins.yaml") var internalAuthComposeTelemetryDelegatedOperatorsFile = filepath.Join(dockerInternalAuthDir, "compose.telemetry.delegated-operators.yaml") var internalAuthEnvTemplateFilePath = filepath.Join(dockerInternalAuthDir, "env.template") @@ -61,12 +63,14 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServicesNames() { name string configPaths []string // file paths to one or more Compose files. envFiles []string // file paths to ".env" files with additional environment variable data + profiles []string // compose profiles to activate expected []string }{ { "compose", []string{internalAuthComposeFile}, s.dotEnvFiles, + nil, []string{ "fiftyone-app", "teams-api", @@ -78,6 +82,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServicesNames() { "composePlugins", []string{internalAuthComposePluginsFile}, s.dotEnvFiles, + nil, []string{ "fiftyone-app", "teams-api", @@ -89,6 +94,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServicesNames() { "composeDedicatedPlugins", []string{internalAuthComposeDedicatedPluginsFile}, s.dotEnvFiles, + nil, []string{ "fiftyone-app", "teams-api", @@ -101,14 +107,30 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServicesNames() { "composeDelegatedOperations", []string{internalAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, + nil, []string{ "teams-do", }, }, + { + "composeDelegatedOperationsGpu", + []string{ + internalAuthComposeDelegatedOperationsFile, + internalAuthComposeDelegatedOperationsGpuFile, + }, + s.dotEnvFiles, + []string{"gpu"}, + []string{ + "teams-do", + "teams-do-gpu", + "teams-do-gpu-telemetry", + }, + }, { "composeTelemetry", []string{internalAuthComposeFile, internalAuthComposeTelemetryFile}, s.dotEnvFiles, + nil, []string{ "fiftyone-app", "fiftyone-app-telemetry", @@ -119,6 +141,28 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServicesNames() { "telemetry-redis", }, }, + { + "composeTelemetryPlugins", + []string{ + internalAuthComposeFile, + internalAuthComposeDedicatedPluginsFile, + internalAuthComposeTelemetryFile, + internalAuthComposeTelemetryPluginsFile, + }, + s.dotEnvFiles, + nil, + []string{ + "fiftyone-app", + "fiftyone-app-telemetry", + "teams-api", + "teams-api-telemetry", + "teams-app", + "teams-cas", + "teams-plugins", + "teams-plugins-telemetry", + "telemetry-redis", + }, + }, { "composeTelemetryDelegatedOperators", []string{ @@ -128,6 +172,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServicesNames() { internalAuthComposeTelemetryDelegatedOperatorsFile, }, s.dotEnvFiles, + nil, []string{ "fiftyone-app", "fiftyone-app-telemetry", @@ -155,6 +200,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServicesNames() { cli.WithName(s.projectName), cli.WithEnvFiles(testCase.envFiles...), cli.WithDotEnv, + cli.WithProfiles(testCase.profiles), ) s.NoError(err) diff --git a/tests/unit/compose/docker-compose-legacy-auth_test.go b/tests/unit/compose/docker-compose-legacy-auth_test.go index 28c3a90d0..39fc805e4 100644 --- a/tests/unit/compose/docker-compose-legacy-auth_test.go +++ b/tests/unit/compose/docker-compose-legacy-auth_test.go @@ -28,7 +28,9 @@ var legacyAuthComposeFile = filepath.Join(dockerLegacyAuthDir, "compose.yaml") var legacyAuthComposePluginsFile = filepath.Join(dockerLegacyAuthDir, "compose.plugins.yaml") var legacyAuthComposeDedicatedPluginsFile = filepath.Join(dockerLegacyAuthDir, "compose.dedicated-plugins.yaml") var legacyAuthComposeDelegatedOperationsFile = filepath.Join(dockerLegacyAuthDir, "compose.delegated-operators.yaml") +var legacyAuthComposeDelegatedOperationsGpuFile = filepath.Join(dockerLegacyAuthDir, "compose.delegated-operators.gpu.yaml") var legacyAuthComposeTelemetryFile = filepath.Join(dockerLegacyAuthDir, "compose.telemetry.yaml") +var legacyAuthComposeTelemetryPluginsFile = filepath.Join(dockerLegacyAuthDir, "compose.telemetry.plugins.yaml") var legacyAuthComposeTelemetryDelegatedOperatorsFile = filepath.Join(dockerLegacyAuthDir, "compose.telemetry.delegated-operators.yaml") var legacyAuthEnvTemplateFilePath = filepath.Join(dockerLegacyAuthDir, "env.template") @@ -61,12 +63,14 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { name string configPaths []string // file paths to one or more Compose files. envFiles []string // file paths to ".env" files with additional environment variable data + profiles []string // compose profiles to activate expected []string }{ { "compose", []string{legacyAuthComposeFile}, s.dotEnvFiles, + nil, []string{ "fiftyone-app", "teams-api", @@ -78,6 +82,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { "composePlugins", []string{legacyAuthComposePluginsFile}, s.dotEnvFiles, + nil, []string{ "fiftyone-app", "teams-api", @@ -89,6 +94,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { "composeDedicatedPlugins", []string{legacyAuthComposeDedicatedPluginsFile}, s.dotEnvFiles, + nil, []string{ "fiftyone-app", "teams-api", @@ -101,14 +107,30 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { "composeDelegatedOperations", []string{legacyAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, + nil, []string{ "teams-do", }, }, + { + "composeDelegatedOperationsGpu", + []string{ + legacyAuthComposeDelegatedOperationsFile, + legacyAuthComposeDelegatedOperationsGpuFile, + }, + s.dotEnvFiles, + []string{"gpu"}, + []string{ + "teams-do", + "teams-do-gpu", + "teams-do-gpu-telemetry", + }, + }, { "composeTelemetry", []string{legacyAuthComposeFile, legacyAuthComposeTelemetryFile}, s.dotEnvFiles, + nil, []string{ "fiftyone-app", "fiftyone-app-telemetry", @@ -119,6 +141,28 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { "telemetry-redis", }, }, + { + "composeTelemetryPlugins", + []string{ + legacyAuthComposeFile, + legacyAuthComposeDedicatedPluginsFile, + legacyAuthComposeTelemetryFile, + legacyAuthComposeTelemetryPluginsFile, + }, + s.dotEnvFiles, + nil, + []string{ + "fiftyone-app", + "fiftyone-app-telemetry", + "teams-api", + "teams-api-telemetry", + "teams-app", + "teams-cas", + "teams-plugins", + "teams-plugins-telemetry", + "telemetry-redis", + }, + }, { "composeTelemetryDelegatedOperators", []string{ @@ -128,6 +172,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { legacyAuthComposeTelemetryDelegatedOperatorsFile, }, s.dotEnvFiles, + nil, []string{ "fiftyone-app", "fiftyone-app-telemetry", @@ -155,6 +200,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { cli.WithName(s.projectName), cli.WithEnvFiles(testCase.envFiles...), cli.WithDotEnv, + cli.WithProfiles(testCase.profiles), ) s.NoError(err) From ee0ba727d13e897ce3054e943519eee2f79e665d Mon Sep 17 00:00:00 2001 From: kacey Date: Fri, 15 May 2026 00:10:47 -0700 Subject: [PATCH 09/49] chore: fix tests --- docker/docs/configuring-telemetry.md | 5 +++-- tests/unit/compose/docker-compose-internal-auth_test.go | 9 +++++++++ tests/unit/compose/docker-compose-legacy-auth_test.go | 9 +++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/docker/docs/configuring-telemetry.md b/docker/docs/configuring-telemetry.md index 664f8211c..0ecadcdac 100644 --- a/docker/docs/configuring-telemetry.md +++ b/docker/docs/configuring-telemetry.md @@ -73,8 +73,9 @@ docker compose --profile gpu \ with the `SYS_PTRACE` capability so py-spy can attach to the target. - `teams-plugins-telemetry` (only with `compose.telemetry.plugins.yaml`) — sidecar for the dedicated `teams-plugins` service. -- `teams-do-telemetry` (only with `compose.telemetry.delegated-operators.yaml`) — - sidecar in `EXECUTOR_SIDECAR=true` mode that watches the executor for +- `teams-do-telemetry` (only with + `compose.telemetry.delegated-operators.yaml`) — sidecar in + `EXECUTOR_SIDECAR=true` mode that watches the executor for per-operation child processes and records per-op metrics back to the `delegated_ops` MongoDB document. - `teams-do-gpu` + `teams-do-gpu-telemetry` (only with diff --git a/tests/unit/compose/docker-compose-internal-auth_test.go b/tests/unit/compose/docker-compose-internal-auth_test.go index 265aaf411..305e6f104 100644 --- a/tests/unit/compose/docker-compose-internal-auth_test.go +++ b/tests/unit/compose/docker-compose-internal-auth_test.go @@ -115,15 +115,24 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServicesNames() { { "composeDelegatedOperationsGpu", []string{ + internalAuthComposeFile, internalAuthComposeDelegatedOperationsFile, internalAuthComposeDelegatedOperationsGpuFile, + internalAuthComposeTelemetryFile, }, s.dotEnvFiles, []string{"gpu"}, []string{ + "fiftyone-app", + "fiftyone-app-telemetry", + "teams-api", + "teams-api-telemetry", + "teams-app", + "teams-cas", "teams-do", "teams-do-gpu", "teams-do-gpu-telemetry", + "telemetry-redis", }, }, { diff --git a/tests/unit/compose/docker-compose-legacy-auth_test.go b/tests/unit/compose/docker-compose-legacy-auth_test.go index 39fc805e4..e10bf8078 100644 --- a/tests/unit/compose/docker-compose-legacy-auth_test.go +++ b/tests/unit/compose/docker-compose-legacy-auth_test.go @@ -115,15 +115,24 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { { "composeDelegatedOperationsGpu", []string{ + legacyAuthComposeFile, legacyAuthComposeDelegatedOperationsFile, legacyAuthComposeDelegatedOperationsGpuFile, + legacyAuthComposeTelemetryFile, }, s.dotEnvFiles, []string{"gpu"}, []string{ + "fiftyone-app", + "fiftyone-app-telemetry", + "teams-api", + "teams-api-telemetry", + "teams-app", + "teams-cas", "teams-do", "teams-do-gpu", "teams-do-gpu-telemetry", + "telemetry-redis", }, }, { From 1ca27a4fb9789c38e46dc75c644ec19d355b9824 Mon Sep 17 00:00:00 2001 From: kacey Date: Fri, 15 May 2026 00:44:16 -0700 Subject: [PATCH 10/49] chore: fix tests --- helm/fiftyone-teams-app/values.schema.json | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/helm/fiftyone-teams-app/values.schema.json b/helm/fiftyone-teams-app/values.schema.json index 099a33f9c..b43868052 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -107,7 +107,6 @@ "items": { "required": [] }, - "required": [], "title": "extraContainers", "type": "array" }, @@ -342,7 +341,6 @@ "items": { "required": [] }, - "required": [], "title": "nativeSidecarContainers", "type": "array" }, @@ -817,7 +815,6 @@ "items": { "required": [] }, - "required": [], "title": "extraContainers", "type": "array" }, @@ -1048,7 +1045,6 @@ "items": { "required": [] }, - "required": [], "title": "nativeSidecarContainers", "type": "array" }, @@ -2066,7 +2062,6 @@ "items": { "required": [] }, - "required": [], "title": "extraContainers", "type": "array" }, @@ -2141,7 +2136,6 @@ "items": { "required": [] }, - "required": [], "title": "nativeSidecarContainers", "type": "array" }, @@ -2444,7 +2438,6 @@ "items": { "required": [] }, - "required": [], "title": "extraContainers", "type": "array" }, @@ -2500,7 +2493,6 @@ "items": { "required": [] }, - "required": [], "title": "nativeSidecarContainers", "type": "array" }, @@ -2868,7 +2860,6 @@ "items": { "required": [] }, - "required": [], "title": "extraContainers", "type": "array" }, @@ -3099,7 +3090,6 @@ "items": { "required": [] }, - "required": [], "title": "nativeSidecarContainers", "type": "array" }, From 30282b1a7e21c2290905bce54391f26f380912a7 Mon Sep 17 00:00:00 2001 From: kacey Date: Fri, 15 May 2026 20:55:54 -0700 Subject: [PATCH 11/49] chore: trigger ci From ea4c3863d10bb42bc68e0ac5a2b4a8806b43c2c4 Mon Sep 17 00:00:00 2001 From: kacey Date: Sat, 16 May 2026 22:02:14 -0700 Subject: [PATCH 12/49] fix: auto-inject sidecar settings --- helm/fiftyone-teams-app/README.md | 2 +- .../delegated-operator-job-configmap.yaml | 40 ++++++++++++++++++- helm/fiftyone-teams-app/values.schema.json | 2 +- helm/fiftyone-teams-app/values.yaml | 4 +- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index a578c9f5e..fbe3bfa68 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -944,7 +944,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | delegatedOperatorJobTemplates.template.image.tag | string | `""` | Image tag for `delegated-operator-executor`. Defaults to the chart version. | | delegatedOperatorJobTemplates.template.jobAnnotations | object | `{}` | Annotations for the `teams-do` deployment. [Reference][annotations]. | | delegatedOperatorJobTemplates.template.labels | object | `{}` | Additional labels for the `delegated-operator-executor` related objects. [Reference][labels-and-selectors]. | -| delegatedOperatorJobTemplates.template.nativeSidecarContainers | list | `[]` | Native sidecar containers for on-demand delegated-operator Job pods (Kubernetes 1.29+). Entries are rendered under `initContainers`; set `restartPolicy: Always` on each entry to enable the native sidecar lifecycle. Kubelet auto-terminates these when the executor container finishes, so the Job completes cleanly. [Reference][sidecar-containers]. | +| delegatedOperatorJobTemplates.template.nativeSidecarContainers | list | `[]` | Native sidecar containers for on-demand delegated-operator Job pods (Kubernetes 1.29+). Entries are rendered under `initContainers`; set `restartPolicy: Always` on each entry to enable the native sidecar lifecycle. Kubelet auto-terminates these when the executor container finishes, so the Job completes cleanly. The chart auto-injects `TARGET_CONTAINER=executor` on each entry (override by setting it explicitly). [Reference][sidecar-containers]. | | delegatedOperatorJobTemplates.template.nodeSelector | object | `{}` | nodeSelector for `delegated-operator-executor`. [Reference][node-selector]. | | delegatedOperatorJobTemplates.template.podAnnotations | object | `{}` | Annotations for delegated-operator-executor pods. [Reference][annotations]. | | delegatedOperatorJobTemplates.template.podSecurityContext | object | `{}` | Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context]. | diff --git a/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml b/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml index 36ea5241b..e62c5de1d 100644 --- a/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml +++ b/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml @@ -44,6 +44,31 @@ data: {{- $mergedVolumes := $jobConfig.volumes | default $baseTpl.volumes }} {{- $mergedExtraContainers := $jobConfig.extraContainers | default $baseTpl.extraContainers }} {{- $mergedNativeSidecarContainers := $jobConfig.nativeSidecarContainers | default $baseTpl.nativeSidecarContainers }} + + {{- /* telemetry-sidecar needs a shared emptyDir; values can't reliably keep it through fleet regenerators, so reconcile it here. */ -}} + {{- $needsTelemetrySocket := false }} + {{- range $sidecar := $mergedNativeSidecarContainers }} + {{- range $vm := ($sidecar.volumeMounts | default list) }} + {{- if eq $vm.name "telemetry-socket" }}{{- $needsTelemetrySocket = true }}{{- end }} + {{- end }} + {{- end }} + {{- if $needsTelemetrySocket }} + {{- $hasVolume := false }} + {{- range $v := ($mergedVolumes | default list) }} + {{- if eq $v.name "telemetry-socket" }}{{- $hasVolume = true }}{{- end }} + {{- end }} + {{- if not $hasVolume }} + {{- $mergedVolumes = append ($mergedVolumes | default list) (dict "name" "telemetry-socket" "emptyDir" dict) }} + {{- end }} + {{- $hasMount := false }} + {{- range $vm := ($mergedVolumeMounts | default list) }} + {{- if eq $vm.name "telemetry-socket" }}{{- $hasMount = true }}{{- end }} + {{- end }} + {{- if not $hasMount }} + {{- $mergedVolumeMounts = append ($mergedVolumeMounts | default list) (dict "name" "telemetry-socket" "mountPath" "/tmp/telemetry") }} + {{- end }} + {{- end }} + {{- /* Trunc at 48 for the random ID to be generated via API */ -}} {{- $metadataName := kebabcase $jobName | trunc 48 }} @@ -129,7 +154,20 @@ data: {{- end }} {{- with $mergedNativeSidecarContainers }} initContainers: - {{- toYaml . | nindent 12 }} + {{- /* TARGET_CONTAINER tells the telemetry sidecar's log tailer which container to follow; the executor is hardcoded so we know the value. */ -}} + {{- range $sidecar := . }} + {{- $hasTargetContainer := false }} + {{- range $env := ($sidecar.env | default list) }} + {{- if eq $env.name "TARGET_CONTAINER" }} + {{- $hasTargetContainer = true }} + {{- end }} + {{- end }} + {{- $rendered := deepCopy $sidecar }} + {{- if not $hasTargetContainer }} + {{- $_ := set $rendered "env" (prepend ($sidecar.env | default list) (dict "name" "TARGET_CONTAINER" "value" "executor")) }} + {{- end }} + {{- list $rendered | toYaml | nindent 12 }} + {{- end }} {{- end }} {{- with $mergedNodeSelector }} nodeSelector: diff --git a/helm/fiftyone-teams-app/values.schema.json b/helm/fiftyone-teams-app/values.schema.json index b43868052..82422ad8f 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -2489,7 +2489,7 @@ "type": "object" }, "nativeSidecarContainers": { - "description": "Native sidecar containers for on-demand delegated-operator Job pods\n(Kubernetes 1.29+). Entries are rendered under `initContainers`; set\n`restartPolicy: Always` on each entry to enable the native sidecar\nlifecycle. Kubelet auto-terminates these when the executor container\nfinishes, so the Job completes cleanly. [Reference][sidecar-containers].", + "description": "Native sidecar containers for on-demand delegated-operator Job pods\n(Kubernetes 1.29+). Entries are rendered under `initContainers`; set\n`restartPolicy: Always` on each entry to enable the native sidecar\nlifecycle. Kubelet auto-terminates these when the executor container\nfinishes, so the Job completes cleanly. The chart auto-injects\n`TARGET_CONTAINER=executor` on each entry (override by setting it\nexplicitly). [Reference][sidecar-containers].", "items": { "required": [] }, diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index 4107806aa..ef9deab1d 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -1031,7 +1031,9 @@ delegatedOperatorJobTemplates: # (Kubernetes 1.29+). Entries are rendered under `initContainers`; set # `restartPolicy: Always` on each entry to enable the native sidecar # lifecycle. Kubelet auto-terminates these when the executor container - # finishes, so the Job completes cleanly. [Reference][sidecar-containers]. + # finishes, so the Job completes cleanly. The chart auto-injects + # `TARGET_CONTAINER=executor` on each entry (override by setting it + # explicitly). [Reference][sidecar-containers]. nativeSidecarContainers: [] # -- On-Demand Delegated Operator Jobs. jobs: {} From d58c01c933523061c9c515840e1760ae40f13144 Mon Sep 17 00:00:00 2001 From: kacey Date: Mon, 18 May 2026 07:05:42 -0700 Subject: [PATCH 13/49] fix: fix auto inject sidecar vars and update test --- .../delegated-operator-job-configmap.yaml | 25 ++-- .../delegated-operator-job-configmap_test.go | 138 ++++++++++++++++++ 2 files changed, 154 insertions(+), 9 deletions(-) diff --git a/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml b/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml index e62c5de1d..b68661a8b 100644 --- a/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml +++ b/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml @@ -45,14 +45,13 @@ data: {{- $mergedExtraContainers := $jobConfig.extraContainers | default $baseTpl.extraContainers }} {{- $mergedNativeSidecarContainers := $jobConfig.nativeSidecarContainers | default $baseTpl.nativeSidecarContainers }} - {{- /* telemetry-sidecar needs a shared emptyDir; values can't reliably keep it through fleet regenerators, so reconcile it here. */ -}} - {{- $needsTelemetrySocket := false }} + {{- $hasExecutorSidecar := false }} {{- range $sidecar := $mergedNativeSidecarContainers }} - {{- range $vm := ($sidecar.volumeMounts | default list) }} - {{- if eq $vm.name "telemetry-socket" }}{{- $needsTelemetrySocket = true }}{{- end }} + {{- range $env := ($sidecar.env | default list) }} + {{- if and (eq $env.name "EXECUTOR_SIDECAR") (eq (toString $env.value) "true") }}{{- $hasExecutorSidecar = true }}{{- end }} {{- end }} {{- end }} - {{- if $needsTelemetrySocket }} + {{- if $hasExecutorSidecar }} {{- $hasVolume := false }} {{- range $v := ($mergedVolumes | default list) }} {{- if eq $v.name "telemetry-socket" }}{{- $hasVolume = true }}{{- end }} @@ -154,18 +153,26 @@ data: {{- end }} {{- with $mergedNativeSidecarContainers }} initContainers: - {{- /* TARGET_CONTAINER tells the telemetry sidecar's log tailer which container to follow; the executor is hardcoded so we know the value. */ -}} {{- range $sidecar := . }} + {{- $isExecutor := false }} {{- $hasTargetContainer := false }} {{- range $env := ($sidecar.env | default list) }} - {{- if eq $env.name "TARGET_CONTAINER" }} - {{- $hasTargetContainer = true }} - {{- end }} + {{- if eq $env.name "TARGET_CONTAINER" }}{{- $hasTargetContainer = true }}{{- end }} + {{- if and (eq $env.name "EXECUTOR_SIDECAR") (eq (toString $env.value) "true") }}{{- $isExecutor = true }}{{- end }} {{- end }} {{- $rendered := deepCopy $sidecar }} {{- if not $hasTargetContainer }} {{- $_ := set $rendered "env" (prepend ($sidecar.env | default list) (dict "name" "TARGET_CONTAINER" "value" "executor")) }} {{- end }} + {{- if $isExecutor }} + {{- $hasSocketMount := false }} + {{- range $vm := ($sidecar.volumeMounts | default list) }} + {{- if eq $vm.name "telemetry-socket" }}{{- $hasSocketMount = true }}{{- end }} + {{- end }} + {{- if not $hasSocketMount }} + {{- $_ := set $rendered "volumeMounts" (append ($sidecar.volumeMounts | default list) (dict "name" "telemetry-socket" "mountPath" "/tmp/telemetry")) }} + {{- end }} + {{- end }} {{- list $rendered | toYaml | nindent 12 }} {{- end }} {{- end }} diff --git a/tests/unit/helm/delegated-operator-job-configmap_test.go b/tests/unit/helm/delegated-operator-job-configmap_test.go index 9ab313a4a..345bc2d4b 100644 --- a/tests/unit/helm/delegated-operator-job-configmap_test.go +++ b/tests/unit/helm/delegated-operator-job-configmap_test.go @@ -657,3 +657,141 @@ func (s *doK8sConfigMapTemplateTest) loadTestFile(filename, chartVersion string) result = strings.TrimSpace(result) return strings.TrimSpace(result) } + +func (s *doK8sConfigMapTemplateTest) TestNativeSidecarTelemetryAutoInject() { + testCases := []struct { + name string + values map[string]string + expected func(subT gruntworkTesting.TestingT, job batchv1.Job) + }{ + { + "AutoInjectsTelemetrySocketWiringWhenSidecarHasExecutorEnv", + map[string]string{ + "delegatedOperatorJobTemplates.jobs.testJob.unused": "nil", + "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].name": "telemetry-sidecar", + "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].image": "telemetry-sidecar:latest", + "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].restartPolicy": "Always", + "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[0].name": "EXECUTOR_SIDECAR", + "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[0].value": "true", + }, + func(subT gruntworkTesting.TestingT, job batchv1.Job) { + podSpec := job.Spec.Template.Spec + + s.Require().NotNil(podSpec.ShareProcessNamespace, "shareProcessNamespace must be set when nativeSidecarContainers present") + s.True(*podSpec.ShareProcessNamespace, "shareProcessNamespace must be true") + + s.Require().Len(podSpec.InitContainers, 1, "Should be one initContainer (the sidecar)") + sidecar := podSpec.InitContainers[0] + s.True(envContains(sidecar.Env, "TARGET_CONTAINER", "executor"), "Sidecar must have auto-injected TARGET_CONTAINER=executor") + s.True(volumeMountContains(sidecar.VolumeMounts, "telemetry-socket", "/tmp/telemetry"), "Sidecar must have auto-injected telemetry-socket volumeMount") + + executor := findContainer(podSpec.Containers, "executor") + s.Require().NotNil(executor, "executor container must exist") + s.True(volumeMountContains(executor.VolumeMounts, "telemetry-socket", "/tmp/telemetry"), "Executor must have auto-injected telemetry-socket volumeMount") + + s.True(volumeContains(podSpec.Volumes, "telemetry-socket"), "Pod must have auto-injected telemetry-socket emptyDir volume") + }, + }, + { + "PreservesExplicitTargetContainerOverride", + map[string]string{ + "delegatedOperatorJobTemplates.jobs.testJob.unused": "nil", + "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].name": "telemetry-sidecar", + "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].image": "telemetry-sidecar:latest", + "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].restartPolicy": "Always", + "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[0].name": "EXECUTOR_SIDECAR", + "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[0].value": "true", + "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[1].name": "TARGET_CONTAINER", + "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[1].value": "custom-executor", + }, + func(subT gruntworkTesting.TestingT, job batchv1.Job) { + sidecar := job.Spec.Template.Spec.InitContainers[0] + s.True(envContains(sidecar.Env, "TARGET_CONTAINER", "custom-executor"), "User-set TARGET_CONTAINER must win") + s.False(envContains(sidecar.Env, "TARGET_CONTAINER", "executor"), "Auto-inject must not also append TARGET_CONTAINER=executor") + }, + }, + { + "DoesNotInjectTelemetryWiringForNonExecutorSidecar", + map[string]string{ + "delegatedOperatorJobTemplates.jobs.testJob.unused": "nil", + "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].name": "istio-proxy", + "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].image": "istio-proxy:latest", + "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].restartPolicy": "Always", + }, + func(subT gruntworkTesting.TestingT, job batchv1.Job) { + podSpec := job.Spec.Template.Spec + s.False(volumeContains(podSpec.Volumes, "telemetry-socket"), "Non-executor sidecar must not auto-inject telemetry-socket volume") + sidecar := podSpec.InitContainers[0] + s.False(volumeMountContains(sidecar.VolumeMounts, "telemetry-socket", "/tmp/telemetry"), "Non-executor sidecar must not auto-inject volumeMount") + executor := findContainer(podSpec.Containers, "executor") + s.Require().NotNil(executor) + s.False(volumeMountContains(executor.VolumeMounts, "telemetry-socket", "/tmp/telemetry"), "Executor must not get telemetry-socket mount when no executor sidecar") + }, + }, + } + + for _, testCase := range testCases { + testCase := testCase + s.Run(testCase.name, func() { + subT := s.T() + subT.Parallel() + + options := &helm.Options{SetValues: testCase.values} + output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) + + var configMap corev1.ConfigMap + helm.UnmarshalK8SYaml(subT, output, &configMap) + + s.Require().NotEmpty(configMap.Data["testJob.yaml"], "testJob.yaml should be rendered in configmap data") + + jinjaArgs := map[string]interface{}{ + "_id": strings.ToLower(random.UniqueId()), + "_command": "fiftyone", + "_args": []string{"delegated", "launch"}, + } + actualJobYaml, err := convertJinjaToYAML(configMap.Data["testJob.yaml"], jinjaArgs) + s.NoError(err) + + var job batchv1.Job + helm.UnmarshalK8SYaml(subT, actualJobYaml, &job) + + testCase.expected(subT, job) + }) + } +} + +func envContains(envs []corev1.EnvVar, name, value string) bool { + for _, e := range envs { + if e.Name == name && e.Value == value { + return true + } + } + return false +} + +func volumeMountContains(vms []corev1.VolumeMount, name, mountPath string) bool { + for _, vm := range vms { + if vm.Name == name && vm.MountPath == mountPath { + return true + } + } + return false +} + +func volumeContains(vols []corev1.Volume, name string) bool { + for _, v := range vols { + if v.Name == name { + return true + } + } + return false +} + +func findContainer(containers []corev1.Container, name string) *corev1.Container { + for i, c := range containers { + if c.Name == name { + return &containers[i] + } + } + return nil +} From d532ec449c651545238ba7975fb4a00edebe27cb Mon Sep 17 00:00:00 2001 From: kacey Date: Mon, 18 May 2026 09:18:53 -0700 Subject: [PATCH 14/49] chore: attempt to fix test --- .../delegated-operator-job-configmap_test.go | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/unit/helm/delegated-operator-job-configmap_test.go b/tests/unit/helm/delegated-operator-job-configmap_test.go index 345bc2d4b..0199a13cc 100644 --- a/tests/unit/helm/delegated-operator-job-configmap_test.go +++ b/tests/unit/helm/delegated-operator-job-configmap_test.go @@ -660,9 +660,10 @@ func (s *doK8sConfigMapTemplateTest) loadTestFile(filename, chartVersion string) func (s *doK8sConfigMapTemplateTest) TestNativeSidecarTelemetryAutoInject() { testCases := []struct { - name string - values map[string]string - expected func(subT gruntworkTesting.TestingT, job batchv1.Job) + name string + values map[string]string + strValues map[string]string + expected func(subT gruntworkTesting.TestingT, job batchv1.Job) }{ { "AutoInjectsTelemetrySocketWiringWhenSidecarHasExecutorEnv", @@ -672,7 +673,9 @@ func (s *doK8sConfigMapTemplateTest) TestNativeSidecarTelemetryAutoInject() { "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].image": "telemetry-sidecar:latest", "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].restartPolicy": "Always", "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[0].name": "EXECUTOR_SIDECAR", - "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[0].value": "true", + }, + map[string]string{ + "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[0].value": "true", }, func(subT gruntworkTesting.TestingT, job batchv1.Job) { podSpec := job.Spec.Template.Spec @@ -700,10 +703,12 @@ func (s *doK8sConfigMapTemplateTest) TestNativeSidecarTelemetryAutoInject() { "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].image": "telemetry-sidecar:latest", "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].restartPolicy": "Always", "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[0].name": "EXECUTOR_SIDECAR", - "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[0].value": "true", "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[1].name": "TARGET_CONTAINER", "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[1].value": "custom-executor", }, + map[string]string{ + "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[0].value": "true", + }, func(subT gruntworkTesting.TestingT, job batchv1.Job) { sidecar := job.Spec.Template.Spec.InitContainers[0] s.True(envContains(sidecar.Env, "TARGET_CONTAINER", "custom-executor"), "User-set TARGET_CONTAINER must win") @@ -718,6 +723,7 @@ func (s *doK8sConfigMapTemplateTest) TestNativeSidecarTelemetryAutoInject() { "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].image": "istio-proxy:latest", "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].restartPolicy": "Always", }, + nil, func(subT gruntworkTesting.TestingT, job batchv1.Job) { podSpec := job.Spec.Template.Spec s.False(volumeContains(podSpec.Volumes, "telemetry-socket"), "Non-executor sidecar must not auto-inject telemetry-socket volume") @@ -736,7 +742,7 @@ func (s *doK8sConfigMapTemplateTest) TestNativeSidecarTelemetryAutoInject() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: testCase.values, SetStrValues: testCase.strValues} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var configMap corev1.ConfigMap From fe89df4d30d32bcc6e135774496f853d87c7c2db Mon Sep 17 00:00:00 2001 From: kacey Date: Tue, 19 May 2026 08:34:28 -0700 Subject: [PATCH 15/49] feat: default true for telemetry and add templates --- helm/fiftyone-teams-app/README.md | 8 +- .../templates/_telemetry.tpl | 40 ++++ .../delegated-operator-job-configmap.yaml | 4 + .../templates/telemetry-redis.yaml | 74 +++++++ .../templates/telemetry-rolebinding.yaml | 34 ++++ helm/fiftyone-teams-app/values.schema.json | 95 +++++++++ helm/fiftyone-teams-app/values.yaml | 37 ++++ tests/unit/helm/common_test.go | 4 +- tests/unit/helm/telemetry-redis_test.go | 190 ++++++++++++++++++ tests/unit/helm/telemetry-rolebinding_test.go | 182 +++++++++++++++++ tests/unit/helm/yaml_helpers.go | 17 ++ 11 files changed, 682 insertions(+), 3 deletions(-) create mode 100644 helm/fiftyone-teams-app/templates/_telemetry.tpl create mode 100644 helm/fiftyone-teams-app/templates/telemetry-redis.yaml create mode 100644 helm/fiftyone-teams-app/templates/telemetry-rolebinding.yaml create mode 100644 tests/unit/helm/telemetry-redis_test.go create mode 100644 tests/unit/helm/telemetry-rolebinding_test.go create mode 100644 tests/unit/helm/yaml_helpers.go diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index fbe3bfa68..c7162d9b5 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -1095,7 +1095,13 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | teamsAppSettings.updateStrategy | object | `{"type":"RollingUpdate"}` | Control how `teams-app` pods are redeployed during an upgrade. [Reference][upgrade-strategies] | | teamsAppSettings.volumeMounts | list | `[]` | Volume mounts for `teams-app` pods. [Reference][volumes]. | | teamsAppSettings.volumes | list | `[]` | Volumes for `teams-app` pods. [Reference][volumes]. | - +| telemetry.enabled | bool | `true` | Whether to render the telemetry Redis Deployment/Service and the telemetry pod-logs Role/RoleBinding. Defaults to `true` so consumers that wire up `telemetry-sidecar` containers get a working redis without additional opt-in. Set to `false` to skip rendering all telemetry-related resources. | +| telemetry.redis.image | string | `"redis:7-alpine"` | Container image for the telemetry Redis Deployment. | +| telemetry.redis.maxmemory | string | `"400mb"` | `--maxmemory` flag passed to `redis-server`. | +| telemetry.redis.maxmemoryPolicy | string | `"allkeys-lru"` | `--maxmemory-policy` flag passed to `redis-server`. | +| telemetry.redis.resources | object | `{"limits":{"cpu":"250m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"256Mi"}}` | Resource requests/limits for the telemetry Redis container. [Reference][resources]. | +| telemetry.redis.storage | string | `"1Gi"` | Persistent volume size for the telemetry Redis PVC. | +| telemetry.serviceAccounts | list | `[]` | ServiceAccount names (in `namespace.name`) bound to the telemetry pod-logs Role. Each entry becomes a `ServiceAccount` subject on the generated RoleBinding. When empty, the RoleBinding subjects default to the `default` ServiceAccount. | diff --git a/helm/fiftyone-teams-app/templates/_telemetry.tpl b/helm/fiftyone-teams-app/templates/_telemetry.tpl new file mode 100644 index 000000000..6ebe5248e --- /dev/null +++ b/helm/fiftyone-teams-app/templates/_telemetry.tpl @@ -0,0 +1,40 @@ +{{/* +Name of the telemetry redis Deployment/Service/PVC. +*/}} +{{- define "telemetry.redis.name" -}} +{{- printf "%s-telemetry-redis" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end }} + +{{/* +Selector labels for the telemetry redis Deployment/Service. +*/}} +{{- define "telemetry.redis.selectorLabels" -}} +app.kubernetes.io/name: telemetry-redis +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Combined labels for the telemetry redis Deployment/Service/PVC. +*/}} +{{- define "telemetry.redis.labels" -}} +{{- include "fiftyone-teams-app.commonLabels" . }} +{{ include "telemetry.redis.selectorLabels" . }} +app.voxel51.com/component: telemetry-redis +{{- end }} + +{{/* +Name of the telemetry Role and RoleBinding for the sidecar's pods/log access. +*/}} +{{- define "telemetry.role.name" -}} +{{- printf "%s-telemetry-pod-logs" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end }} + +{{/* +Combined labels for the telemetry Role and RoleBinding. +*/}} +{{- define "telemetry.role.labels" -}} +{{- include "fiftyone-teams-app.commonLabels" . }} +app.kubernetes.io/name: telemetry +app.kubernetes.io/instance: {{ .Release.Name }} +app.voxel51.com/component: telemetry +{{- end }} diff --git a/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml b/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml index b68661a8b..b611a83fd 100644 --- a/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml +++ b/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml @@ -172,6 +172,10 @@ data: {{- if not $hasSocketMount }} {{- $_ := set $rendered "volumeMounts" (append ($sidecar.volumeMounts | default list) (dict "name" "telemetry-socket" "mountPath" "/tmp/telemetry")) }} {{- end }} + {{- /* Gate the main container on the socket being bound — native sidecar readiness alone doesn't wait. */ -}} + {{- if not (hasKey $sidecar "readinessProbe") }} + {{- $_ := set $rendered "readinessProbe" (dict "exec" (dict "command" (list "sh" "-c" "test -S /tmp/telemetry/agent.sock")) "periodSeconds" 1 "failureThreshold" 30) }} + {{- end }} {{- end }} {{- list $rendered | toYaml | nindent 12 }} {{- end }} diff --git a/helm/fiftyone-teams-app/templates/telemetry-redis.yaml b/helm/fiftyone-teams-app/templates/telemetry-redis.yaml new file mode 100644 index 000000000..03c611da7 --- /dev/null +++ b/helm/fiftyone-teams-app/templates/telemetry-redis.yaml @@ -0,0 +1,74 @@ +{{- if .Values.telemetry.enabled }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "telemetry.redis.name" . }}-data + namespace: {{ .Values.namespace.name }} + labels: + {{- include "telemetry.redis.labels" . | nindent 4 }} +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.telemetry.redis.storage | default "1Gi" }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "telemetry.redis.name" . }} + namespace: {{ .Values.namespace.name }} + labels: + {{- include "telemetry.redis.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "telemetry.redis.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "telemetry.redis.labels" . | nindent 8 }} + spec: + containers: + - name: redis + image: {{ .Values.telemetry.redis.image | default "redis:7-alpine" }} + args: + - redis-server + - --save + - "60" + - "1" + - --dir + - /data + - --maxmemory + - {{ .Values.telemetry.redis.maxmemory | default "400mb" | quote }} + - --maxmemory-policy + - {{ .Values.telemetry.redis.maxmemoryPolicy | default "allkeys-lru" | quote }} + ports: + - containerPort: 6379 + {{- with .Values.telemetry.redis.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: redis-data + mountPath: /data + volumes: + - name: redis-data + persistentVolumeClaim: + claimName: {{ include "telemetry.redis.name" . }}-data +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "telemetry.redis.name" . }} + namespace: {{ .Values.namespace.name }} + labels: + {{- include "telemetry.redis.labels" . | nindent 4 }} +spec: + selector: + {{- include "telemetry.redis.selectorLabels" . | nindent 4 }} + ports: + - port: 6379 + targetPort: 6379 +{{- end }} diff --git a/helm/fiftyone-teams-app/templates/telemetry-rolebinding.yaml b/helm/fiftyone-teams-app/templates/telemetry-rolebinding.yaml new file mode 100644 index 000000000..e148ae7db --- /dev/null +++ b/helm/fiftyone-teams-app/templates/telemetry-rolebinding.yaml @@ -0,0 +1,34 @@ +{{- if .Values.telemetry.enabled }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "telemetry.role.name" . }} + namespace: {{ .Values.namespace.name }} + labels: + {{- include "telemetry.role.labels" . | nindent 4 }} +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get"] + - apiGroups: [""] + resources: ["pods/log"] + verbs: ["get"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "telemetry.role.name" . }} + namespace: {{ .Values.namespace.name }} + labels: + {{- include "telemetry.role.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "telemetry.role.name" . }} +subjects: +{{- range .Values.telemetry.serviceAccounts | default (list "default") }} + - kind: ServiceAccount + name: {{ . }} + namespace: {{ $.Values.namespace.name }} +{{- end }} +{{- end }} diff --git a/helm/fiftyone-teams-app/values.schema.json b/helm/fiftyone-teams-app/values.schema.json index 82422ad8f..c4e63a40e 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -4008,6 +4008,101 @@ "required": [], "title": "teamsAppSettings", "type": "object" + }, + "telemetry": { + "description": "In-cluster telemetry support. When enabled, the chart renders a small\nRedis Deployment + Service (used by `telemetry-sidecar` containers\nconfigured on workload pods via `extraContainers`) plus a Role/RoleBinding\ngranting the sidecar's service accounts read access to `pods/log`.\nThe chart itself does NOT inject `telemetry-sidecar` containers; consumers\nwire those into `extraContainers` on the relevant workloads and point the\n`FIFTYONE_TELEMETRY_REDIS_URL` env var at this service.", + "properties": { + "enabled": { + "default": true, + "description": "Whether to render the telemetry Redis Deployment/Service and the\ntelemetry pod-logs Role/RoleBinding. Defaults to `true` so consumers\nthat wire up `telemetry-sidecar` containers get a working redis without\nadditional opt-in. Set to `false` to skip rendering all telemetry-related\nresources.", + "title": "enabled", + "type": "boolean" + }, + "redis": { + "properties": { + "image": { + "default": "redis:7-alpine", + "description": "Container image for the telemetry Redis Deployment.", + "title": "image", + "type": "string" + }, + "maxmemory": { + "default": "400mb", + "description": "`--maxmemory` flag passed to `redis-server`.", + "title": "maxmemory", + "type": "string" + }, + "maxmemoryPolicy": { + "default": "allkeys-lru", + "description": "`--maxmemory-policy` flag passed to `redis-server`.", + "title": "maxmemoryPolicy", + "type": "string" + }, + "resources": { + "description": "Resource requests/limits for the telemetry Redis container. [Reference][resources].", + "properties": { + "limits": { + "properties": { + "cpu": { + "default": "250m", + "title": "cpu", + "type": "string" + }, + "memory": { + "default": "512Mi", + "title": "memory", + "type": "string" + } + }, + "required": [], + "title": "limits", + "type": "object" + }, + "requests": { + "properties": { + "cpu": { + "default": "100m", + "title": "cpu", + "type": "string" + }, + "memory": { + "default": "256Mi", + "title": "memory", + "type": "string" + } + }, + "required": [], + "title": "requests", + "type": "object" + } + }, + "required": [], + "title": "resources", + "type": "object" + }, + "storage": { + "default": "1Gi", + "description": "Persistent volume size for the telemetry Redis PVC.", + "title": "storage", + "type": "string" + } + }, + "required": [], + "title": "redis", + "type": "object" + }, + "serviceAccounts": { + "description": "ServiceAccount names (in `namespace.name`) bound to the telemetry\npod-logs Role. Each entry becomes a `ServiceAccount` subject on the\ngenerated RoleBinding. When empty, the RoleBinding subjects default to\nthe `default` ServiceAccount.", + "items": { + "required": [] + }, + "title": "serviceAccounts", + "type": "array" + } + }, + "required": [], + "title": "telemetry", + "type": "object" } }, "required": [], diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index ef9deab1d..89780b6e1 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -1642,3 +1642,40 @@ teamsAppSettings: volumeMounts: [] # -- Volumes for `teams-app` pods. [Reference][volumes]. volumes: [] + +# In-cluster telemetry support. When enabled, the chart renders a small +# Redis Deployment + Service (used by `telemetry-sidecar` containers +# configured on workload pods via `extraContainers`) plus a Role/RoleBinding +# granting the sidecar's service accounts read access to `pods/log`. +# The chart itself does NOT inject `telemetry-sidecar` containers; consumers +# wire those into `extraContainers` on the relevant workloads and point the +# `FIFTYONE_TELEMETRY_REDIS_URL` env var at this service. +telemetry: + # -- Whether to render the telemetry Redis Deployment/Service and the + # telemetry pod-logs Role/RoleBinding. Defaults to `true` so consumers + # that wire up `telemetry-sidecar` containers get a working redis without + # additional opt-in. Set to `false` to skip rendering all telemetry-related + # resources. + enabled: true + redis: + # -- Container image for the telemetry Redis Deployment. + image: redis:7-alpine + # -- Persistent volume size for the telemetry Redis PVC. + storage: 1Gi + # -- `--maxmemory` flag passed to `redis-server`. + maxmemory: 400mb + # -- `--maxmemory-policy` flag passed to `redis-server`. + maxmemoryPolicy: allkeys-lru + # -- Resource requests/limits for the telemetry Redis container. [Reference][resources]. + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 250m + memory: 512Mi + # -- ServiceAccount names (in `namespace.name`) bound to the telemetry + # pod-logs Role. Each entry becomes a `ServiceAccount` subject on the + # generated RoleBinding. When empty, the RoleBinding subjects default to + # the `default` ServiceAccount. + serviceAccounts: [] diff --git a/tests/unit/helm/common_test.go b/tests/unit/helm/common_test.go index 2ce0909b0..f4bf76ef0 100644 --- a/tests/unit/helm/common_test.go +++ b/tests/unit/helm/common_test.go @@ -1,5 +1,5 @@ -//go:build all || helm || unit || unitApiDeployment || unitApiService || unitAppDeployment || unitAppHpa || unitAppService || unitCasDeployment || unitCasService || unitIngress || unitNamespace || unitPluginsDeployment || unitHpaPlugins || unitPluginsService || unitSecrets || unitServiceAccount || unitTeamsAppDeployment || unitTeamsAppHpa || unitTeamsAppService -// +build all helm unit unitApiDeployment unitApiService unitAppDeployment unitAppHpa unitAppService unitCasDeployment unitCasService unitIngress unitNamespace unitPluginsDeployment unitHpaPlugins unitPluginsService unitSecrets unitServiceAccount unitTeamsAppDeployment unitTeamsAppHpa unitTeamsAppService +//go:build all || helm || unit || unitApiDeployment || unitApiService || unitAppDeployment || unitAppHpa || unitAppService || unitCasDeployment || unitCasService || unitIngress || unitNamespace || unitPluginsDeployment || unitHpaPlugins || unitPluginsService || unitSecrets || unitServiceAccount || unitTeamsAppDeployment || unitTeamsAppHpa || unitTeamsAppService || unitTelemetryRedis || unitTelemetryRoleBinding +// +build all helm unit unitApiDeployment unitApiService unitAppDeployment unitAppHpa unitAppService unitCasDeployment unitCasService unitIngress unitNamespace unitPluginsDeployment unitHpaPlugins unitPluginsService unitSecrets unitServiceAccount unitTeamsAppDeployment unitTeamsAppHpa unitTeamsAppService unitTelemetryRedis unitTelemetryRoleBinding package unit diff --git a/tests/unit/helm/telemetry-redis_test.go b/tests/unit/helm/telemetry-redis_test.go new file mode 100644 index 000000000..8ce69c431 --- /dev/null +++ b/tests/unit/helm/telemetry-redis_test.go @@ -0,0 +1,190 @@ +//go:build kubeall || helm || unit || unitTelemetryRedis +// +build kubeall helm unit unitTelemetryRedis + +package unit + +import ( + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" +) + +type telemetryRedisTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestTelemetryRedisTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &telemetryRedisTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/telemetry-redis.yaml"}, + }) +} + +func (s *telemetryRedisTemplateTest) TestEnabledByDefault() { + options := &helm.Options{SetValues: nil} + + output, err := helm.RenderTemplateE(s.T(), options, s.chartPath, s.releaseName, s.templates) + s.Require().NoError(err) + s.Contains(output, "kind: Deployment", "Redis Deployment should be rendered by default") + s.Contains(output, "kind: Service", "Redis Service should be rendered by default") + s.Contains(output, "kind: PersistentVolumeClaim", "Redis PVC should be rendered by default") +} + +func (s *telemetryRedisTemplateTest) TestExplicitlyDisabled() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.enabled": "false", + }} + + _, err := helm.RenderTemplateE(s.T(), options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/telemetry-redis.yaml in chart") +} + +// extractDeployment finds the Deployment document in the multi-doc render output. +func (s *telemetryRedisTemplateTest) extractDeployment(output string) appsv1.Deployment { + for _, doc := range splitYAMLDocs(output) { + if !strings.Contains(doc, "kind: Deployment") { + continue + } + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(s.T(), doc, &deployment) + return deployment + } + s.Fail("Deployment document not found in rendered output") + return appsv1.Deployment{} +} + +func (s *telemetryRedisTemplateTest) TestDeploymentMetadata() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.enabled": "true", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + deployment := s.extractDeployment(output) + + expectedName := fmt.Sprintf("%s-telemetry-redis", s.releaseName) + s.Equal(expectedName, deployment.ObjectMeta.Name, "Deployment name should be release-prefixed") + s.Equal("fiftyone-teams", deployment.ObjectMeta.Namespace, "Deployment namespace should default to fiftyone-teams") + s.Equal("telemetry-redis", deployment.ObjectMeta.Labels["app.kubernetes.io/name"]) + s.Equal(s.releaseName, deployment.ObjectMeta.Labels["app.kubernetes.io/instance"]) + s.Equal("telemetry-redis", deployment.ObjectMeta.Labels["app.voxel51.com/component"]) +} + +func (s *telemetryRedisTemplateTest) TestDeploymentNamespaceOverride() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.enabled": "true", + "namespace.name": "my-ns", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + deployment := s.extractDeployment(output) + s.Equal("my-ns", deployment.ObjectMeta.Namespace) +} + +func (s *telemetryRedisTemplateTest) TestDeploymentDefaultImage() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.enabled": "true", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + deployment := s.extractDeployment(output) + + containers := deployment.Spec.Template.Spec.Containers + s.Require().Len(containers, 1, "Deployment should have exactly one container") + s.Equal("redis", containers[0].Name) + s.Equal("redis:7-alpine", containers[0].Image) +} + +func (s *telemetryRedisTemplateTest) TestDeploymentImageOverride() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.enabled": "true", + "telemetry.redis.image": "my-registry/redis:custom", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + deployment := s.extractDeployment(output) + s.Equal("my-registry/redis:custom", deployment.Spec.Template.Spec.Containers[0].Image) +} + +func (s *telemetryRedisTemplateTest) TestDeploymentRedisArgs() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.enabled": "true", + "telemetry.redis.maxmemory": "1gb", + "telemetry.redis.maxmemoryPolicy": "noeviction", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + deployment := s.extractDeployment(output) + + args := deployment.Spec.Template.Spec.Containers[0].Args + s.Contains(args, "redis-server") + s.Contains(args, "1gb", "maxmemory arg should be present") + s.Contains(args, "noeviction", "maxmemory-policy arg should be present") +} + +func (s *telemetryRedisTemplateTest) TestServiceMetadata() { + // The render output contains PVC + Deployment + Service. Use a Service-only + // unmarshal by isolating the Service doc. + options := &helm.Options{SetValues: map[string]string{ + "telemetry.enabled": "true", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + + for _, doc := range splitYAMLDocs(output) { + if !strings.Contains(doc, "kind: Service") { + continue + } + var svc corev1.Service + helm.UnmarshalK8SYaml(s.T(), doc, &svc) + expectedName := fmt.Sprintf("%s-telemetry-redis", s.releaseName) + s.Equal(expectedName, svc.ObjectMeta.Name) + s.Require().Len(svc.Spec.Ports, 1) + s.EqualValues(6379, svc.Spec.Ports[0].Port) + return + } + s.Fail("Service document not found in rendered output") +} + +func (s *telemetryRedisTemplateTest) TestPVCMetadata() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.enabled": "true", + "telemetry.redis.storage": "5Gi", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + + for _, doc := range splitYAMLDocs(output) { + if !strings.Contains(doc, "kind: PersistentVolumeClaim") { + continue + } + var pvc corev1.PersistentVolumeClaim + helm.UnmarshalK8SYaml(s.T(), doc, &pvc) + expectedName := fmt.Sprintf("%s-telemetry-redis-data", s.releaseName) + s.Equal(expectedName, pvc.ObjectMeta.Name) + req := pvc.Spec.Resources.Requests[corev1.ResourceStorage] + s.Equal("5Gi", req.String()) + return + } + s.Fail("PVC document not found in rendered output") +} diff --git a/tests/unit/helm/telemetry-rolebinding_test.go b/tests/unit/helm/telemetry-rolebinding_test.go new file mode 100644 index 000000000..7b052daf4 --- /dev/null +++ b/tests/unit/helm/telemetry-rolebinding_test.go @@ -0,0 +1,182 @@ +//go:build kubeall || helm || unit || unitTelemetryRoleBinding +// +build kubeall helm unit unitTelemetryRoleBinding + +package unit + +import ( + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + rbacv1 "k8s.io/api/rbac/v1" +) + +type telemetryRoleBindingTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string + templates []string +} + +func TestTelemetryRoleBindingTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &telemetryRoleBindingTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + templates: []string{"templates/telemetry-rolebinding.yaml"}, + }) +} + +func (s *telemetryRoleBindingTemplateTest) TestEnabledByDefault() { + options := &helm.Options{SetValues: nil} + + output, err := helm.RenderTemplateE(s.T(), options, s.chartPath, s.releaseName, s.templates) + s.Require().NoError(err) + s.Contains(output, "kind: Role", "Role should be rendered by default") + s.Contains(output, "kind: RoleBinding", "RoleBinding should be rendered by default") +} + +func (s *telemetryRoleBindingTemplateTest) TestExplicitlyDisabled() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.enabled": "false", + }} + + _, err := helm.RenderTemplateE(s.T(), options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/telemetry-rolebinding.yaml in chart") +} + +// extractRole finds the Role document (not RoleBinding) in multi-doc render output. +func (s *telemetryRoleBindingTemplateTest) extractRole(output string) (rbacv1.Role, bool) { + for _, doc := range splitYAMLDocs(output) { + if !strings.Contains(doc, "\nkind: Role\n") { + continue + } + var role rbacv1.Role + helm.UnmarshalK8SYaml(s.T(), doc, &role) + return role, true + } + return rbacv1.Role{}, false +} + +// extractRoleBinding finds the RoleBinding document in multi-doc render output. +func (s *telemetryRoleBindingTemplateTest) extractRoleBinding(output string) (rbacv1.RoleBinding, bool) { + for _, doc := range splitYAMLDocs(output) { + if !strings.Contains(doc, "kind: RoleBinding") { + continue + } + var rb rbacv1.RoleBinding + helm.UnmarshalK8SYaml(s.T(), doc, &rb) + return rb, true + } + return rbacv1.RoleBinding{}, false +} + +func (s *telemetryRoleBindingTemplateTest) TestRoleMetadata() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.enabled": "true", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + role, ok := s.extractRole(output) + s.Require().True(ok, "Role document not found in rendered output") + + expectedName := fmt.Sprintf("%s-telemetry-pod-logs", s.releaseName) + s.Equal(expectedName, role.ObjectMeta.Name) + s.Equal("fiftyone-teams", role.ObjectMeta.Namespace) + s.Equal("telemetry", role.ObjectMeta.Labels["app.kubernetes.io/name"]) + s.Equal(s.releaseName, role.ObjectMeta.Labels["app.kubernetes.io/instance"]) + s.Equal("telemetry", role.ObjectMeta.Labels["app.voxel51.com/component"]) +} + +func (s *telemetryRoleBindingTemplateTest) TestRoleRules() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.enabled": "true", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + role, ok := s.extractRole(output) + s.Require().True(ok, "Role document not found in rendered output") + + s.Require().Len(role.Rules, 2, "Role should have exactly two rules") + + var hasPodsGet, hasPodsLogGet bool + for _, rule := range role.Rules { + for _, resource := range rule.Resources { + if resource == "pods" { + s.Contains(rule.Verbs, "get", "pods rule should grant get") + hasPodsGet = true + } + if resource == "pods/log" { + s.Contains(rule.Verbs, "get", "pods/log rule should grant get") + hasPodsLogGet = true + } + } + } + s.True(hasPodsGet, "Role should grant get on pods") + s.True(hasPodsLogGet, "Role should grant get on pods/log") +} + +func (s *telemetryRoleBindingTemplateTest) TestRoleBindingDefaultSubject() { + // With an unset serviceAccounts list, the binding should fall back to + // a single subject pointing at the "default" ServiceAccount in the + // chart namespace. + options := &helm.Options{SetValues: map[string]string{ + "telemetry.enabled": "true", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + rb, ok := s.extractRoleBinding(output) + s.Require().True(ok, "RoleBinding document not found in rendered output") + + s.Require().Len(rb.Subjects, 1, "Default RoleBinding should have exactly one subject") + s.Equal("ServiceAccount", rb.Subjects[0].Kind) + s.Equal("default", rb.Subjects[0].Name) + s.Equal("fiftyone-teams", rb.Subjects[0].Namespace) +} + +func (s *telemetryRoleBindingTemplateTest) TestRoleBindingMultipleSubjects() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.enabled": "true", + "telemetry.serviceAccounts[0]": "sa-one", + "telemetry.serviceAccounts[1]": "sa-two", + "namespace.name": "my-ns", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + rb, ok := s.extractRoleBinding(output) + s.Require().True(ok, "RoleBinding document not found in rendered output") + + s.Require().Len(rb.Subjects, 2) + s.Equal("sa-one", rb.Subjects[0].Name) + s.Equal("my-ns", rb.Subjects[0].Namespace) + s.Equal("sa-two", rb.Subjects[1].Name) + s.Equal("my-ns", rb.Subjects[1].Namespace) +} + +func (s *telemetryRoleBindingTemplateTest) TestRoleBindingRoleRef() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.enabled": "true", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + rb, ok := s.extractRoleBinding(output) + s.Require().True(ok, "RoleBinding document not found in rendered output") + + expectedName := fmt.Sprintf("%s-telemetry-pod-logs", s.releaseName) + s.Equal("Role", rb.RoleRef.Kind) + s.Equal(expectedName, rb.RoleRef.Name) + s.Equal("rbac.authorization.k8s.io", rb.RoleRef.APIGroup) +} diff --git a/tests/unit/helm/yaml_helpers.go b/tests/unit/helm/yaml_helpers.go new file mode 100644 index 000000000..05e13c0e7 --- /dev/null +++ b/tests/unit/helm/yaml_helpers.go @@ -0,0 +1,17 @@ +package unit + +import "strings" + +// splitYAMLDocs splits a multi-doc YAML string on `---` separators. +// Empty/whitespace-only docs are dropped. +func splitYAMLDocs(s string) []string { + out := []string{} + for _, raw := range strings.Split(s, "\n---") { + doc := strings.TrimSpace(raw) + if doc == "" { + continue + } + out = append(out, doc) + } + return out +} From 8348c4b20c1fd567c585aa9344e86365a890091c Mon Sep 17 00:00:00 2001 From: kacey Date: Tue, 19 May 2026 09:37:13 -0700 Subject: [PATCH 16/49] feat: enable sidecar by default in docker deployments --- docker/README.md | 8 +- docker/common-services.yaml | 13 +- docker/docs/configuring-telemetry.md | 87 +++++++---- .../compose.dedicated-plugins.yaml | 75 ++++++++++ .../compose.delegated-operators.gpu.yaml | 5 +- .../compose.delegated-operators.yaml | 34 +++++ docker/internal-auth/compose.plugins.yaml | 56 +++++++ ...compose.telemetry.delegated-operators.yaml | 62 -------- .../compose.telemetry.plugins.yaml | 37 ----- docker/internal-auth/compose.telemetry.yaml | 90 ----------- docker/internal-auth/compose.yaml | 66 ++++++++ .../compose.dedicated-plugins.yaml | 75 ++++++++++ .../compose.delegated-operators.gpu.yaml | 5 +- .../compose.delegated-operators.yaml | 34 +++++ docker/legacy-auth/compose.plugins.yaml | 56 +++++++ ...compose.telemetry.delegated-operators.yaml | 62 -------- .../compose.telemetry.plugins.yaml | 37 ----- docker/legacy-auth/compose.telemetry.yaml | 90 ----------- docker/legacy-auth/compose.yaml | 66 ++++++++ .../configuring-kubernetes-orchestrator.md | 12 +- .../docker-compose-internal-auth_test.go | 138 +++++++++-------- .../docker-compose-legacy-auth_test.go | 141 +++++++++--------- utils/validate-docker-pulls.sh | 1 + 23 files changed, 685 insertions(+), 565 deletions(-) delete mode 100644 docker/internal-auth/compose.telemetry.delegated-operators.yaml delete mode 100644 docker/internal-auth/compose.telemetry.plugins.yaml delete mode 100644 docker/internal-auth/compose.telemetry.yaml delete mode 100644 docker/legacy-auth/compose.telemetry.delegated-operators.yaml delete mode 100644 docker/legacy-auth/compose.telemetry.plugins.yaml delete mode 100644 docker/legacy-auth/compose.telemetry.yaml diff --git a/docker/README.md b/docker/README.md index d47c7c771..ba9ea9feb 100644 --- a/docker/README.md +++ b/docker/README.md @@ -659,10 +659,10 @@ for questions regarding storage and encryption. ### Telemetry -FiftyOne Enterprise ships an optional telemetry overlay that exposes live -per-service metrics (CPU, memory, FDs, thread counts) and tailed logs on -the Settings → Metrics page. Enable by layering `compose.telemetry.yaml` -onto your existing compose invocation. +FiftyOne Enterprise bundles a telemetry sidecar + Redis backend by +default in every compose file. The Settings → Metrics page in teams-app +exposes live per-service metrics (CPU, memory, FDs, thread counts) and +tailed logs. No opt-in flags are required. Please refer to the [telemetry configuration documentation](./docs/configuring-telemetry.md) diff --git a/docker/common-services.yaml b/docker/common-services.yaml index bc71259a8..04067e940 100644 --- a/docker/common-services.yaml +++ b/docker/common-services.yaml @@ -17,6 +17,7 @@ services: FIFTYONE_MEDIA_CACHE_APP_IMAGES: false FIFTYONE_MEDIA_CACHE_SIZE_BYTES: -1 FIFTYONE_SIGNED_URL_EXPIRATION: ${FIFTYONE_SIGNED_URL_EXPIRATION:-24} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} # If you are routing through a proxy server you will want to set # HTTP_PROXY_URL, HTTPS_PROXY_URL, and NO_PROXY_LIST in your .env # then add the following environment variables to your @@ -43,6 +44,7 @@ services: FIFTYONE_ENV: ${FIFTYONE_ENV} FIFTYONE_INTERNAL_SERVICE: true FIFTYONE_LOGGING_FORMAT: ${FIFTYONE_LOGGING_FORMAT:-text} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} GRAPHQL_DEFAULT_LIMIT: ${GRAPHQL_DEFAULT_LIMIT} LOGGING_LEVEL: ${API_LOGGING_LEVEL:-INFO} MONGO_DEFAULT_DB: ${FIFTYONE_DATABASE_NAME} @@ -72,6 +74,7 @@ services: FIFTYONE_SERVER_ADDRESS: "" FIFTYONE_SERVER_PATH_PREFIX: /api/proxy/fiftyone-teams FIFTYONE_TEAMS_PROXY_URL: ${FIFTYONE_TEAMS_PROXY_URL} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} NODE_ENV: production RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED: false FIFTYONE_APP_ANONYMOUS_ANALYTICS_ENABLED: ${FIFTYONE_APP_ANONYMOUS_ANALYTICS_ENABLED:-true} @@ -144,6 +147,7 @@ services: FIFTYONE_MEDIA_CACHE_APP_IMAGES: false FIFTYONE_MEDIA_CACHE_SIZE_BYTES: -1 FIFTYONE_PLUGINS_DIR: /opt/plugins + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} # If you are routing through a proxy server you will want to set # HTTP_PROXY_URL, HTTPS_PROXY_URL, and NO_PROXY_LIST in your .env # then add the following environment variables to your @@ -158,8 +162,12 @@ services: teams-do-common: image: voxel51/fiftyone-teams-cv-full:v2.19.0 + # Telemetry default-on requires teams-do to share its PID namespace + # with a single sidecar (compose's `pid: "service:"` only joins + # one replica). Force replicas=1 to keep the 1:1 pairing deterministic. + # See docker/docs/configuring-telemetry.md for multi-worker patterns. deploy: - replicas: ${FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS:-3} + replicas: 1 command: > /bin/sh -c "fiftyone delegated launch -t remote -m" environment: @@ -171,6 +179,9 @@ services: FIFTYONE_INTERNAL_SERVICE: true FIFTYONE_MEDIA_CACHE_SIZE_BYTES: -1 FIFTYONE_PLUGINS_DIR: /opt/plugins + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + TELEMETRY_SOCKET: /tmp/telemetry/agent.sock restart: always volumes: - plugins-vol:/opt/plugins:ro + - telemetry-socket:/tmp/telemetry diff --git a/docker/docs/configuring-telemetry.md b/docker/docs/configuring-telemetry.md index 0ecadcdac..99f7d0bb1 100644 --- a/docker/docs/configuring-telemetry.md +++ b/docker/docs/configuring-telemetry.md @@ -14,39 +14,38 @@ # Configuring FiftyOne Enterprise Telemetry -The telemetry overlay adds a lightweight per-service metrics collector -(sidecar pattern) plus a Redis backend. Once enabled, the Settings → -Metrics page in teams-app displays live CPU / memory / thread / file-descriptor -samples and tailed stdout logs for each observed service. +Telemetry adds a lightweight per-service metrics collector (sidecar +pattern) plus a Redis backend. The Settings → Metrics page in teams-app +displays live CPU / memory / thread / file-descriptor samples and tailed +stdout logs for each observed service. -This overlay is optional and layered on top of the main compose file. +**Telemetry is enabled by default.** The base compose files bundle the +`telemetry-redis` service and per-workload sidecars; no opt-in flags or +overlay files are needed. -## Enable - -Use the `compose.telemetry.yaml` overlay alongside your normal deployment: +## Default deployment ```shell -docker compose \ - -f compose.yaml \ - -f compose.telemetry.yaml \ - up -d +docker compose -f compose.yaml up -d ``` -Can be combined with the plugins or delegated-operators overlays. Each -optional service has its own companion telemetry overlay so its sidecar -is added only when that service is enabled: +renders `fiftyone-app`, `teams-api`, `teams-app`, `teams-cas`, +`telemetry-redis`, `fiftyone-app-telemetry`, and `teams-api-telemetry`. + +Combine with optional overlays as before — each carries its own bundled +sidecar: ```shell docker compose \ -f compose.yaml \ -f compose.dedicated-plugins.yaml \ -f compose.delegated-operators.yaml \ - -f compose.telemetry.yaml \ - -f compose.telemetry.plugins.yaml \ - -f compose.telemetry.delegated-operators.yaml \ up -d ``` +This adds `teams-plugins`, `teams-plugins-telemetry`, `teams-do`, and +`teams-do-telemetry` in addition to the default set. + For GPU-enabled delegated operators, layer `compose.delegated-operators.gpu.yaml` (which includes its own bundled telemetry sidecar) and activate the `gpu` profile: @@ -56,12 +55,10 @@ docker compose --profile gpu \ -f compose.yaml \ -f compose.delegated-operators.yaml \ -f compose.delegated-operators.gpu.yaml \ - -f compose.telemetry.yaml \ - -f compose.telemetry.delegated-operators.yaml \ up -d ``` -## What it adds +## What's bundled by default - `telemetry-redis` — Redis 7 container that holds metric streams and log entries. Data is capped by a maxmemory policy (`allkeys-lru`) so disk usage @@ -71,11 +68,10 @@ docker compose --profile gpu \ `pid: "service:"` so it can read `/proc//fd/1` and use psutil to sample CPU, memory, FDs, and thread counts. Sidecars run with the `SYS_PTRACE` capability so py-spy can attach to the target. -- `teams-plugins-telemetry` (only with `compose.telemetry.plugins.yaml`) — +- `teams-plugins-telemetry` (only with `compose.dedicated-plugins.yaml`) — sidecar for the dedicated `teams-plugins` service. -- `teams-do-telemetry` (only with - `compose.telemetry.delegated-operators.yaml`) — sidecar in - `EXECUTOR_SIDECAR=true` mode that watches the executor for +- `teams-do-telemetry` (only with `compose.delegated-operators.yaml`) — + sidecar in `EXECUTOR_SIDECAR=true` mode that watches the executor for per-operation child processes and records per-op metrics back to the `delegated_ops` MongoDB document. - `teams-do-gpu` + `teams-do-gpu-telemetry` (only with @@ -84,15 +80,46 @@ docker compose --profile gpu \ orchestrator (`-n teams-do-gpu`) plus its paired sidecar. Sidecar reads GPU metrics via NVML and requires its own GPU reservation. - `FIFTYONE_TELEMETRY_REDIS_URL` injected on `fiftyone-app`, `teams-api`, - `teams-app`, and (when the DO overlay is used) `teams-do` so the in-app - telemetry blueprint and SSE endpoints can read from Redis. + `teams-app`, `teams-plugins`, and (when the DO overlay is used) + `teams-do` so the in-app telemetry blueprint and SSE endpoints can + read from Redis. + +## Opt out + +To run without telemetry, add a `compose.override.yaml` that scales the +telemetry services to zero replicas: + +```yaml +services: + telemetry-redis: + deploy: + replicas: 0 + fiftyone-app-telemetry: + deploy: + replicas: 0 + teams-api-telemetry: + deploy: + replicas: 0 + # Only needed when running the corresponding overlay: + teams-plugins-telemetry: + deploy: + replicas: 0 + teams-do-telemetry: + deploy: + replicas: 0 +``` + +`docker compose -f compose.yaml -f compose.override.yaml up -d` will start +the base services without the telemetry collector. The main containers +will still have `FIFTYONE_TELEMETRY_REDIS_URL` set, but the in-app agent +gracefully no-ops when Redis is unreachable. ### Scaling teams-do with telemetry docker-compose's `pid: "service:"` only joins a single replica's -PID namespace. To keep the sidecar observation honest, the telemetry DO -overlay **forces `teams-do` replicas to 1**, overriding -`FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS`. +PID namespace. To keep the sidecar observation honest, `teams-do-common` +**forces `teams-do` replicas to 1**, overriding any +`FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS` setting. If you need more than one delegated-operator worker observed at the same time, either: diff --git a/docker/internal-auth/compose.dedicated-plugins.yaml b/docker/internal-auth/compose.dedicated-plugins.yaml index 1cf90e790..e037e6ef0 100644 --- a/docker/internal-auth/compose.dedicated-plugins.yaml +++ b/docker/internal-auth/compose.dedicated-plugins.yaml @@ -39,5 +39,80 @@ services: volumes: - plugins-vol:/opt/plugins:ro + telemetry-redis: + image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} + command: + - redis-server + - --save + - "60" + - "1" + - --dir + - /data + - --maxmemory + - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} + - --maxmemory-policy + - allkeys-lru + restart: always + volumes: + - telemetry-redis-data:/data + + fiftyone-app-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:fiftyone-app" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: fiftyone-app + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: fiftyone-app + TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + fiftyone-app: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + + teams-api-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-api" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: teams-api + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: teams-api + TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + teams-api: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + + teams-plugins-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-plugins" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: teams-plugins + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: teams-plugins + TARGET_NAME: ${TEAMS_PLUGINS_TARGET_NAME:-hypercorn} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + teams-plugins: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + volumes: plugins-vol: + telemetry-redis-data: diff --git a/docker/internal-auth/compose.delegated-operators.gpu.yaml b/docker/internal-auth/compose.delegated-operators.gpu.yaml index 6f38f07bb..a6de978d8 100644 --- a/docker/internal-auth/compose.delegated-operators.gpu.yaml +++ b/docker/internal-auth/compose.delegated-operators.gpu.yaml @@ -10,10 +10,11 @@ # -f compose.yaml \ # -f compose.delegated-operators.yaml \ # -f compose.delegated-operators.gpu.yaml \ -# -f compose.telemetry.yaml \ -# -f compose.telemetry.delegated-operators.yaml \ # up -d # +# Telemetry (redis + paired sidecars) is bundled by default in the base +# compose.yaml and compose.delegated-operators.yaml files. +# # For Proxy Server instructions please see # https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies services: diff --git a/docker/internal-auth/compose.delegated-operators.yaml b/docker/internal-auth/compose.delegated-operators.yaml index 5337711f8..2daddc633 100644 --- a/docker/internal-auth/compose.delegated-operators.yaml +++ b/docker/internal-auth/compose.delegated-operators.yaml @@ -1,4 +1,12 @@ --- +# Layered on top of a base compose file (e.g. compose.yaml). The base +# compose file is expected to provide the `telemetry-redis` service. +# +# `teams-do` is forced to `replicas: 1` (see teams-do-common in +# ../common-services.yaml) so the paired `teams-do-telemetry` sidecar's +# `pid: "service:teams-do"` joins a single deterministic PID namespace. +# For multi-worker patterns see docker/docs/configuring-telemetry.md. +# # For Proxy Server instructions please see # https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies services: @@ -7,5 +15,31 @@ services: file: ../common-services.yaml service: teams-do-common + teams-do-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-do" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: teams-do + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: delegated-operator + TARGET_NAME: ${TEAMS_DO_TARGET_NAME:-fiftyone delegated} + EXECUTOR_SIDECAR: "true" + TELEMETRY_SOCKET: /tmp/telemetry/agent.sock + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} + FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + volumes: + - telemetry-socket:/tmp/telemetry + depends_on: + teams-do: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + volumes: plugins-vol: + telemetry-socket: diff --git a/docker/internal-auth/compose.plugins.yaml b/docker/internal-auth/compose.plugins.yaml index 1eefc9b95..66fb916a4 100644 --- a/docker/internal-auth/compose.plugins.yaml +++ b/docker/internal-auth/compose.plugins.yaml @@ -34,5 +34,61 @@ services: file: ../common-services.yaml service: teams-cas-common + telemetry-redis: + image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} + command: + - redis-server + - --save + - "60" + - "1" + - --dir + - /data + - --maxmemory + - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} + - --maxmemory-policy + - allkeys-lru + restart: always + volumes: + - telemetry-redis-data:/data + + fiftyone-app-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:fiftyone-app" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: fiftyone-app + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: fiftyone-app + TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + fiftyone-app: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + + teams-api-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-api" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: teams-api + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: teams-api + TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + teams-api: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + volumes: plugins-vol: + telemetry-redis-data: diff --git a/docker/internal-auth/compose.telemetry.delegated-operators.yaml b/docker/internal-auth/compose.telemetry.delegated-operators.yaml deleted file mode 100644 index f6a1c7d51..000000000 --- a/docker/internal-auth/compose.telemetry.delegated-operators.yaml +++ /dev/null @@ -1,62 +0,0 @@ ---- -# Telemetry overlay for the delegated operators (teams-do) service. -# Use alongside compose.telemetry.yaml and compose.delegated-operators.yaml: -# -# docker compose -f compose.yaml \ -# -f compose.delegated-operators.yaml \ -# -f compose.telemetry.yaml \ -# -f compose.telemetry.delegated-operators.yaml \ -# up -d -# -# The EXECUTOR_SIDECAR mode watches the teams-do executor for per-operation -# child processes and records per-op metrics back to the delegated_ops -# MongoDB document (via FIFTYONE_DATABASE_URI). -# -# This overlay FORCES `teams-do` replicas to 1 so the sidecar has a -# deterministic 1:1 pairing via the shared PID namespace. docker-compose's -# `pid: "service:"` only joins a single replica's PID namespace, -# so running multiple replicas with a single sidecar would silently miss -# the other replicas. If you need multiple DO workers observed, run one -# teams-do + sidecar pair per worker by defining additional services -# (teams-do-1, teams-do-2, ...), or switch to the helm chart which scales -# the sidecar alongside each pod automatically. -# -# For Proxy Server instructions please see -# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies -services: - teams-do: - environment: - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - TELEMETRY_SOCKET: /tmp/telemetry/agent.sock - deploy: - replicas: 1 - volumes: - - telemetry-socket:/tmp/telemetry - - teams-do-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} - pid: "service:teams-do" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: teams-do - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: delegated-operator - TARGET_NAME: ${TEAMS_DO_TARGET_NAME:-fiftyone delegated} - EXECUTOR_SIDECAR: "true" - TELEMETRY_SOCKET: /tmp/telemetry/agent.sock - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} - FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} - volumes: - - telemetry-socket:/tmp/telemetry - depends_on: - teams-do: - condition: service_started - restart: true - telemetry-redis: - condition: service_started - restart: always - -volumes: - telemetry-socket: diff --git a/docker/internal-auth/compose.telemetry.plugins.yaml b/docker/internal-auth/compose.telemetry.plugins.yaml deleted file mode 100644 index ac1de8bf2..000000000 --- a/docker/internal-auth/compose.telemetry.plugins.yaml +++ /dev/null @@ -1,37 +0,0 @@ ---- -# Telemetry overlay for the dedicated teams-plugins service. The sidecar -# shares the target's PID namespace via `pid: "service:teams-plugins"`. -# -# Layered after compose.dedicated-plugins.yaml + compose.telemetry.yaml: -# -# docker compose -f compose.yaml \ -# -f compose.dedicated-plugins.yaml \ -# -f compose.telemetry.yaml \ -# -f compose.telemetry.plugins.yaml \ -# up -d -# -# For Proxy Server instructions please see -# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies -services: - teams-plugins: - environment: - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - - teams-plugins-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} - pid: "service:teams-plugins" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: teams-plugins - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: teams-plugins - TARGET_NAME: ${TEAMS_PLUGINS_TARGET_NAME:-hypercorn} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - depends_on: - teams-plugins: - condition: service_started - restart: true - telemetry-redis: - condition: service_started - restart: always diff --git a/docker/internal-auth/compose.telemetry.yaml b/docker/internal-auth/compose.telemetry.yaml deleted file mode 100644 index 89b9d57a4..000000000 --- a/docker/internal-auth/compose.telemetry.yaml +++ /dev/null @@ -1,90 +0,0 @@ ---- -# Telemetry overlay. Adds a Redis backend + sidecar containers that observe -# each target service via a shared PID namespace, publishing metrics and -# log events to Redis. -# -# Layered on top of the main compose file, e.g.: -# docker compose -f compose.yaml -f compose.telemetry.yaml up -# -# Or with delegated operators and/or dedicated plugins: -# docker compose -f compose.yaml \ -# -f compose.delegated-operators.yaml \ -# -f compose.dedicated-plugins.yaml \ -# -f compose.telemetry.yaml up -# -# For Proxy Server instructions please see -# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies -services: - telemetry-redis: - image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} - command: - - redis-server - - --save - - "60" - - "1" - - --dir - - /data - - --maxmemory - - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} - - --maxmemory-policy - - allkeys-lru - restart: always - volumes: - - telemetry-redis-data:/data - - fiftyone-app: - environment: - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - - teams-api: - environment: - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - - teams-app: - environment: - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - - # One sidecar per observed service. `pid: "service:"` joins the - # target's PID namespace so the sidecar can read /proc//fd/1 and - # psutil can see the target's process. - - fiftyone-app-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} - pid: "service:fiftyone-app" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: fiftyone-app - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: fiftyone-app - TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - depends_on: - fiftyone-app: - condition: service_started - restart: true - telemetry-redis: - condition: service_started - restart: always - - teams-api-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} - pid: "service:teams-api" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: teams-api - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: teams-api - TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - depends_on: - teams-api: - condition: service_started - restart: true - telemetry-redis: - condition: service_started - restart: always - -volumes: - telemetry-redis-data: diff --git a/docker/internal-auth/compose.yaml b/docker/internal-auth/compose.yaml index 896585019..b2a9a83fd 100644 --- a/docker/internal-auth/compose.yaml +++ b/docker/internal-auth/compose.yaml @@ -1,6 +1,11 @@ --- # For Proxy Server instructions please see # https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies +# +# Telemetry (`telemetry-redis` + per-service sidecars) is enabled by +# default. To opt out, scale the telemetry services to 0 in a +# `compose.override.yaml`, or unset `FIFTYONE_TELEMETRY_REDIS_URL` and +# remove the sidecar services. See docker/docs/configuring-telemetry.md. services: fiftyone-app: extends: @@ -25,3 +30,64 @@ services: extends: file: ../common-services.yaml service: teams-cas-common + + telemetry-redis: + image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} + command: + - redis-server + - --save + - "60" + - "1" + - --dir + - /data + - --maxmemory + - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} + - --maxmemory-policy + - allkeys-lru + restart: always + volumes: + - telemetry-redis-data:/data + + # One sidecar per observed service. `pid: "service:"` joins the + # target's PID namespace so the sidecar can read /proc//fd/1 and + # psutil can see the target's process. + fiftyone-app-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:fiftyone-app" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: fiftyone-app + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: fiftyone-app + TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + fiftyone-app: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + + teams-api-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-api" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: teams-api + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: teams-api + TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + teams-api: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + +volumes: + telemetry-redis-data: diff --git a/docker/legacy-auth/compose.dedicated-plugins.yaml b/docker/legacy-auth/compose.dedicated-plugins.yaml index ace9a32c2..aa58adbf1 100644 --- a/docker/legacy-auth/compose.dedicated-plugins.yaml +++ b/docker/legacy-auth/compose.dedicated-plugins.yaml @@ -38,5 +38,80 @@ services: volumes: - plugins-vol:/opt/plugins:ro + telemetry-redis: + image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} + command: + - redis-server + - --save + - "60" + - "1" + - --dir + - /data + - --maxmemory + - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} + - --maxmemory-policy + - allkeys-lru + restart: always + volumes: + - telemetry-redis-data:/data + + fiftyone-app-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:fiftyone-app" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: fiftyone-app + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: fiftyone-app + TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + fiftyone-app: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + + teams-api-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-api" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: teams-api + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: teams-api + TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + teams-api: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + + teams-plugins-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-plugins" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: teams-plugins + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: teams-plugins + TARGET_NAME: ${TEAMS_PLUGINS_TARGET_NAME:-hypercorn} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + teams-plugins: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + volumes: plugins-vol: + telemetry-redis-data: diff --git a/docker/legacy-auth/compose.delegated-operators.gpu.yaml b/docker/legacy-auth/compose.delegated-operators.gpu.yaml index 6f38f07bb..a6de978d8 100644 --- a/docker/legacy-auth/compose.delegated-operators.gpu.yaml +++ b/docker/legacy-auth/compose.delegated-operators.gpu.yaml @@ -10,10 +10,11 @@ # -f compose.yaml \ # -f compose.delegated-operators.yaml \ # -f compose.delegated-operators.gpu.yaml \ -# -f compose.telemetry.yaml \ -# -f compose.telemetry.delegated-operators.yaml \ # up -d # +# Telemetry (redis + paired sidecars) is bundled by default in the base +# compose.yaml and compose.delegated-operators.yaml files. +# # For Proxy Server instructions please see # https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies services: diff --git a/docker/legacy-auth/compose.delegated-operators.yaml b/docker/legacy-auth/compose.delegated-operators.yaml index 5337711f8..2daddc633 100644 --- a/docker/legacy-auth/compose.delegated-operators.yaml +++ b/docker/legacy-auth/compose.delegated-operators.yaml @@ -1,4 +1,12 @@ --- +# Layered on top of a base compose file (e.g. compose.yaml). The base +# compose file is expected to provide the `telemetry-redis` service. +# +# `teams-do` is forced to `replicas: 1` (see teams-do-common in +# ../common-services.yaml) so the paired `teams-do-telemetry` sidecar's +# `pid: "service:teams-do"` joins a single deterministic PID namespace. +# For multi-worker patterns see docker/docs/configuring-telemetry.md. +# # For Proxy Server instructions please see # https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies services: @@ -7,5 +15,31 @@ services: file: ../common-services.yaml service: teams-do-common + teams-do-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-do" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: teams-do + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: delegated-operator + TARGET_NAME: ${TEAMS_DO_TARGET_NAME:-fiftyone delegated} + EXECUTOR_SIDECAR: "true" + TELEMETRY_SOCKET: /tmp/telemetry/agent.sock + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} + FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + volumes: + - telemetry-socket:/tmp/telemetry + depends_on: + teams-do: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + volumes: plugins-vol: + telemetry-socket: diff --git a/docker/legacy-auth/compose.plugins.yaml b/docker/legacy-auth/compose.plugins.yaml index c71f27224..f2bea08cb 100644 --- a/docker/legacy-auth/compose.plugins.yaml +++ b/docker/legacy-auth/compose.plugins.yaml @@ -33,5 +33,61 @@ services: CAS_URL: ${AUTH0_BASE_URL} NEXTAUTH_URL: ${AUTH0_BASE_URL}/cas/api/auth + telemetry-redis: + image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} + command: + - redis-server + - --save + - "60" + - "1" + - --dir + - /data + - --maxmemory + - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} + - --maxmemory-policy + - allkeys-lru + restart: always + volumes: + - telemetry-redis-data:/data + + fiftyone-app-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:fiftyone-app" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: fiftyone-app + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: fiftyone-app + TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + fiftyone-app: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + + teams-api-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-api" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: teams-api + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: teams-api + TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + teams-api: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + volumes: plugins-vol: + telemetry-redis-data: diff --git a/docker/legacy-auth/compose.telemetry.delegated-operators.yaml b/docker/legacy-auth/compose.telemetry.delegated-operators.yaml deleted file mode 100644 index f6a1c7d51..000000000 --- a/docker/legacy-auth/compose.telemetry.delegated-operators.yaml +++ /dev/null @@ -1,62 +0,0 @@ ---- -# Telemetry overlay for the delegated operators (teams-do) service. -# Use alongside compose.telemetry.yaml and compose.delegated-operators.yaml: -# -# docker compose -f compose.yaml \ -# -f compose.delegated-operators.yaml \ -# -f compose.telemetry.yaml \ -# -f compose.telemetry.delegated-operators.yaml \ -# up -d -# -# The EXECUTOR_SIDECAR mode watches the teams-do executor for per-operation -# child processes and records per-op metrics back to the delegated_ops -# MongoDB document (via FIFTYONE_DATABASE_URI). -# -# This overlay FORCES `teams-do` replicas to 1 so the sidecar has a -# deterministic 1:1 pairing via the shared PID namespace. docker-compose's -# `pid: "service:"` only joins a single replica's PID namespace, -# so running multiple replicas with a single sidecar would silently miss -# the other replicas. If you need multiple DO workers observed, run one -# teams-do + sidecar pair per worker by defining additional services -# (teams-do-1, teams-do-2, ...), or switch to the helm chart which scales -# the sidecar alongside each pod automatically. -# -# For Proxy Server instructions please see -# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies -services: - teams-do: - environment: - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - TELEMETRY_SOCKET: /tmp/telemetry/agent.sock - deploy: - replicas: 1 - volumes: - - telemetry-socket:/tmp/telemetry - - teams-do-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} - pid: "service:teams-do" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: teams-do - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: delegated-operator - TARGET_NAME: ${TEAMS_DO_TARGET_NAME:-fiftyone delegated} - EXECUTOR_SIDECAR: "true" - TELEMETRY_SOCKET: /tmp/telemetry/agent.sock - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} - FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} - volumes: - - telemetry-socket:/tmp/telemetry - depends_on: - teams-do: - condition: service_started - restart: true - telemetry-redis: - condition: service_started - restart: always - -volumes: - telemetry-socket: diff --git a/docker/legacy-auth/compose.telemetry.plugins.yaml b/docker/legacy-auth/compose.telemetry.plugins.yaml deleted file mode 100644 index ac1de8bf2..000000000 --- a/docker/legacy-auth/compose.telemetry.plugins.yaml +++ /dev/null @@ -1,37 +0,0 @@ ---- -# Telemetry overlay for the dedicated teams-plugins service. The sidecar -# shares the target's PID namespace via `pid: "service:teams-plugins"`. -# -# Layered after compose.dedicated-plugins.yaml + compose.telemetry.yaml: -# -# docker compose -f compose.yaml \ -# -f compose.dedicated-plugins.yaml \ -# -f compose.telemetry.yaml \ -# -f compose.telemetry.plugins.yaml \ -# up -d -# -# For Proxy Server instructions please see -# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies -services: - teams-plugins: - environment: - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - - teams-plugins-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} - pid: "service:teams-plugins" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: teams-plugins - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: teams-plugins - TARGET_NAME: ${TEAMS_PLUGINS_TARGET_NAME:-hypercorn} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - depends_on: - teams-plugins: - condition: service_started - restart: true - telemetry-redis: - condition: service_started - restart: always diff --git a/docker/legacy-auth/compose.telemetry.yaml b/docker/legacy-auth/compose.telemetry.yaml deleted file mode 100644 index 89b9d57a4..000000000 --- a/docker/legacy-auth/compose.telemetry.yaml +++ /dev/null @@ -1,90 +0,0 @@ ---- -# Telemetry overlay. Adds a Redis backend + sidecar containers that observe -# each target service via a shared PID namespace, publishing metrics and -# log events to Redis. -# -# Layered on top of the main compose file, e.g.: -# docker compose -f compose.yaml -f compose.telemetry.yaml up -# -# Or with delegated operators and/or dedicated plugins: -# docker compose -f compose.yaml \ -# -f compose.delegated-operators.yaml \ -# -f compose.dedicated-plugins.yaml \ -# -f compose.telemetry.yaml up -# -# For Proxy Server instructions please see -# https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies -services: - telemetry-redis: - image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} - command: - - redis-server - - --save - - "60" - - "1" - - --dir - - /data - - --maxmemory - - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} - - --maxmemory-policy - - allkeys-lru - restart: always - volumes: - - telemetry-redis-data:/data - - fiftyone-app: - environment: - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - - teams-api: - environment: - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - - teams-app: - environment: - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - - # One sidecar per observed service. `pid: "service:"` joins the - # target's PID namespace so the sidecar can read /proc//fd/1 and - # psutil can see the target's process. - - fiftyone-app-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} - pid: "service:fiftyone-app" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: fiftyone-app - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: fiftyone-app - TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - depends_on: - fiftyone-app: - condition: service_started - restart: true - telemetry-redis: - condition: service_started - restart: always - - teams-api-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} - pid: "service:teams-api" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: teams-api - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: teams-api - TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - depends_on: - teams-api: - condition: service_started - restart: true - telemetry-redis: - condition: service_started - restart: always - -volumes: - telemetry-redis-data: diff --git a/docker/legacy-auth/compose.yaml b/docker/legacy-auth/compose.yaml index c463cf25e..447c1ddf3 100644 --- a/docker/legacy-auth/compose.yaml +++ b/docker/legacy-auth/compose.yaml @@ -1,6 +1,11 @@ --- # For Proxy Server instructions please see # https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies +# +# Telemetry (`telemetry-redis` + per-service sidecars) is enabled by +# default. To opt out, scale the telemetry services to 0 in a +# `compose.override.yaml`, or unset `FIFTYONE_TELEMETRY_REDIS_URL` and +# remove the sidecar services. See docker/docs/configuring-telemetry.md. services: fiftyone-app: extends: @@ -24,3 +29,64 @@ services: extends: file: ../common-services.yaml service: teams-cas-common + + telemetry-redis: + image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} + command: + - redis-server + - --save + - "60" + - "1" + - --dir + - /data + - --maxmemory + - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} + - --maxmemory-policy + - allkeys-lru + restart: always + volumes: + - telemetry-redis-data:/data + + # One sidecar per observed service. `pid: "service:"` joins the + # target's PID namespace so the sidecar can read /proc//fd/1 and + # psutil can see the target's process. + fiftyone-app-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:fiftyone-app" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: fiftyone-app + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: fiftyone-app + TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + fiftyone-app: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + + teams-api-telemetry: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + pid: "service:teams-api" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: teams-api + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: teams-api + TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + depends_on: + teams-api: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + restart: always + +volumes: + telemetry-redis-data: diff --git a/docs/orchestrators/configuring-kubernetes-orchestrator.md b/docs/orchestrators/configuring-kubernetes-orchestrator.md index a6f8d6bd8..8e614d5bb 100644 --- a/docs/orchestrators/configuring-kubernetes-orchestrator.md +++ b/docs/orchestrators/configuring-kubernetes-orchestrator.md @@ -462,12 +462,12 @@ fom.register_orchestrator( ## Optional: Telemetry Sidecar -If your deployment runs the telemetry overlay (see the helm chart's -`extraContainers` / `nativeSidecarContainers` options or the docker -`compose.telemetry.yaml`), you can attach a per-Job telemetry sidecar to -on-demand Kubernetes orchestrators as well. This emits per-operation -metrics back to the same Redis backend so the Settings → Metrics page -sees individual delegated runs. +If your deployment runs telemetry (see the helm chart's +`extraContainers` / `nativeSidecarContainers` options, or the docker +compose files where it is bundled by default), you can attach a per-Job +telemetry sidecar to on-demand Kubernetes orchestrators as well. This +emits per-operation metrics back to the same Redis backend so the +Settings → Metrics page sees individual delegated runs. Use a Kubernetes [native sidecar](https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/) diff --git a/tests/unit/compose/docker-compose-internal-auth_test.go b/tests/unit/compose/docker-compose-internal-auth_test.go index 305e6f104..8de86dc86 100644 --- a/tests/unit/compose/docker-compose-internal-auth_test.go +++ b/tests/unit/compose/docker-compose-internal-auth_test.go @@ -29,9 +29,6 @@ var internalAuthComposePluginsFile = filepath.Join(dockerInternalAuthDir, "compo var internalAuthComposeDedicatedPluginsFile = filepath.Join(dockerInternalAuthDir, "compose.dedicated-plugins.yaml") var internalAuthComposeDelegatedOperationsFile = filepath.Join(dockerInternalAuthDir, "compose.delegated-operators.yaml") var internalAuthComposeDelegatedOperationsGpuFile = filepath.Join(dockerInternalAuthDir, "compose.delegated-operators.gpu.yaml") -var internalAuthComposeTelemetryFile = filepath.Join(dockerInternalAuthDir, "compose.telemetry.yaml") -var internalAuthComposeTelemetryPluginsFile = filepath.Join(dockerInternalAuthDir, "compose.telemetry.plugins.yaml") -var internalAuthComposeTelemetryDelegatedOperatorsFile = filepath.Join(dockerInternalAuthDir, "compose.telemetry.delegated-operators.yaml") var internalAuthEnvTemplateFilePath = filepath.Join(dockerInternalAuthDir, "env.template") type commonServicesInternalAuthDockerComposeTest struct { @@ -73,9 +70,12 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServicesNames() { nil, []string{ "fiftyone-app", + "fiftyone-app-telemetry", "teams-api", + "teams-api-telemetry", "teams-app", "teams-cas", + "telemetry-redis", }, }, { @@ -83,45 +83,6 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServicesNames() { []string{internalAuthComposePluginsFile}, s.dotEnvFiles, nil, - []string{ - "fiftyone-app", - "teams-api", - "teams-app", - "teams-cas", - }, - }, - { - "composeDedicatedPlugins", - []string{internalAuthComposeDedicatedPluginsFile}, - s.dotEnvFiles, - nil, - []string{ - "fiftyone-app", - "teams-api", - "teams-app", - "teams-cas", - "teams-plugins", - }, - }, - { - "composeDelegatedOperations", - []string{internalAuthComposeDelegatedOperationsFile}, - s.dotEnvFiles, - nil, - []string{ - "teams-do", - }, - }, - { - "composeDelegatedOperationsGpu", - []string{ - internalAuthComposeFile, - internalAuthComposeDelegatedOperationsFile, - internalAuthComposeDelegatedOperationsGpuFile, - internalAuthComposeTelemetryFile, - }, - s.dotEnvFiles, - []string{"gpu"}, []string{ "fiftyone-app", "fiftyone-app-telemetry", @@ -129,15 +90,12 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServicesNames() { "teams-api-telemetry", "teams-app", "teams-cas", - "teams-do", - "teams-do-gpu", - "teams-do-gpu-telemetry", "telemetry-redis", }, }, { - "composeTelemetry", - []string{internalAuthComposeFile, internalAuthComposeTelemetryFile}, + "composeDedicatedPlugins", + []string{internalAuthComposeDedicatedPluginsFile}, s.dotEnvFiles, nil, []string{ @@ -147,17 +105,14 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServicesNames() { "teams-api-telemetry", "teams-app", "teams-cas", + "teams-plugins", + "teams-plugins-telemetry", "telemetry-redis", }, }, { - "composeTelemetryPlugins", - []string{ - internalAuthComposeFile, - internalAuthComposeDedicatedPluginsFile, - internalAuthComposeTelemetryFile, - internalAuthComposeTelemetryPluginsFile, - }, + "composeDelegatedOperations", + []string{internalAuthComposeFile, internalAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, nil, []string{ @@ -167,21 +122,20 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServicesNames() { "teams-api-telemetry", "teams-app", "teams-cas", - "teams-plugins", - "teams-plugins-telemetry", + "teams-do", + "teams-do-telemetry", "telemetry-redis", }, }, { - "composeTelemetryDelegatedOperators", + "composeDelegatedOperationsGpu", []string{ internalAuthComposeFile, internalAuthComposeDelegatedOperationsFile, - internalAuthComposeTelemetryFile, - internalAuthComposeTelemetryDelegatedOperatorsFile, + internalAuthComposeDelegatedOperationsGpuFile, }, s.dotEnvFiles, - nil, + []string{"gpu"}, []string{ "fiftyone-app", "fiftyone-app-telemetry", @@ -190,6 +144,8 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServicesNames() { "teams-app", "teams-cas", "teams-do", + "teams-do-gpu", + "teams-do-gpu-telemetry", "teams-do-telemetry", "telemetry-redis", }, @@ -275,28 +231,28 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceImage() { { "delegatedOperationsTeamsDo", "teams-do", - []string{internalAuthComposeDelegatedOperationsFile}, + []string{internalAuthComposeFile, internalAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, "voxel51/fiftyone-teams-cv-full:v2.19.0", }, { "telemetryRedis", "telemetry-redis", - []string{internalAuthComposeFile, internalAuthComposeTelemetryFile}, + []string{internalAuthComposeFile}, s.dotEnvFiles, "redis:7-alpine", }, { "telemetrySidecarFiftyoneApp", "fiftyone-app-telemetry", - []string{internalAuthComposeFile, internalAuthComposeTelemetryFile}, + []string{internalAuthComposeFile}, s.dotEnvFiles, "voxel51/telemetry-sidecar:latest", }, { "telemetrySidecarTeamsApi", "teams-api-telemetry", - []string{internalAuthComposeFile, internalAuthComposeTelemetryFile}, + []string{internalAuthComposeFile}, s.dotEnvFiles, "voxel51/telemetry-sidecar:latest", }, @@ -306,12 +262,17 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceImage() { []string{ internalAuthComposeFile, internalAuthComposeDelegatedOperationsFile, - internalAuthComposeTelemetryFile, - internalAuthComposeTelemetryDelegatedOperatorsFile, }, s.dotEnvFiles, "voxel51/telemetry-sidecar:latest", }, + { + "telemetrySidecarTeamsPlugins", + "teams-plugins-telemetry", + []string{internalAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:latest", + }, } for _, testCase := range testCases { @@ -375,6 +336,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", "FIFTYONE_SIGNED_URL_EXPIRATION=24", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", }, }, { @@ -392,6 +354,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_ENV=production", "FIFTYONE_INTERNAL_SERVICE=true", "FIFTYONE_LOGGING_FORMAT=text", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", "GRAPHQL_DEFAULT_LIMIT=10", "LOGGING_LEVEL=INFO", "MONGO_DEFAULT_DB=fiftyone", @@ -412,6 +375,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_SERVER_ADDRESS=", "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", "NODE_ENV=production", "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false", "FIFTYONE_APP_ANONYMOUS_ANALYTICS_ENABLED=true", @@ -459,6 +423,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", "FIFTYONE_PLUGINS_DIR=/opt/plugins", "FIFTYONE_SIGNED_URL_EXPIRATION=24", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", }, }, { @@ -476,6 +441,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_ENV=production", "FIFTYONE_INTERNAL_SERVICE=true", "FIFTYONE_LOGGING_FORMAT=text", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", "GRAPHQL_DEFAULT_LIMIT=10", "LOGGING_LEVEL=INFO", "MONGO_DEFAULT_DB=fiftyone", @@ -497,6 +463,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_SERVER_ADDRESS=", "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", "NODE_ENV=production", "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false", "FIFTYONE_APP_ANONYMOUS_ANALYTICS_ENABLED=true", @@ -543,6 +510,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", "FIFTYONE_SIGNED_URL_EXPIRATION=24", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", }, }, { @@ -560,6 +528,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_ENV=production", "FIFTYONE_INTERNAL_SERVICE=true", "FIFTYONE_LOGGING_FORMAT=text", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", "GRAPHQL_DEFAULT_LIMIT=10", "LOGGING_LEVEL=INFO", "MONGO_DEFAULT_DB=fiftyone", @@ -581,6 +550,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_SERVER_ADDRESS=", "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", "NODE_ENV=production", "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false", "FIFTYONE_TEAMS_PLUGIN_URL=http://teams-plugins:5151", @@ -626,12 +596,13 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", "FIFTYONE_PLUGINS_DIR=/opt/plugins", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", }, }, { "delegatedOperationsTeamsDo", "teams-do", - []string{internalAuthComposeDelegatedOperationsFile}, + []string{internalAuthComposeFile, internalAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, []string{ "API_URL=http://teams-api:8000", @@ -642,6 +613,8 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_INTERNAL_SERVICE=true", "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", "FIFTYONE_PLUGINS_DIR=/opt/plugins", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", + "TELEMETRY_SOCKET=/tmp/telemetry/agent.sock", }, }, } @@ -832,7 +805,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceRestart() { { "delegatedOperationsTeamsDo", "teams-do", - []string{internalAuthComposeDelegatedOperationsFile}, + []string{internalAuthComposeFile, internalAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, types.RestartPolicyAlways, }, @@ -1030,7 +1003,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceVolumes() { { "delegatedOperationsTeamsDo", "teams-do", - []string{internalAuthComposeDelegatedOperationsFile}, + []string{internalAuthComposeFile, internalAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, []types.ServiceVolumeConfig{ { @@ -1040,6 +1013,13 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceVolumes() { ReadOnly: true, Volume: &types.ServiceVolumeVolume{}, }, + { + Type: "volume", + Source: "telemetry-socket", + Target: "/tmp/telemetry", + ReadOnly: false, + Volume: &types.ServiceVolumeVolume{}, + }, }, }, } @@ -1087,7 +1067,11 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestVolumes() { "default", []string{internalAuthComposeFile}, s.dotEnvFiles, - nil, + types.Volumes{ + "telemetry-redis-data": { + Name: "fiftyone-compose-test_telemetry-redis-data", + }, + }, }, { "plugins", @@ -1097,6 +1081,9 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestVolumes() { "plugins-vol": { Name: "fiftyone-compose-test_plugins-vol", }, + "telemetry-redis-data": { + Name: "fiftyone-compose-test_telemetry-redis-data", + }, }, }, { @@ -1107,16 +1094,25 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestVolumes() { "plugins-vol": { Name: "fiftyone-compose-test_plugins-vol", }, + "telemetry-redis-data": { + Name: "fiftyone-compose-test_telemetry-redis-data", + }, }, }, { "delegatedOperations", - []string{internalAuthComposeDelegatedOperationsFile}, + []string{internalAuthComposeFile, internalAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, types.Volumes{ "plugins-vol": { Name: "fiftyone-compose-test_plugins-vol", }, + "telemetry-redis-data": { + Name: "fiftyone-compose-test_telemetry-redis-data", + }, + "telemetry-socket": { + Name: "fiftyone-compose-test_telemetry-socket", + }, }, }, } diff --git a/tests/unit/compose/docker-compose-legacy-auth_test.go b/tests/unit/compose/docker-compose-legacy-auth_test.go index e10bf8078..240a86dc8 100644 --- a/tests/unit/compose/docker-compose-legacy-auth_test.go +++ b/tests/unit/compose/docker-compose-legacy-auth_test.go @@ -29,9 +29,6 @@ var legacyAuthComposePluginsFile = filepath.Join(dockerLegacyAuthDir, "compose.p var legacyAuthComposeDedicatedPluginsFile = filepath.Join(dockerLegacyAuthDir, "compose.dedicated-plugins.yaml") var legacyAuthComposeDelegatedOperationsFile = filepath.Join(dockerLegacyAuthDir, "compose.delegated-operators.yaml") var legacyAuthComposeDelegatedOperationsGpuFile = filepath.Join(dockerLegacyAuthDir, "compose.delegated-operators.gpu.yaml") -var legacyAuthComposeTelemetryFile = filepath.Join(dockerLegacyAuthDir, "compose.telemetry.yaml") -var legacyAuthComposeTelemetryPluginsFile = filepath.Join(dockerLegacyAuthDir, "compose.telemetry.plugins.yaml") -var legacyAuthComposeTelemetryDelegatedOperatorsFile = filepath.Join(dockerLegacyAuthDir, "compose.telemetry.delegated-operators.yaml") var legacyAuthEnvTemplateFilePath = filepath.Join(dockerLegacyAuthDir, "env.template") type commonServicesLegacyAuthDockerComposeTest struct { @@ -73,9 +70,12 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { nil, []string{ "fiftyone-app", + "fiftyone-app-telemetry", "teams-api", + "teams-api-telemetry", "teams-app", "teams-cas", + "telemetry-redis", }, }, { @@ -83,45 +83,6 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { []string{legacyAuthComposePluginsFile}, s.dotEnvFiles, nil, - []string{ - "fiftyone-app", - "teams-api", - "teams-app", - "teams-cas", - }, - }, - { - "composeDedicatedPlugins", - []string{legacyAuthComposeDedicatedPluginsFile}, - s.dotEnvFiles, - nil, - []string{ - "fiftyone-app", - "teams-api", - "teams-app", - "teams-cas", - "teams-plugins", - }, - }, - { - "composeDelegatedOperations", - []string{legacyAuthComposeDelegatedOperationsFile}, - s.dotEnvFiles, - nil, - []string{ - "teams-do", - }, - }, - { - "composeDelegatedOperationsGpu", - []string{ - legacyAuthComposeFile, - legacyAuthComposeDelegatedOperationsFile, - legacyAuthComposeDelegatedOperationsGpuFile, - legacyAuthComposeTelemetryFile, - }, - s.dotEnvFiles, - []string{"gpu"}, []string{ "fiftyone-app", "fiftyone-app-telemetry", @@ -129,15 +90,12 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { "teams-api-telemetry", "teams-app", "teams-cas", - "teams-do", - "teams-do-gpu", - "teams-do-gpu-telemetry", "telemetry-redis", }, }, { - "composeTelemetry", - []string{legacyAuthComposeFile, legacyAuthComposeTelemetryFile}, + "composeDedicatedPlugins", + []string{legacyAuthComposeDedicatedPluginsFile}, s.dotEnvFiles, nil, []string{ @@ -147,17 +105,14 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { "teams-api-telemetry", "teams-app", "teams-cas", + "teams-plugins", + "teams-plugins-telemetry", "telemetry-redis", }, }, { - "composeTelemetryPlugins", - []string{ - legacyAuthComposeFile, - legacyAuthComposeDedicatedPluginsFile, - legacyAuthComposeTelemetryFile, - legacyAuthComposeTelemetryPluginsFile, - }, + "composeDelegatedOperations", + []string{legacyAuthComposeFile, legacyAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, nil, []string{ @@ -167,21 +122,20 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { "teams-api-telemetry", "teams-app", "teams-cas", - "teams-plugins", - "teams-plugins-telemetry", + "teams-do", + "teams-do-telemetry", "telemetry-redis", }, }, { - "composeTelemetryDelegatedOperators", + "composeDelegatedOperationsGpu", []string{ legacyAuthComposeFile, legacyAuthComposeDelegatedOperationsFile, - legacyAuthComposeTelemetryFile, - legacyAuthComposeTelemetryDelegatedOperatorsFile, + legacyAuthComposeDelegatedOperationsGpuFile, }, s.dotEnvFiles, - nil, + []string{"gpu"}, []string{ "fiftyone-app", "fiftyone-app-telemetry", @@ -190,6 +144,8 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { "teams-app", "teams-cas", "teams-do", + "teams-do-gpu", + "teams-do-gpu-telemetry", "teams-do-telemetry", "telemetry-redis", }, @@ -275,28 +231,28 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceImage() { { "delegatedOperationsTeamsDo", "teams-do", - []string{legacyAuthComposeDelegatedOperationsFile}, + []string{legacyAuthComposeFile, legacyAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, "voxel51/fiftyone-teams-cv-full:v2.19.0", }, { "telemetryRedis", "telemetry-redis", - []string{legacyAuthComposeFile, legacyAuthComposeTelemetryFile}, + []string{legacyAuthComposeFile}, s.dotEnvFiles, "redis:7-alpine", }, { "telemetrySidecarFiftyoneApp", "fiftyone-app-telemetry", - []string{legacyAuthComposeFile, legacyAuthComposeTelemetryFile}, + []string{legacyAuthComposeFile}, s.dotEnvFiles, "voxel51/telemetry-sidecar:latest", }, { "telemetrySidecarTeamsApi", "teams-api-telemetry", - []string{legacyAuthComposeFile, legacyAuthComposeTelemetryFile}, + []string{legacyAuthComposeFile}, s.dotEnvFiles, "voxel51/telemetry-sidecar:latest", }, @@ -306,12 +262,17 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceImage() { []string{ legacyAuthComposeFile, legacyAuthComposeDelegatedOperationsFile, - legacyAuthComposeTelemetryFile, - legacyAuthComposeTelemetryDelegatedOperatorsFile, }, s.dotEnvFiles, "voxel51/telemetry-sidecar:latest", }, + { + "telemetrySidecarTeamsPlugins", + "teams-plugins-telemetry", + []string{legacyAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:latest", + }, } for _, testCase := range testCases { @@ -375,6 +336,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", "FIFTYONE_SIGNED_URL_EXPIRATION=24", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", }, }, { @@ -392,6 +354,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_ENV=production", "FIFTYONE_INTERNAL_SERVICE=true", "FIFTYONE_LOGGING_FORMAT=text", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", "GRAPHQL_DEFAULT_LIMIT=10", "LOGGING_LEVEL=INFO", "MONGO_DEFAULT_DB=fiftyone", @@ -412,6 +375,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_SERVER_ADDRESS=", "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", "NODE_ENV=production", "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false", "FIFTYONE_APP_ANONYMOUS_ANALYTICS_ENABLED=true", @@ -457,6 +421,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", "FIFTYONE_SIGNED_URL_EXPIRATION=24", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", }, }, { @@ -474,6 +439,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_ENV=production", "FIFTYONE_INTERNAL_SERVICE=true", "FIFTYONE_LOGGING_FORMAT=text", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", "GRAPHQL_DEFAULT_LIMIT=10", "LOGGING_LEVEL=INFO", "MONGO_DEFAULT_DB=fiftyone", @@ -494,6 +460,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_SERVER_ADDRESS=", "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", "NODE_ENV=production", "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false", "FIFTYONE_APP_ANONYMOUS_ANALYTICS_ENABLED=true", @@ -540,6 +507,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", "FIFTYONE_PLUGINS_DIR=/opt/plugins", "FIFTYONE_SIGNED_URL_EXPIRATION=24", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", }, }, { @@ -558,6 +526,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_INTERNAL_SERVICE=true", "FIFTYONE_LOGGING_FORMAT=text", "FIFTYONE_PLUGINS_DIR=/opt/plugins", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", "GRAPHQL_DEFAULT_LIMIT=10", "LOGGING_LEVEL=INFO", "MONGO_DEFAULT_DB=fiftyone", @@ -578,6 +547,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_SERVER_ADDRESS=", "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", "NODE_ENV=production", "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false", "FIFTYONE_APP_ANONYMOUS_ANALYTICS_ENABLED=true", @@ -623,6 +593,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", "FIFTYONE_SIGNED_URL_EXPIRATION=24", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", }, }, { @@ -641,6 +612,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_INTERNAL_SERVICE=true", "FIFTYONE_LOGGING_FORMAT=text", "FIFTYONE_PLUGINS_DIR=/opt/plugins", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", "GRAPHQL_DEFAULT_LIMIT=10", "LOGGING_LEVEL=INFO", "MONGO_DEFAULT_DB=fiftyone", @@ -661,6 +633,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_SERVER_ADDRESS=", "FIFTYONE_SERVER_PATH_PREFIX=/api/proxy/fiftyone-teams", "FIFTYONE_TEAMS_PROXY_URL=http://fiftyone-app:5151", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", "NODE_ENV=production", "RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false", "FIFTYONE_TEAMS_PLUGIN_URL=http://teams-plugins:5151", @@ -705,12 +678,13 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_MEDIA_CACHE_APP_IMAGES=false", "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", "FIFTYONE_PLUGINS_DIR=/opt/plugins", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", }, }, { "delegatedOperationsTeamsDo", "teams-do", - []string{legacyAuthComposeDelegatedOperationsFile}, + []string{legacyAuthComposeFile, legacyAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, []string{ "API_URL=http://teams-api:8000", @@ -721,6 +695,8 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceEnvironment() { "FIFTYONE_INTERNAL_SERVICE=true", "FIFTYONE_MEDIA_CACHE_SIZE_BYTES=-1", "FIFTYONE_PLUGINS_DIR=/opt/plugins", + "FIFTYONE_TELEMETRY_REDIS_URL=redis://telemetry-redis:6379", + "TELEMETRY_SOCKET=/tmp/telemetry/agent.sock", }, }, } @@ -916,7 +892,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceRestart() { { "delegatedOperationsTeamsDo", "teams-do", - []string{legacyAuthComposeDelegatedOperationsFile}, + []string{legacyAuthComposeFile, legacyAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, types.RestartPolicyAlways, }, @@ -1114,7 +1090,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceVolumes() { { "delegatedOperationsTeamsDo", "teams-do", - []string{legacyAuthComposeDelegatedOperationsFile}, + []string{legacyAuthComposeFile, legacyAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, []types.ServiceVolumeConfig{ { @@ -1124,6 +1100,13 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceVolumes() { ReadOnly: true, Volume: &types.ServiceVolumeVolume{}, }, + { + Type: "volume", + Source: "telemetry-socket", + Target: "/tmp/telemetry", + ReadOnly: false, + Volume: &types.ServiceVolumeVolume{}, + }, }, }, } @@ -1171,7 +1154,11 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestVolumes() { "default", []string{legacyAuthComposeFile}, s.dotEnvFiles, - nil, + types.Volumes{ + "telemetry-redis-data": { + Name: "fiftyone-compose-test_telemetry-redis-data", + }, + }, }, { "plugins", @@ -1181,6 +1168,9 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestVolumes() { "plugins-vol": { Name: "fiftyone-compose-test_plugins-vol", }, + "telemetry-redis-data": { + Name: "fiftyone-compose-test_telemetry-redis-data", + }, }, }, { @@ -1191,16 +1181,25 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestVolumes() { "plugins-vol": { Name: "fiftyone-compose-test_plugins-vol", }, + "telemetry-redis-data": { + Name: "fiftyone-compose-test_telemetry-redis-data", + }, }, }, { "delegatedOperations", - []string{legacyAuthComposeDelegatedOperationsFile}, + []string{legacyAuthComposeFile, legacyAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, types.Volumes{ "plugins-vol": { Name: "fiftyone-compose-test_plugins-vol", }, + "telemetry-redis-data": { + Name: "fiftyone-compose-test_telemetry-redis-data", + }, + "telemetry-socket": { + Name: "fiftyone-compose-test_telemetry-socket", + }, }, }, } diff --git a/utils/validate-docker-pulls.sh b/utils/validate-docker-pulls.sh index 2af6b79c2..c830e09e5 100755 --- a/utils/validate-docker-pulls.sh +++ b/utils/validate-docker-pulls.sh @@ -16,6 +16,7 @@ set -euo pipefail # exhaustive set. EXPECTED_IMAGES=( "docker.io/busybox:stable-glibc" # For initContainers + "docker.io/redis:7-alpine" # For telemetry redis (telemetry.enabled defaults to true) "voxel51/fiftyone-app" "voxel51/fiftyone-app-gpt" "voxel51/fiftyone-app-torch" From b290329e54e12a45a250f36b229644cae2004e16 Mon Sep 17 00:00:00 2001 From: kacey Date: Tue, 19 May 2026 10:55:33 -0700 Subject: [PATCH 17/49] chore: temp fix for tests --- tests/fixtures/docker/compose.override.yaml | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/fixtures/docker/compose.override.yaml b/tests/fixtures/docker/compose.override.yaml index 7b1bcfccc..d1b1f4cdd 100644 --- a/tests/fixtures/docker/compose.override.yaml +++ b/tests/fixtures/docker/compose.override.yaml @@ -2,3 +2,25 @@ services: fiftyone-app: environment: FIFTYONE_DATABASE_ADMIN: false + + # Integration tests don't have access to the private + # `voxel51/telemetry-sidecar` image. Replace each sidecar with a + # public idle alpine container so the stack still composes correctly + # but no private-registry pull is required. Sidecars are not exercised + # by these tests — only the main services are. + fiftyone-app-telemetry: + image: alpine:3.20 + command: ["tail", "-f", "/dev/null"] + pull_policy: missing + teams-api-telemetry: + image: alpine:3.20 + command: ["tail", "-f", "/dev/null"] + pull_policy: missing + teams-plugins-telemetry: + image: alpine:3.20 + command: ["tail", "-f", "/dev/null"] + pull_policy: missing + teams-do-telemetry: + image: alpine:3.20 + command: ["tail", "-f", "/dev/null"] + pull_policy: missing From 229c9eb23be4482ccc5c25a9a82ec18f72a83fdc Mon Sep 17 00:00:00 2001 From: kacey Date: Tue, 19 May 2026 11:57:26 -0700 Subject: [PATCH 18/49] feat: default enable telemetric with all default settings generated --- helm/fiftyone-teams-app/README.md | 7 +- .../templates/_do_targets.tpl | 6 + .../fiftyone-teams-app/templates/_helpers.tpl | 10 ++ .../templates/_telemetry.tpl | 154 ++++++++++++++++++ .../templates/api-deployment.yaml | 5 +- .../templates/app-deployment.yaml | 5 +- ...elegated-operator-instance-deployment.yaml | 30 +++- .../delegated-operator-job-configmap.yaml | 17 +- .../templates/plugins-deployment.yaml | 5 +- .../templates/telemetry-rolebinding.yaml | 6 +- helm/fiftyone-teams-app/values.schema.json | 67 +++++++- helm/fiftyone-teams-app/values.yaml | 41 +++-- tests/unit/helm/api-deployment_test.go | 73 +++++---- tests/unit/helm/app-deployment_test.go | 66 ++++---- ...gated-operator-instance-deployment_test.go | 106 ++++++------ .../delegated-operator-job-configmap_test.go | 14 +- tests/unit/helm/plugins-deployment_test.go | 108 ++++++------ tests/unit/helm/teams-app-deployment_test.go | 66 ++++---- tests/unit/helm/telemetry-rolebinding_test.go | 23 ++- tests/unit/helm/yaml_helpers.go | 14 ++ 20 files changed, 572 insertions(+), 251 deletions(-) diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index c7162d9b5..e42d57603 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -1095,13 +1095,16 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | teamsAppSettings.updateStrategy | object | `{"type":"RollingUpdate"}` | Control how `teams-app` pods are redeployed during an upgrade. [Reference][upgrade-strategies] | | teamsAppSettings.volumeMounts | list | `[]` | Volume mounts for `teams-app` pods. [Reference][volumes]. | | teamsAppSettings.volumes | list | `[]` | Volumes for `teams-app` pods. [Reference][volumes]. | -| telemetry.enabled | bool | `true` | Whether to render the telemetry Redis Deployment/Service and the telemetry pod-logs Role/RoleBinding. Defaults to `true` so consumers that wire up `telemetry-sidecar` containers get a working redis without additional opt-in. Set to `false` to skip rendering all telemetry-related resources. | +| telemetry.enabled | bool | `true` | Master switch. When `true` (default), all telemetry resources and auto-injected sidecars are rendered. When `false`, none are. | | telemetry.redis.image | string | `"redis:7-alpine"` | Container image for the telemetry Redis Deployment. | | telemetry.redis.maxmemory | string | `"400mb"` | `--maxmemory` flag passed to `redis-server`. | | telemetry.redis.maxmemoryPolicy | string | `"allkeys-lru"` | `--maxmemory-policy` flag passed to `redis-server`. | | telemetry.redis.resources | object | `{"limits":{"cpu":"250m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"256Mi"}}` | Resource requests/limits for the telemetry Redis container. [Reference][resources]. | | telemetry.redis.storage | string | `"1Gi"` | Persistent volume size for the telemetry Redis PVC. | -| telemetry.serviceAccounts | list | `[]` | ServiceAccount names (in `namespace.name`) bound to the telemetry pod-logs Role. Each entry becomes a `ServiceAccount` subject on the generated RoleBinding. When empty, the RoleBinding subjects default to the `default` ServiceAccount. | +| telemetry.serviceAccounts | list | `[]` | ServiceAccount names (in `namespace.name`) bound to the telemetry pod-logs Role. Each entry becomes a `ServiceAccount` subject on the generated RoleBinding. When empty, the RoleBinding subjects default to the chart's main app service account and the teams-api RBAC service account so auto-injected sidecars can read their target's logs. | +| telemetry.sidecar.image | string | `"voxel51/telemetry-sidecar:latest"` | Container image for the auto-injected `telemetry-sidecar` containers. | +| telemetry.sidecar.imagePullPolicy | string | `"Always"` | Pull policy for the sidecar image. | +| telemetry.sidecar.resources | object | `{"limits":{"cpu":"100m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"512Mi"}}` | Resource requests/limits for each auto-injected sidecar container. | diff --git a/helm/fiftyone-teams-app/templates/_do_targets.tpl b/helm/fiftyone-teams-app/templates/_do_targets.tpl index 7806b82ca..3a90d039b 100644 --- a/helm/fiftyone-teams-app/templates/_do_targets.tpl +++ b/helm/fiftyone-teams-app/templates/_do_targets.tpl @@ -50,6 +50,12 @@ Create a merged list of environment variables for delegated-operator templates secretKeyRef: name: {{ .secretName }} key: encryptionKey +{{- if and .ctx .ctx.Values.telemetry.enabled }} +- name: FIFTYONE_TELEMETRY_REDIS_URL + value: {{ printf "redis://%s:6379" (include "telemetry.redis.name" .ctx) | quote }} +- name: TELEMETRY_SOCKET + value: /tmp/telemetry/agent.sock +{{- end }} {{- range $key, $val := .env }} - name: {{ $key }} value: {{ $val | quote }} diff --git a/helm/fiftyone-teams-app/templates/_helpers.tpl b/helm/fiftyone-teams-app/templates/_helpers.tpl index bf4a311cb..8b5e1ac9f 100644 --- a/helm/fiftyone-teams-app/templates/_helpers.tpl +++ b/helm/fiftyone-teams-app/templates/_helpers.tpl @@ -324,6 +324,12 @@ Create a merged list of environment variables for delegated-operator-executor secretKeyRef: name: {{ .secretName }} key: encryptionKey +{{- if and .ctx .ctx.Values.telemetry.enabled }} +- name: FIFTYONE_TELEMETRY_REDIS_URL + value: {{ printf "redis://%s:6379" (include "telemetry.redis.name" .ctx) | quote }} +- name: TELEMETRY_SOCKET + value: /tmp/telemetry/agent.sock +{{- end }} {{- range $key, $val := .env }} - name: {{ $key }} value: {{ $val | quote }} @@ -382,6 +388,7 @@ Create a merged list of environment variables for fiftyone-teams-api secretKeyRef: name: {{ $secretName }} key: fiftyoneDatabaseName +{{- include "telemetry.redis-url-env" . }} {{- range $key, $val := .Values.apiSettings.env }} - name: {{ $key }} value: {{ $val | quote }} @@ -427,6 +434,7 @@ Create a merged list of environment variables for fiftyone-app secretKeyRef: name: {{ $secretName }} key: encryptionKey +{{- include "telemetry.redis-url-env" . }} {{- range $key, $val := .Values.appSettings.env }} - name: {{ $key }} value: {{ $val | quote }} @@ -543,6 +551,7 @@ Create a merged list of environment variables for fiftyone-teams-plugins secretKeyRef: name: {{ $secretName }} key: encryptionKey +{{- include "telemetry.redis-url-env" . }} {{- range $key, $val := .Values.pluginsSettings.env }} - name: {{ $key }} value: {{ $val | quote }} @@ -594,6 +603,7 @@ Create a merged list of environment variables for fiftyone-teams-app {{- else }} value: {{ printf "http://%s:%.0f" .Values.appSettings.service.name (float64 .Values.appSettings.service.port) | quote }} {{- end }} +{{- include "telemetry.redis-url-env" . }} {{- range $key, $val := .Values.teamsAppSettings.env }} - name: {{ $key }} value: {{ $val | quote }} diff --git a/helm/fiftyone-teams-app/templates/_telemetry.tpl b/helm/fiftyone-teams-app/templates/_telemetry.tpl index 6ebe5248e..632f1eb0b 100644 --- a/helm/fiftyone-teams-app/templates/_telemetry.tpl +++ b/helm/fiftyone-teams-app/templates/_telemetry.tpl @@ -38,3 +38,157 @@ app.kubernetes.io/name: telemetry app.kubernetes.io/instance: {{ .Release.Name }} app.voxel51.com/component: telemetry {{- end }} + +{{/* +Default subjects for the telemetry RoleBinding. When .Values.telemetry.serviceAccounts +is empty, bind to both the main app service account and the teams-api RBAC service +account (covering the SAs used by the auto-injected sidecars). +*/}} +{{- define "telemetry.role.subjects" -}} +{{- $appSA := include "fiftyone-teams-app.serviceAccountName" . | trim -}} +{{- $apiSA := include "teams-api-rbac.service-account-name" . | trim -}} +{{- $defaultSubjects := list $appSA $apiSA | uniq -}} +{{- $subjects := .Values.telemetry.serviceAccounts | default $defaultSubjects -}} +{{- range $subjects }} +- kind: ServiceAccount + name: {{ . }} + namespace: {{ $.Values.namespace.name }} +{{- end }} +{{- end }} + +{{/* +The shared base env vars for any telemetry-sidecar container. +Inputs (dict): + ctx — root context (.) + serviceType — value for SERVICE_TYPE env var (e.g. "teams-api") + targetName — value for TARGET_NAME env var (e.g. "fiftyone-teams-api") + podName — value for POD_NAME env var (defaults to fieldRef metadata.name) + executor — bool, when true emit EXECUTOR_SIDECAR=true and TELEMETRY_SOCKET env + targetContainer — when set, emit TARGET_CONTAINER env var (used by job sidecars) +*/}} +{{- define "telemetry.sidecar-env" -}} +{{- $secretName := .ctx.Values.secret.name -}} +- name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name +- name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace +- name: SERVICE_TYPE + value: {{ .serviceType | quote }} +- name: TARGET_NAME + value: {{ .targetName | quote }} +{{- if .targetContainer }} +- name: TARGET_CONTAINER + value: {{ .targetContainer | quote }} +{{- end }} +{{- if .executor }} +- name: EXECUTOR_SIDECAR + value: "true" +- name: TELEMETRY_SOCKET + value: /tmp/telemetry/agent.sock +{{- end }} +- name: FIFTYONE_TELEMETRY_REDIS_URL + value: {{ printf "redis://%s:6379" (include "telemetry.redis.name" .ctx) | quote }} +- name: FIFTYONE_DATABASE_URI + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: mongodbConnectionString +- name: FIFTYONE_DATABASE_NAME + valueFrom: + secretKeyRef: + name: {{ $secretName }} + key: fiftyoneDatabaseName +{{- end }} + +{{/* +A regular spec.containers entry: telemetry-sidecar for api/app/plugins/DO deployments. +Inputs: same dict as telemetry.sidecar-env. +*/}} +{{- define "telemetry.sidecar" -}} +- name: telemetry-sidecar + image: {{ .ctx.Values.telemetry.sidecar.image | default "voxel51/telemetry-sidecar:latest" | quote }} + imagePullPolicy: {{ .ctx.Values.telemetry.sidecar.imagePullPolicy | default "Always" }} + env: + {{- include "telemetry.sidecar-env" . | nindent 4 }} + {{- with .ctx.Values.telemetry.sidecar.resources }} + resources: + {{- toYaml . | nindent 4 }} + {{- end }} + securityContext: + capabilities: + add: + - SYS_PTRACE + {{- if .executor }} + volumeMounts: + - name: telemetry-socket + mountPath: /tmp/telemetry + {{- end }} +{{- end }} + +{{/* +A native-sidecar (initContainer with restartPolicy: Always) variant — for use under +spec.initContainers, primarily in DO Jobs where a regular extraContainer that does +not exit would block Job completion. +*/}} +{{- define "telemetry.native-sidecar" -}} +- name: telemetry-sidecar + image: {{ .ctx.Values.telemetry.sidecar.image | default "voxel51/telemetry-sidecar:latest" | quote }} + imagePullPolicy: {{ .ctx.Values.telemetry.sidecar.imagePullPolicy | default "Always" }} + restartPolicy: Always + env: + {{- include "telemetry.sidecar-env" . | nindent 4 }} + {{- with .ctx.Values.telemetry.sidecar.resources }} + resources: + {{- toYaml . | nindent 4 }} + {{- end }} + readinessProbe: + exec: + command: + - sh + - -c + - test -S /tmp/telemetry/agent.sock + failureThreshold: 30 + periodSeconds: 1 + securityContext: + capabilities: + add: + - SYS_PTRACE + volumeMounts: + - name: telemetry-socket + mountPath: /tmp/telemetry +{{- end }} + +{{/* +Emit a `FIFTYONE_TELEMETRY_REDIS_URL` env entry for a main workload container. +Renders empty when telemetry is disabled, allowing safe inclusion from +env-vars-list helpers. +*/}} +{{- define "telemetry.redis-url-env" -}} +{{- if .Values.telemetry.enabled }} +- name: FIFTYONE_TELEMETRY_REDIS_URL + value: {{ printf "redis://%s:6379" (include "telemetry.redis.name" .) | quote }} +{{- end }} +{{- end }} + +{{/* +Emit the `telemetry-socket` shared emptyDir volume entry for spec.volumes. +Used by DO deployments and DO jobs so the sidecar's unix socket can be +reached by both the executor and the sidecar. +*/}} +{{- define "telemetry.socket-volume" -}} +- name: telemetry-socket + emptyDir: {} +{{- end }} + +{{/* +Emit the `telemetry-socket` volumeMount for a workload container that needs to +reach the sidecar's unix socket. Pair with telemetry.socket-volume. +*/}} +{{- define "telemetry.socket-volume-mount" -}} +- name: telemetry-socket + mountPath: /tmp/telemetry +{{- end }} diff --git a/helm/fiftyone-teams-app/templates/api-deployment.yaml b/helm/fiftyone-teams-app/templates/api-deployment.yaml index 002f6f660..37635c293 100644 --- a/helm/fiftyone-teams-app/templates/api-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/api-deployment.yaml @@ -44,7 +44,7 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ $serviceAccountName }} - {{- if or .Values.apiSettings.extraContainers .Values.apiSettings.nativeSidecarContainers }} + {{- if or .Values.apiSettings.extraContainers .Values.apiSettings.nativeSidecarContainers .Values.telemetry.enabled }} shareProcessNamespace: true {{- end }} securityContext: @@ -95,6 +95,9 @@ spec: {{- with .Values.apiSettings.extraContainers }} {{- toYaml . | nindent 8 }} {{- end }} + {{- if .Values.telemetry.enabled }} + {{- include "telemetry.sidecar" (dict "ctx" . "serviceType" "teams-api" "targetName" "fiftyone-teams-api") | nindent 8 }} + {{- end }} {{- if or .Values.apiSettings.initContainers.enabled .Values.apiSettings.nativeSidecarContainers }} initContainers: {{- if .Values.apiSettings.initContainers.enabled }} diff --git a/helm/fiftyone-teams-app/templates/app-deployment.yaml b/helm/fiftyone-teams-app/templates/app-deployment.yaml index 91d3f9d0c..b071e0a13 100644 --- a/helm/fiftyone-teams-app/templates/app-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/app-deployment.yaml @@ -35,7 +35,7 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" . }} - {{- if or .Values.appSettings.extraContainers .Values.appSettings.nativeSidecarContainers }} + {{- if or .Values.appSettings.extraContainers .Values.appSettings.nativeSidecarContainers .Values.telemetry.enabled }} shareProcessNamespace: true {{- end }} securityContext: @@ -79,6 +79,9 @@ spec: {{- with .Values.appSettings.extraContainers }} {{- toYaml . | nindent 8 }} {{- end }} + {{- if .Values.telemetry.enabled }} + {{- include "telemetry.sidecar" (dict "ctx" . "serviceType" "fiftyone-app" "targetName" "hypercorn") | nindent 8 }} + {{- end }} {{- if or .Values.appSettings.initContainers.enabled .Values.appSettings.nativeSidecarContainers }} initContainers: {{- if .Values.appSettings.initContainers.enabled }} diff --git a/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml b/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml index ab196e367..a628858d4 100644 --- a/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml @@ -11,6 +11,7 @@ "apiServicePort" (float64 $.Values.apiSettings.service.port) "env" (merge (dict) ($v.env | default dict) ($baseTpl.env)) "secretEnv" (merge (dict) ($v.secretEnv | default dict) ($baseTpl.secretEnv)) + "ctx" $ ) }} {{- $defaultDescription := printf "Long running operations delegated to %s" $name }} @@ -50,7 +51,7 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" $ }} - {{- if or ($v.extraContainers | default $baseTpl.extraContainers) ($v.nativeSidecarContainers | default $baseTpl.nativeSidecarContainers) }} + {{- if or ($v.extraContainers | default $baseTpl.extraContainers) ($v.nativeSidecarContainers | default $baseTpl.nativeSidecarContainers) $.Values.telemetry.enabled }} shareProcessNamespace: true {{- end }} securityContext: @@ -78,7 +79,17 @@ spec: {{- include "delegated-operator-deployments.env-vars-list" $envContext | indent 12 }} resources: {{- toYaml (merge (dict) ($v.resources | default dict) ($baseTpl.resources)) | nindent 12 }} - {{- with $v.volumeMounts | default $baseTpl.volumeMounts }} + {{- $mergedVolumeMounts := $v.volumeMounts | default $baseTpl.volumeMounts | default list }} + {{- if $.Values.telemetry.enabled }} + {{- $hasSocketMount := false }} + {{- range $vm := $mergedVolumeMounts }} + {{- if eq $vm.name "telemetry-socket" }}{{- $hasSocketMount = true }}{{- end }} + {{- end }} + {{- if not $hasSocketMount }} + {{- $mergedVolumeMounts = append $mergedVolumeMounts (dict "name" "telemetry-socket" "mountPath" "/tmp/telemetry") }} + {{- end }} + {{- end }} + {{- with $mergedVolumeMounts }} volumeMounts: {{- toYaml . | nindent 12 }} {{- end }} @@ -112,6 +123,9 @@ spec: {{- with $v.extraContainers | default $baseTpl.extraContainers }} {{- toYaml . | nindent 8 }} {{- end }} + {{- if $.Values.telemetry.enabled }} + {{- include "telemetry.sidecar" (dict "ctx" $ "serviceType" "delegated-operator" "targetName" "fiftyone delegated" "executor" true) | nindent 8 }} + {{- end }} {{- with $v.nativeSidecarContainers | default $baseTpl.nativeSidecarContainers }} initContainers: {{- toYaml . | nindent 8 }} @@ -139,7 +153,17 @@ spec: ) | nindent 8 }} {{- end }} - {{- with $v.volumes | default $baseTpl.volumes }} + {{- $mergedVolumes := $v.volumes | default $baseTpl.volumes | default list }} + {{- if $.Values.telemetry.enabled }} + {{- $hasSocketVol := false }} + {{- range $vol := $mergedVolumes }} + {{- if eq $vol.name "telemetry-socket" }}{{- $hasSocketVol = true }}{{- end }} + {{- end }} + {{- if not $hasSocketVol }} + {{- $mergedVolumes = append $mergedVolumes (dict "name" "telemetry-socket" "emptyDir" dict) }} + {{- end }} + {{- end }} + {{- with $mergedVolumes }} volumes: {{- toYaml . | nindent 8 }} {{- end }} diff --git a/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml b/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml index b611a83fd..adf148452 100644 --- a/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml +++ b/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml @@ -24,6 +24,7 @@ data: "apiServicePort" (float64 $.Values.apiSettings.service.port) "env" (merge (deepCopy ($jobConfig.env | default dict)) ($baseTpl.env)) "secretEnv" (merge (deepCopy ($jobConfig.secretEnv | default dict)) ($baseTpl.secretEnv)) + "ctx" $ }} {{- /* label context for helpers. */ -}} @@ -43,7 +44,21 @@ data: {{- $mergedVolumeMounts := $jobConfig.volumeMounts | default $baseTpl.volumeMounts }} {{- $mergedVolumes := $jobConfig.volumes | default $baseTpl.volumes }} {{- $mergedExtraContainers := $jobConfig.extraContainers | default $baseTpl.extraContainers }} - {{- $mergedNativeSidecarContainers := $jobConfig.nativeSidecarContainers | default $baseTpl.nativeSidecarContainers }} + {{- $mergedNativeSidecarContainers := $jobConfig.nativeSidecarContainers | default $baseTpl.nativeSidecarContainers | default list }} + + {{- /* Auto-inject the telemetry native-sidecar when telemetry is enabled + and the consumer hasn't supplied one of their own. */ -}} + {{- if $.Values.telemetry.enabled }} + {{- $hasTelemetrySidecar := false }} + {{- range $sc := $mergedNativeSidecarContainers }} + {{- if eq $sc.name "telemetry-sidecar" }}{{- $hasTelemetrySidecar = true }}{{- end }} + {{- end }} + {{- if not $hasTelemetrySidecar }} + {{- $telemetrySidecarYaml := include "telemetry.native-sidecar" (dict "ctx" $ "serviceType" "delegated-operator" "targetName" "fiftyone delegated" "targetContainer" "executor" "executor" true) }} + {{- $telemetrySidecar := $telemetrySidecarYaml | fromYamlArray | first }} + {{- $mergedNativeSidecarContainers = append $mergedNativeSidecarContainers $telemetrySidecar }} + {{- end }} + {{- end }} {{- $hasExecutorSidecar := false }} {{- range $sidecar := $mergedNativeSidecarContainers }} diff --git a/helm/fiftyone-teams-app/templates/plugins-deployment.yaml b/helm/fiftyone-teams-app/templates/plugins-deployment.yaml index 5524ae2ad..baa9762e9 100644 --- a/helm/fiftyone-teams-app/templates/plugins-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/plugins-deployment.yaml @@ -36,7 +36,7 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" . }} - {{- if or .Values.pluginsSettings.extraContainers .Values.pluginsSettings.nativeSidecarContainers }} + {{- if or .Values.pluginsSettings.extraContainers .Values.pluginsSettings.nativeSidecarContainers .Values.telemetry.enabled }} shareProcessNamespace: true {{- end }} securityContext: @@ -80,6 +80,9 @@ spec: {{- with .Values.pluginsSettings.extraContainers }} {{- toYaml . | nindent 8 }} {{- end }} + {{- if .Values.telemetry.enabled }} + {{- include "telemetry.sidecar" (dict "ctx" . "serviceType" "teams-plugins" "targetName" "hypercorn") | nindent 8 }} + {{- end }} {{- if or .Values.pluginsSettings.initContainers.enabled .Values.pluginsSettings.nativeSidecarContainers }} initContainers: {{- if .Values.pluginsSettings.initContainers.enabled }} diff --git a/helm/fiftyone-teams-app/templates/telemetry-rolebinding.yaml b/helm/fiftyone-teams-app/templates/telemetry-rolebinding.yaml index e148ae7db..8075f0d42 100644 --- a/helm/fiftyone-teams-app/templates/telemetry-rolebinding.yaml +++ b/helm/fiftyone-teams-app/templates/telemetry-rolebinding.yaml @@ -26,9 +26,5 @@ roleRef: kind: Role name: {{ include "telemetry.role.name" . }} subjects: -{{- range .Values.telemetry.serviceAccounts | default (list "default") }} - - kind: ServiceAccount - name: {{ . }} - namespace: {{ $.Values.namespace.name }} -{{- end }} + {{- include "telemetry.role.subjects" . | nindent 2 }} {{- end }} diff --git a/helm/fiftyone-teams-app/values.schema.json b/helm/fiftyone-teams-app/values.schema.json index c4e63a40e..b95f5c3f0 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -4010,11 +4010,11 @@ "type": "object" }, "telemetry": { - "description": "In-cluster telemetry support. When enabled, the chart renders a small\nRedis Deployment + Service (used by `telemetry-sidecar` containers\nconfigured on workload pods via `extraContainers`) plus a Role/RoleBinding\ngranting the sidecar's service accounts read access to `pods/log`.\nThe chart itself does NOT inject `telemetry-sidecar` containers; consumers\nwire those into `extraContainers` on the relevant workloads and point the\n`FIFTYONE_TELEMETRY_REDIS_URL` env var at this service.", + "description": "In-cluster telemetry. When enabled (the default), the chart renders:\n - a Redis Deployment + Service that collects per-service metric streams\n - a Role + RoleBinding granting the sidecar read access to `pods/log`\n - a `telemetry-sidecar` container auto-injected into the\n `teams-api`, `fiftyone-app`, `teams-plugins`, and delegated-operator\n workloads, plus a `nativeSidecarContainers` entry on the\n delegated-operator Job template\n - `FIFTYONE_TELEMETRY_REDIS_URL` on every workload that talks to the\n redis backend (api, app, plugins, teams-app, delegated operators)\n\nSet `enabled: false` to skip all telemetry resources and sidecar injection.", "properties": { "enabled": { "default": true, - "description": "Whether to render the telemetry Redis Deployment/Service and the\ntelemetry pod-logs Role/RoleBinding. Defaults to `true` so consumers\nthat wire up `telemetry-sidecar` containers get a working redis without\nadditional opt-in. Set to `false` to skip rendering all telemetry-related\nresources.", + "description": "Master switch. When `true` (default), all telemetry resources and\nauto-injected sidecars are rendered. When `false`, none are.", "title": "enabled", "type": "boolean" }, @@ -4092,12 +4092,73 @@ "type": "object" }, "serviceAccounts": { - "description": "ServiceAccount names (in `namespace.name`) bound to the telemetry\npod-logs Role. Each entry becomes a `ServiceAccount` subject on the\ngenerated RoleBinding. When empty, the RoleBinding subjects default to\nthe `default` ServiceAccount.", + "description": "ServiceAccount names (in `namespace.name`) bound to the telemetry\npod-logs Role. Each entry becomes a `ServiceAccount` subject on the\ngenerated RoleBinding. When empty, the RoleBinding subjects default to\nthe chart's main app service account and the teams-api RBAC service\naccount so auto-injected sidecars can read their target's logs.", "items": { "required": [] }, "title": "serviceAccounts", "type": "array" + }, + "sidecar": { + "properties": { + "image": { + "default": "voxel51/telemetry-sidecar:latest", + "description": "Container image for the auto-injected `telemetry-sidecar` containers.", + "title": "image", + "type": "string" + }, + "imagePullPolicy": { + "default": "Always", + "description": "Pull policy for the sidecar image.", + "title": "imagePullPolicy", + "type": "string" + }, + "resources": { + "description": "Resource requests/limits for each auto-injected sidecar container.", + "properties": { + "limits": { + "properties": { + "cpu": { + "default": "100m", + "title": "cpu", + "type": "string" + }, + "memory": { + "default": "512Mi", + "title": "memory", + "type": "string" + } + }, + "required": [], + "title": "limits", + "type": "object" + }, + "requests": { + "properties": { + "cpu": { + "default": "100m", + "title": "cpu", + "type": "string" + }, + "memory": { + "default": "512Mi", + "title": "memory", + "type": "string" + } + }, + "required": [], + "title": "requests", + "type": "object" + } + }, + "required": [], + "title": "resources", + "type": "object" + } + }, + "required": [], + "title": "sidecar", + "type": "object" } }, "required": [], diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index 89780b6e1..6ea8e906c 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -1643,19 +1643,20 @@ teamsAppSettings: # -- Volumes for `teams-app` pods. [Reference][volumes]. volumes: [] -# In-cluster telemetry support. When enabled, the chart renders a small -# Redis Deployment + Service (used by `telemetry-sidecar` containers -# configured on workload pods via `extraContainers`) plus a Role/RoleBinding -# granting the sidecar's service accounts read access to `pods/log`. -# The chart itself does NOT inject `telemetry-sidecar` containers; consumers -# wire those into `extraContainers` on the relevant workloads and point the -# `FIFTYONE_TELEMETRY_REDIS_URL` env var at this service. +# In-cluster telemetry. When enabled (the default), the chart renders: +# - a Redis Deployment + Service that collects per-service metric streams +# - a Role + RoleBinding granting the sidecar read access to `pods/log` +# - a `telemetry-sidecar` container auto-injected into the +# `teams-api`, `fiftyone-app`, `teams-plugins`, and delegated-operator +# workloads, plus a `nativeSidecarContainers` entry on the +# delegated-operator Job template +# - `FIFTYONE_TELEMETRY_REDIS_URL` on every workload that talks to the +# redis backend (api, app, plugins, teams-app, delegated operators) +# +# Set `enabled: false` to skip all telemetry resources and sidecar injection. telemetry: - # -- Whether to render the telemetry Redis Deployment/Service and the - # telemetry pod-logs Role/RoleBinding. Defaults to `true` so consumers - # that wire up `telemetry-sidecar` containers get a working redis without - # additional opt-in. Set to `false` to skip rendering all telemetry-related - # resources. + # -- Master switch. When `true` (default), all telemetry resources and + # auto-injected sidecars are rendered. When `false`, none are. enabled: true redis: # -- Container image for the telemetry Redis Deployment. @@ -1674,8 +1675,22 @@ telemetry: limits: cpu: 250m memory: 512Mi + sidecar: + # -- Container image for the auto-injected `telemetry-sidecar` containers. + image: voxel51/telemetry-sidecar:latest + # -- Pull policy for the sidecar image. + imagePullPolicy: Always + # -- Resource requests/limits for each auto-injected sidecar container. + resources: + requests: + cpu: 100m + memory: 512Mi + limits: + cpu: 100m + memory: 512Mi # -- ServiceAccount names (in `namespace.name`) bound to the telemetry # pod-logs Role. Each entry becomes a `ServiceAccount` subject on the # generated RoleBinding. When empty, the RoleBinding subjects default to - # the `default` ServiceAccount. + # the chart's main app service account and the teams-api RBAC service + # account so auto-injected sidecars can read their target's logs. serviceAccounts: [] diff --git a/tests/unit/helm/api-deployment_test.go b/tests/unit/helm/api-deployment_test.go index 30c37c935..bf7b66861 100644 --- a/tests/unit/helm/api-deployment_test.go +++ b/tests/unit/helm/api-deployment_test.go @@ -96,7 +96,7 @@ func (s *deploymentApiTemplateTest) TestMetadataLabels() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -137,7 +137,7 @@ func (s *deploymentApiTemplateTest) TestMetadataName() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -175,7 +175,7 @@ func (s *deploymentApiTemplateTest) TestMetadataNamespace() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -221,7 +221,7 @@ func (s *deploymentApiTemplateTest) TestReplicas() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -380,7 +380,7 @@ func (s *deploymentApiTemplateTest) TestTopologySpreadConstraints() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -399,9 +399,14 @@ func (s *deploymentApiTemplateTest) TestContainerCount() { }{ { "defaultValues", - nil, + map[string]string{"telemetry.enabled": "false"}, 1, }, + { + "telemetryEnabled", + map[string]string{"telemetry.enabled": "true"}, + 2, // main + telemetry-sidecar + }, } for _, testCase := range testCases { @@ -411,7 +416,7 @@ func (s *deploymentApiTemplateTest) TestContainerCount() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1164,7 +1169,7 @@ func (s *deploymentApiTemplateTest) TestContainerEnv() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1226,7 +1231,7 @@ func (s *deploymentApiTemplateTest) TestContainerImage() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1264,7 +1269,7 @@ func (s *deploymentApiTemplateTest) TestContainerImagePullPolicy() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1303,7 +1308,7 @@ func (s *deploymentApiTemplateTest) TestContainerName() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1392,7 +1397,7 @@ func (s *deploymentApiTemplateTest) TestContainerLivenessProbe() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1455,7 +1460,7 @@ func (s *deploymentApiTemplateTest) TestContainerPorts() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1544,7 +1549,7 @@ func (s *deploymentApiTemplateTest) TestContainerReadinessProbe() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1633,7 +1638,7 @@ func (s *deploymentApiTemplateTest) TestContainerStartupProbe() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1691,7 +1696,7 @@ func (s *deploymentApiTemplateTest) TestContainerResourceRequirements() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1745,7 +1750,7 @@ func (s *deploymentApiTemplateTest) TestContainerSecurityContext() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1865,7 +1870,7 @@ func (s *deploymentApiTemplateTest) TestContainerVolumeMounts() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1903,7 +1908,7 @@ func (s *deploymentApiTemplateTest) TestInitContainerCount() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1942,7 +1947,7 @@ func (s *deploymentApiTemplateTest) TestInitContainerImage() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1994,7 +1999,7 @@ func (s *deploymentApiTemplateTest) TestInitContainerCommand() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2067,7 +2072,7 @@ func (s *deploymentApiTemplateTest) TestInitContainerResourceRequirements() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2128,7 +2133,7 @@ func (s *deploymentApiTemplateTest) TestInitContainerSecurityContext() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2195,7 +2200,7 @@ func (s *deploymentApiTemplateTest) TestAffinity() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2233,7 +2238,7 @@ func (s *deploymentApiTemplateTest) TestImagePullSecrets() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2277,7 +2282,7 @@ func (s *deploymentApiTemplateTest) TestNodeSelector() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2320,7 +2325,7 @@ func (s *deploymentApiTemplateTest) TestDeploymentAnnotations() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2367,7 +2372,7 @@ func (s *deploymentApiTemplateTest) TestPodAnnotations() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2429,7 +2434,7 @@ func (s *deploymentApiTemplateTest) TestPodSecurityContext() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2504,7 +2509,7 @@ func (s *deploymentApiTemplateTest) TestTemplateLabels() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2575,7 +2580,7 @@ func (s *deploymentApiTemplateTest) TestServiceAccountName() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2631,7 +2636,7 @@ func (s *deploymentApiTemplateTest) TestTolerations() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2787,7 +2792,7 @@ func (s *deploymentApiTemplateTest) TestVolumes() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2860,7 +2865,7 @@ func (s *deploymentApiTemplateTest) TestDeploymentUpdateStrategy() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment diff --git a/tests/unit/helm/app-deployment_test.go b/tests/unit/helm/app-deployment_test.go index bfb1b9298..3f3cfba20 100644 --- a/tests/unit/helm/app-deployment_test.go +++ b/tests/unit/helm/app-deployment_test.go @@ -94,7 +94,7 @@ func (s *deploymentAppTemplateTest) TestMetadataLabels() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -135,7 +135,7 @@ func (s *deploymentAppTemplateTest) TestMetadataName() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -173,7 +173,7 @@ func (s *deploymentAppTemplateTest) TestMetadataNamespace() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -211,7 +211,7 @@ func (s *deploymentAppTemplateTest) TestReplicas() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -370,7 +370,7 @@ func (s *deploymentAppTemplateTest) TestTopologySpreadConstraints() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -401,7 +401,7 @@ func (s *deploymentAppTemplateTest) TestContainerCount() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -912,7 +912,7 @@ func (s *deploymentAppTemplateTest) TestContainerEnv() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -974,7 +974,7 @@ func (s *deploymentAppTemplateTest) TestContainerImage() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1012,7 +1012,7 @@ func (s *deploymentAppTemplateTest) TestContainerImagePullPolicy() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1051,7 +1051,7 @@ func (s *deploymentAppTemplateTest) TestContainerName() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1137,7 +1137,7 @@ func (s *deploymentAppTemplateTest) TestContainerLivenessProbe() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1200,7 +1200,7 @@ func (s *deploymentAppTemplateTest) TestContainerPorts() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1286,7 +1286,7 @@ func (s *deploymentAppTemplateTest) TestContainerReadinessProbe() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1372,7 +1372,7 @@ func (s *deploymentAppTemplateTest) TestContainerStartupProbe() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1430,7 +1430,7 @@ func (s *deploymentAppTemplateTest) TestContainerResourceRequirements() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1484,7 +1484,7 @@ func (s *deploymentAppTemplateTest) TestContainerSecurityContext() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1561,7 +1561,7 @@ func (s *deploymentAppTemplateTest) TestContainerVolumeMounts() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1599,7 +1599,7 @@ func (s *deploymentAppTemplateTest) TestInitContainerCount() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1638,7 +1638,7 @@ func (s *deploymentAppTemplateTest) TestInitContainerImage() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1690,7 +1690,7 @@ func (s *deploymentAppTemplateTest) TestInitContainerCommand() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1763,7 +1763,7 @@ func (s *deploymentAppTemplateTest) TestInitContainerResourceRequirements() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1824,7 +1824,7 @@ func (s *deploymentAppTemplateTest) TestInitContainerSecurityContext() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1891,7 +1891,7 @@ func (s *deploymentAppTemplateTest) TestAffinity() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1929,7 +1929,7 @@ func (s *deploymentAppTemplateTest) TestImagePullSecrets() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1973,7 +1973,7 @@ func (s *deploymentAppTemplateTest) TestNodeSelector() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2016,7 +2016,7 @@ func (s *deploymentAppTemplateTest) TestDeploymentAnnotations() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2063,7 +2063,7 @@ func (s *deploymentAppTemplateTest) TestPodAnnotations() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2125,7 +2125,7 @@ func (s *deploymentAppTemplateTest) TestPodSecurityContext() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2194,7 +2194,7 @@ func (s *deploymentAppTemplateTest) TestTemplateLabels() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2242,7 +2242,7 @@ func (s *deploymentAppTemplateTest) TestServiceAccountName() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2298,7 +2298,7 @@ func (s *deploymentAppTemplateTest) TestTolerations() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2381,7 +2381,7 @@ func (s *deploymentAppTemplateTest) TestVolumes() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2454,7 +2454,7 @@ func (s *deploymentAppTemplateTest) TestDeploymentUpdateStrategy() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment diff --git a/tests/unit/helm/delegated-operator-instance-deployment_test.go b/tests/unit/helm/delegated-operator-instance-deployment_test.go index ba744ea5e..118a4dbdf 100644 --- a/tests/unit/helm/delegated-operator-instance-deployment_test.go +++ b/tests/unit/helm/delegated-operator-instance-deployment_test.go @@ -82,7 +82,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestDisabled() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} if testCase.expected == nil { output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) @@ -200,7 +200,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestMetadataLabels() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} if testCase.expected == nil { output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) @@ -276,7 +276,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestMetadataName() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} if testCase.expected == nil { output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -358,7 +358,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestMetadataNamespace( subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} if testCase.expected == nil { output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) @@ -370,7 +370,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestMetadataNamespace( s.Empty(deployment.ObjectMeta.Namespace, "Metadata namespace should be nil") } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) @@ -455,7 +455,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestReplicas() { subT.Parallel() if testCase.expected == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -465,7 +465,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestReplicas() { s.Empty(&deployment.Spec.Replicas, "Replica count should be nil.") } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -782,7 +782,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestTopologySpreadCons subT.Parallel() if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -792,7 +792,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestTopologySpreadCons s.Empty(deployment.Spec.Template.Spec.TopologySpreadConstraints, "Topology constraints should be nil") } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) @@ -845,7 +845,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerCount() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} if testCase.expected == nil { output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) @@ -1697,7 +1697,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerEnv() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -1707,7 +1707,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerEnv() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -1901,7 +1901,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerImage() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -1911,7 +1911,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerImage() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -2001,7 +2001,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerImagePull // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -2011,7 +2011,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerImagePull s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -2074,7 +2074,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerName() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -2084,7 +2084,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerName() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -2425,7 +2425,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerResourceR subT.Parallel() // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -2435,7 +2435,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerResourceR s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -2581,7 +2581,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerSecurityC // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -2592,7 +2592,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerSecurityC s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -2800,7 +2800,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerVolumeMou // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -2811,7 +2811,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerVolumeMou s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -3127,9 +3127,9 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestAffinity() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -3209,7 +3209,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestImagePullSecrets() // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -3220,7 +3220,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestImagePullSecrets() s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -3332,7 +3332,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestNodeSelector() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -3342,7 +3342,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestNodeSelector() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -3462,7 +3462,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestDeploymentAnnotati // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -3473,7 +3473,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestDeploymentAnnotati s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 allRange := strings.Split(output, "---") @@ -3594,7 +3594,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestPodAnnotations() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -3604,7 +3604,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestPodAnnotations() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 allRange := strings.Split(output, "---") @@ -3767,7 +3767,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestPodSecurityContext // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -3777,7 +3777,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestPodSecurityContext s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -3955,7 +3955,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestTemplateLabels() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -3966,7 +3966,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestTemplateLabels() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -4038,7 +4038,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestServiceAccountName subT.Parallel() if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -4048,7 +4048,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestServiceAccountName s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -4227,7 +4227,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestTolerations() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -4238,7 +4238,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestTolerations() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -4451,7 +4451,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestVolumes() { subT.Parallel() if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -4462,7 +4462,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestVolumes() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { // when vars are set outside of the if statement, they aren't accessible from within the conditional - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -4754,7 +4754,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerLivenessP subT.Parallel() if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -4765,7 +4765,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerLivenessP s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -5007,7 +5007,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerReadiness subT := s.T() subT.Parallel() if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -5017,7 +5017,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerReadiness s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -5262,7 +5262,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerStartupPr subT := s.T() subT.Parallel() if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -5272,7 +5272,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerStartupPr s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -5472,7 +5472,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerCmdArgs() subT.Parallel() if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/delegated-operator-instance-deployment.yaml in chart") @@ -5482,7 +5482,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestContainerCmdArgs() s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) // https://github.com/gruntwork-io/terratest/issues/586#issuecomment-848542351 @@ -5659,7 +5659,7 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestDeploymentUpdateSt subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} if testCase.values == nil { diff --git a/tests/unit/helm/delegated-operator-job-configmap_test.go b/tests/unit/helm/delegated-operator-job-configmap_test.go index 0199a13cc..dcbb97d5c 100644 --- a/tests/unit/helm/delegated-operator-job-configmap_test.go +++ b/tests/unit/helm/delegated-operator-job-configmap_test.go @@ -89,7 +89,7 @@ func (s *doK8sConfigMapTemplateTest) TestDisabled() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} if testCase.expected == "" { output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) @@ -138,7 +138,7 @@ func (s *doK8sConfigMapTemplateTest) TestMetadataName() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) @@ -177,7 +177,7 @@ func (s *doK8sConfigMapTemplateTest) TestMetadataNamespace() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) @@ -218,7 +218,7 @@ func (s *doK8sConfigMapTemplateTest) TestMetadataAnnotations() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var configMap corev1.ConfigMap @@ -291,7 +291,7 @@ func (s *doK8sConfigMapTemplateTest) TestMetadataLabels() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var configMap corev1.ConfigMap @@ -638,7 +638,7 @@ func (s *doK8sConfigMapTemplateTest) TestData() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) @@ -742,7 +742,7 @@ func (s *doK8sConfigMapTemplateTest) TestNativeSidecarTelemetryAutoInject() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values, SetStrValues: testCase.strValues} + options := &helm.Options{SetValues: disableTelemetry(testCase.values), SetStrValues: testCase.strValues} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var configMap corev1.ConfigMap diff --git a/tests/unit/helm/plugins-deployment_test.go b/tests/unit/helm/plugins-deployment_test.go index 72ba89406..459679ce8 100644 --- a/tests/unit/helm/plugins-deployment_test.go +++ b/tests/unit/helm/plugins-deployment_test.go @@ -102,7 +102,7 @@ func (s *deploymentPluginsTemplateTest) TestMetadataLabels() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} if testCase.expected == nil { output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) @@ -162,7 +162,7 @@ func (s *deploymentPluginsTemplateTest) TestMetadataName() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} if testCase.expected == "" { output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) @@ -220,7 +220,7 @@ func (s *deploymentPluginsTemplateTest) TestMetadataNamespace() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} if testCase.expected == "" { output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) @@ -278,7 +278,7 @@ func (s *deploymentPluginsTemplateTest) TestReplicas() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} if testCase.expected == 0 { output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -452,7 +452,7 @@ func (s *deploymentPluginsTemplateTest) TestTopologySpreadConstraints() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -490,7 +490,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerCount() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} if testCase.expected == 0 { output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) @@ -971,7 +971,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerEnv() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -981,7 +981,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerEnv() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1056,7 +1056,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerImage() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -1066,7 +1066,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerImage() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1115,7 +1115,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerImagePullPolicy() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -1125,7 +1125,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerImagePullPolicy() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1174,7 +1174,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerName() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -1184,7 +1184,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerName() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1284,7 +1284,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerLivenessProbe() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -1294,7 +1294,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerLivenessProbe() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1370,7 +1370,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerPorts() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -1380,7 +1380,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerPorts() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1480,7 +1480,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerReadinessProbe() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -1490,7 +1490,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerReadinessProbe() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1590,7 +1590,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerStartupProbe() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -1600,7 +1600,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerStartupProbe() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1671,7 +1671,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerResourceRequirements() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -1681,7 +1681,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerResourceRequirements() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1748,7 +1748,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerSecurityContext() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -1758,7 +1758,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerSecurityContext() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1849,7 +1849,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerVolumeMounts() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -1859,7 +1859,7 @@ func (s *deploymentPluginsTemplateTest) TestContainerVolumeMounts() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1902,7 +1902,7 @@ func (s *deploymentPluginsTemplateTest) TestInitContainerCount() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1944,7 +1944,7 @@ func (s *deploymentPluginsTemplateTest) TestInitContainerImage() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1999,7 +1999,7 @@ func (s *deploymentPluginsTemplateTest) TestInitContainerCommand() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2075,7 +2075,7 @@ func (s *deploymentPluginsTemplateTest) TestInitContainerResourceRequirements() subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2139,7 +2139,7 @@ func (s *deploymentPluginsTemplateTest) TestInitContainerSecurityContext() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2218,7 +2218,7 @@ func (s *deploymentPluginsTemplateTest) TestAffinity() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -2228,7 +2228,7 @@ func (s *deploymentPluginsTemplateTest) TestAffinity() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2277,7 +2277,7 @@ func (s *deploymentPluginsTemplateTest) TestImagePullSecrets() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -2287,7 +2287,7 @@ func (s *deploymentPluginsTemplateTest) TestImagePullSecrets() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2342,7 +2342,7 @@ func (s *deploymentPluginsTemplateTest) TestNodeSelector() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -2352,7 +2352,7 @@ func (s *deploymentPluginsTemplateTest) TestNodeSelector() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2406,7 +2406,7 @@ func (s *deploymentPluginsTemplateTest) TestDeploymentAnnotations() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -2416,7 +2416,7 @@ func (s *deploymentPluginsTemplateTest) TestDeploymentAnnotations() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2474,7 +2474,7 @@ func (s *deploymentPluginsTemplateTest) TestPodAnnotations() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -2484,7 +2484,7 @@ func (s *deploymentPluginsTemplateTest) TestPodAnnotations() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2559,7 +2559,7 @@ func (s *deploymentPluginsTemplateTest) TestPodSecurityContext() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -2569,7 +2569,7 @@ func (s *deploymentPluginsTemplateTest) TestPodSecurityContext() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2650,7 +2650,7 @@ func (s *deploymentPluginsTemplateTest) TestTemplateLabels() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -2660,7 +2660,7 @@ func (s *deploymentPluginsTemplateTest) TestTemplateLabels() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2718,7 +2718,7 @@ func (s *deploymentPluginsTemplateTest) TestServiceAccountName() { subT.Parallel() if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -2728,7 +2728,7 @@ func (s *deploymentPluginsTemplateTest) TestServiceAccountName() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2797,7 +2797,7 @@ func (s *deploymentPluginsTemplateTest) TestTolerations() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -2807,7 +2807,7 @@ func (s *deploymentPluginsTemplateTest) TestTolerations() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2904,7 +2904,7 @@ func (s *deploymentPluginsTemplateTest) TestVolumes() { // when vars are set outside of the if statement, they aren't accessible from within the conditional if testCase.values == nil { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output, err := helm.RenderTemplateE(subT, options, s.chartPath, s.releaseName, s.templates) s.ErrorContains(err, "could not find template templates/plugins-deployment.yaml in chart") @@ -2914,7 +2914,7 @@ func (s *deploymentPluginsTemplateTest) TestVolumes() { s.Nil(deployment.Spec.Template.Spec.Containers) } else { - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -3000,7 +3000,7 @@ func (s *deploymentPluginsTemplateTest) TestDeploymentUpdateStrategy() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} var deployment appsv1.Deployment if testCase.values == nil { diff --git a/tests/unit/helm/teams-app-deployment_test.go b/tests/unit/helm/teams-app-deployment_test.go index 9a51fad57..e1c96eff9 100644 --- a/tests/unit/helm/teams-app-deployment_test.go +++ b/tests/unit/helm/teams-app-deployment_test.go @@ -97,7 +97,7 @@ func (s *deploymentTeamsAppTemplateTest) TestMetadataLabels() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -138,7 +138,7 @@ func (s *deploymentTeamsAppTemplateTest) TestMetadataName() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -176,7 +176,7 @@ func (s *deploymentTeamsAppTemplateTest) TestMetadataNamespace() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -214,7 +214,7 @@ func (s *deploymentTeamsAppTemplateTest) TestReplicas() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -373,7 +373,7 @@ func (s *deploymentTeamsAppTemplateTest) TestTopologySpreadConstraints() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -404,7 +404,7 @@ func (s *deploymentTeamsAppTemplateTest) TestContainerCount() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1046,7 +1046,7 @@ func (s *deploymentTeamsAppTemplateTest) TestContainerEnv() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1108,7 +1108,7 @@ func (s *deploymentTeamsAppTemplateTest) TestContainerImage() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1146,7 +1146,7 @@ func (s *deploymentTeamsAppTemplateTest) TestContainerImagePullPolicy() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1185,7 +1185,7 @@ func (s *deploymentTeamsAppTemplateTest) TestContainerName() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1274,7 +1274,7 @@ func (s *deploymentTeamsAppTemplateTest) TestContainerLivenessProbe() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1337,7 +1337,7 @@ func (s *deploymentTeamsAppTemplateTest) TestContainerPorts() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1426,7 +1426,7 @@ func (s *deploymentTeamsAppTemplateTest) TestContainerReadinessProbe() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1515,7 +1515,7 @@ func (s *deploymentTeamsAppTemplateTest) TestContainerStartupProbe() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1573,7 +1573,7 @@ func (s *deploymentTeamsAppTemplateTest) TestContainerResourceRequirements() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1627,7 +1627,7 @@ func (s *deploymentTeamsAppTemplateTest) TestContainerSecurityContext() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1704,7 +1704,7 @@ func (s *deploymentTeamsAppTemplateTest) TestContainerVolumeMounts() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1742,7 +1742,7 @@ func (s *deploymentTeamsAppTemplateTest) TestInitContainerCount() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1781,7 +1781,7 @@ func (s *deploymentTeamsAppTemplateTest) TestInitContainerImage() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1833,7 +1833,7 @@ func (s *deploymentTeamsAppTemplateTest) TestInitContainerCommand() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1906,7 +1906,7 @@ func (s *deploymentTeamsAppTemplateTest) TestInitContainerResourceRequirements() subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -1967,7 +1967,7 @@ func (s *deploymentTeamsAppTemplateTest) TestInitContainerSecurityContext() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2034,7 +2034,7 @@ func (s *deploymentTeamsAppTemplateTest) TestAffinity() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2072,7 +2072,7 @@ func (s *deploymentTeamsAppTemplateTest) TestImagePullSecrets() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2116,7 +2116,7 @@ func (s *deploymentTeamsAppTemplateTest) TestNodeSelector() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2159,7 +2159,7 @@ func (s *deploymentTeamsAppTemplateTest) TestDeploymentAnnotations() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2206,7 +2206,7 @@ func (s *deploymentTeamsAppTemplateTest) TestPodAnnotations() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2268,7 +2268,7 @@ func (s *deploymentTeamsAppTemplateTest) TestPodSecurityContext() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2339,7 +2339,7 @@ func (s *deploymentTeamsAppTemplateTest) TestTemplateLabels() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2387,7 +2387,7 @@ func (s *deploymentTeamsAppTemplateTest) TestServiceAccountName() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2443,7 +2443,7 @@ func (s *deploymentTeamsAppTemplateTest) TestTolerations() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2526,7 +2526,7 @@ func (s *deploymentTeamsAppTemplateTest) TestVolumes() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment @@ -2599,7 +2599,7 @@ func (s *deploymentTeamsAppTemplateTest) TestDeploymentUpdateStrategy() { subT := s.T() subT.Parallel() - options := &helm.Options{SetValues: testCase.values} + options := &helm.Options{SetValues: disableTelemetry(testCase.values)} output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) var deployment appsv1.Deployment diff --git a/tests/unit/helm/telemetry-rolebinding_test.go b/tests/unit/helm/telemetry-rolebinding_test.go index 7b052daf4..024bb4757 100644 --- a/tests/unit/helm/telemetry-rolebinding_test.go +++ b/tests/unit/helm/telemetry-rolebinding_test.go @@ -130,9 +130,9 @@ func (s *telemetryRoleBindingTemplateTest) TestRoleRules() { } func (s *telemetryRoleBindingTemplateTest) TestRoleBindingDefaultSubject() { - // With an unset serviceAccounts list, the binding should fall back to - // a single subject pointing at the "default" ServiceAccount in the - // chart namespace. + // With an unset serviceAccounts list, the binding falls back to the + // chart's main app service account and the teams-api RBAC service + // account (the SAs used by auto-injected sidecars). options := &helm.Options{SetValues: map[string]string{ "telemetry.enabled": "true", }} @@ -141,10 +141,19 @@ func (s *telemetryRoleBindingTemplateTest) TestRoleBindingDefaultSubject() { rb, ok := s.extractRoleBinding(output) s.Require().True(ok, "RoleBinding document not found in rendered output") - s.Require().Len(rb.Subjects, 1, "Default RoleBinding should have exactly one subject") - s.Equal("ServiceAccount", rb.Subjects[0].Kind) - s.Equal("default", rb.Subjects[0].Name) - s.Equal("fiftyone-teams", rb.Subjects[0].Namespace) + s.Require().Len(rb.Subjects, 2, "Default RoleBinding should bind to app + api-rbac SAs") + subjectNames := map[string]bool{} + for _, sub := range rb.Subjects { + s.Equal("ServiceAccount", sub.Kind) + s.Equal("fiftyone-teams", sub.Namespace) + subjectNames[sub.Name] = true + } + // Main app SA: chart default serviceAccount.name = "fiftyone-teams" + mainAppSA := "fiftyone-teams" + // teams-api SA: -teams-api + apiSA := fmt.Sprintf("%s-fiftyone-teams-app-teams-api", s.releaseName) + s.True(subjectNames[mainAppSA], "should bind to main app SA %q", mainAppSA) + s.True(subjectNames[apiSA], "should bind to teams-api SA %q", apiSA) } func (s *telemetryRoleBindingTemplateTest) TestRoleBindingMultipleSubjects() { diff --git a/tests/unit/helm/yaml_helpers.go b/tests/unit/helm/yaml_helpers.go index 05e13c0e7..de1a1c8df 100644 --- a/tests/unit/helm/yaml_helpers.go +++ b/tests/unit/helm/yaml_helpers.go @@ -15,3 +15,17 @@ func splitYAMLDocs(s string) []string { } return out } + +// disableTelemetry returns a copy of the given helm SetValues map with +// `telemetry.enabled=false` injected as a default. Caller-supplied keys +// (including `telemetry.enabled=true`) win over the default. Use this in +// pre-existing tests that assert non-telemetry deployment shape so the +// chart's new `telemetry.enabled=true` default doesn't add a sidecar +// container / env var / volume that breaks their expectations. +func disableTelemetry(values map[string]string) map[string]string { + out := map[string]string{"telemetry.enabled": "false"} + for k, v := range values { + out[k] = v + } + return out +} From 5d410c42c9b0b6d263a7833d7d41e75e24314205 Mon Sep 17 00:00:00 2001 From: kacey Date: Tue, 19 May 2026 12:14:05 -0700 Subject: [PATCH 19/49] test: temporarily hardcode the sidecar --- helm/fiftyone-teams-app/README.md | 2 +- .../templates/_telemetry.tpl | 4 +-- helm/fiftyone-teams-app/values.schema.json | 4 +-- helm/fiftyone-teams-app/values.yaml | 5 +++- utils/validate-docker-pulls.sh | 29 +++++++++++++++++-- 5 files changed, 36 insertions(+), 8 deletions(-) diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index e42d57603..aa5a601f9 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -1102,7 +1102,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | telemetry.redis.resources | object | `{"limits":{"cpu":"250m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"256Mi"}}` | Resource requests/limits for the telemetry Redis container. [Reference][resources]. | | telemetry.redis.storage | string | `"1Gi"` | Persistent volume size for the telemetry Redis PVC. | | telemetry.serviceAccounts | list | `[]` | ServiceAccount names (in `namespace.name`) bound to the telemetry pod-logs Role. Each entry becomes a `ServiceAccount` subject on the generated RoleBinding. When empty, the RoleBinding subjects default to the chart's main app service account and the teams-api RBAC service account so auto-injected sidecars can read their target's logs. | -| telemetry.sidecar.image | string | `"voxel51/telemetry-sidecar:latest"` | Container image for the auto-injected `telemetry-sidecar` containers. | +| telemetry.sidecar.image | string | `"us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.61"` | Container image for the auto-injected `telemetry-sidecar` containers. The image is built and published from voxel51/fiftyone-teams `sidecar/` to the internal Voxel51 GAR. Override for environments without access to that registry (e.g. external chart consumers). | | telemetry.sidecar.imagePullPolicy | string | `"Always"` | Pull policy for the sidecar image. | | telemetry.sidecar.resources | object | `{"limits":{"cpu":"100m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"512Mi"}}` | Resource requests/limits for each auto-injected sidecar container. | diff --git a/helm/fiftyone-teams-app/templates/_telemetry.tpl b/helm/fiftyone-teams-app/templates/_telemetry.tpl index 632f1eb0b..f8dfd2602 100644 --- a/helm/fiftyone-teams-app/templates/_telemetry.tpl +++ b/helm/fiftyone-teams-app/templates/_telemetry.tpl @@ -110,7 +110,7 @@ Inputs: same dict as telemetry.sidecar-env. */}} {{- define "telemetry.sidecar" -}} - name: telemetry-sidecar - image: {{ .ctx.Values.telemetry.sidecar.image | default "voxel51/telemetry-sidecar:latest" | quote }} + image: {{ .ctx.Values.telemetry.sidecar.image | default "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.61" | quote }} imagePullPolicy: {{ .ctx.Values.telemetry.sidecar.imagePullPolicy | default "Always" }} env: {{- include "telemetry.sidecar-env" . | nindent 4 }} @@ -136,7 +136,7 @@ not exit would block Job completion. */}} {{- define "telemetry.native-sidecar" -}} - name: telemetry-sidecar - image: {{ .ctx.Values.telemetry.sidecar.image | default "voxel51/telemetry-sidecar:latest" | quote }} + image: {{ .ctx.Values.telemetry.sidecar.image | default "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.61" | quote }} imagePullPolicy: {{ .ctx.Values.telemetry.sidecar.imagePullPolicy | default "Always" }} restartPolicy: Always env: diff --git a/helm/fiftyone-teams-app/values.schema.json b/helm/fiftyone-teams-app/values.schema.json index b95f5c3f0..a43c57ce9 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -4102,8 +4102,8 @@ "sidecar": { "properties": { "image": { - "default": "voxel51/telemetry-sidecar:latest", - "description": "Container image for the auto-injected `telemetry-sidecar` containers.", + "default": "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.61", + "description": "Container image for the auto-injected `telemetry-sidecar` containers.\nThe image is built and published from voxel51/fiftyone-teams `sidecar/`\nto the internal Voxel51 GAR. Override for environments without access\nto that registry (e.g. external chart consumers).", "title": "image", "type": "string" }, diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index 6ea8e906c..77de6e3d3 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -1677,7 +1677,10 @@ telemetry: memory: 512Mi sidecar: # -- Container image for the auto-injected `telemetry-sidecar` containers. - image: voxel51/telemetry-sidecar:latest + # The image is built and published from voxel51/fiftyone-teams `sidecar/` + # to the internal Voxel51 GAR. Override for environments without access + # to that registry (e.g. external chart consumers). + image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.61 # -- Pull policy for the sidecar image. imagePullPolicy: Always # -- Resource requests/limits for each auto-injected sidecar container. diff --git a/utils/validate-docker-pulls.sh b/utils/validate-docker-pulls.sh index c830e09e5..d4e619b0a 100755 --- a/utils/validate-docker-pulls.sh +++ b/utils/validate-docker-pulls.sh @@ -14,6 +14,8 @@ set -euo pipefail # These are images we expect to be pullable in an # exhaustive set. +# Images with an explicit `:tag` are taken as-is; voxel51/ images without +# a tag get the chart appVersion appended automatically. EXPECTED_IMAGES=( "docker.io/busybox:stable-glibc" # For initContainers "docker.io/redis:7-alpine" # For telemetry redis (telemetry.enabled defaults to true) @@ -26,6 +28,21 @@ EXPECTED_IMAGES=( "voxel51/fiftyone-teams-cv-full" ) +# Images that the chart references by default but that are NOT publicly +# pullable. They bypass the docker_pull validation step but still count +# as "expected" for the in-helm-template check (so their presence doesn't +# trip the "in helm template but not in our published images" guard). +# Add the explicit `:tag` here — no auto-tag. +# +# Move entries OUT of this list once the corresponding image has a +# publicly-pullable tag (e.g. once the telemetry sidecar starts +# publishing to Docker Hub instead of the private Voxel51 GAR). +UNPUBLISHED_IMAGES=( + # Built from voxel51/fiftyone-teams `sidecar/`, published to the + # internal Voxel51 GAR. Requires GCP auth to pull. + "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.61" +) + . "$(dirname "$0")/common.sh" VALUES_YAML="${GIT_ROOT}/helm/fiftyone-teams-app/values.yaml" @@ -102,7 +119,11 @@ expected_images_with_tag=() expected_tag=$(yq '.appVersion' "${GIT_ROOT}/helm/fiftyone-teams-app/Chart.yaml") for img in "${EXPECTED_IMAGES[@]}"; do - if [[ ${img} =~ "voxel51/" ]]; then + if [[ ${img} == *":"* ]]; then + # Image already carries an explicit tag (e.g. telemetry-sidecar:latest); + # use as-is. + img_with_tag="${img}" + elif [[ ${img} =~ "voxel51/" ]]; then # Only add a tag for our organizations images, not # publicly available ones our chart may reference img_with_tag="${img}:${expected_tag}" @@ -123,12 +144,16 @@ helm_images=$( read -r -a helm_images_array <<<"${helm_images}" +# Images that are allowed in the helm template but skip docker_pull +# validation (e.g. not yet published to a public registry). +known_images_for_template_check=("${expected_images_with_tag[@]}" "${UNPUBLISHED_IMAGES[@]}") + pids=() rcs=() exit_code=0 for img in "${helm_images_array[@]}"; do - if [[ ! ${expected_images_with_tag[*]} =~ ${img} ]]; then + if [[ ! ${known_images_for_template_check[*]} =~ ${img} ]]; then echo "Image ${img} is in the 'helm template', but not in our published images!" exit 1 fi From a8c035712b8e4d3089f388f30f9701a5a4e2ea0e Mon Sep 17 00:00:00 2001 From: kacey Date: Tue, 19 May 2026 12:18:31 -0700 Subject: [PATCH 20/49] fix: use latest sidecar version 0.1.62 --- helm/fiftyone-teams-app/README.md | 2 +- helm/fiftyone-teams-app/templates/_telemetry.tpl | 4 ++-- helm/fiftyone-teams-app/values.schema.json | 2 +- helm/fiftyone-teams-app/values.yaml | 2 +- utils/validate-docker-pulls.sh | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index aa5a601f9..1e45d1ff1 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -1102,7 +1102,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | telemetry.redis.resources | object | `{"limits":{"cpu":"250m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"256Mi"}}` | Resource requests/limits for the telemetry Redis container. [Reference][resources]. | | telemetry.redis.storage | string | `"1Gi"` | Persistent volume size for the telemetry Redis PVC. | | telemetry.serviceAccounts | list | `[]` | ServiceAccount names (in `namespace.name`) bound to the telemetry pod-logs Role. Each entry becomes a `ServiceAccount` subject on the generated RoleBinding. When empty, the RoleBinding subjects default to the chart's main app service account and the teams-api RBAC service account so auto-injected sidecars can read their target's logs. | -| telemetry.sidecar.image | string | `"us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.61"` | Container image for the auto-injected `telemetry-sidecar` containers. The image is built and published from voxel51/fiftyone-teams `sidecar/` to the internal Voxel51 GAR. Override for environments without access to that registry (e.g. external chart consumers). | +| telemetry.sidecar.image | string | `"us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62"` | Container image for the auto-injected `telemetry-sidecar` containers. The image is built and published from voxel51/fiftyone-teams `sidecar/` to the internal Voxel51 GAR. Override for environments without access to that registry (e.g. external chart consumers). | | telemetry.sidecar.imagePullPolicy | string | `"Always"` | Pull policy for the sidecar image. | | telemetry.sidecar.resources | object | `{"limits":{"cpu":"100m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"512Mi"}}` | Resource requests/limits for each auto-injected sidecar container. | diff --git a/helm/fiftyone-teams-app/templates/_telemetry.tpl b/helm/fiftyone-teams-app/templates/_telemetry.tpl index f8dfd2602..4b91b88b7 100644 --- a/helm/fiftyone-teams-app/templates/_telemetry.tpl +++ b/helm/fiftyone-teams-app/templates/_telemetry.tpl @@ -110,7 +110,7 @@ Inputs: same dict as telemetry.sidecar-env. */}} {{- define "telemetry.sidecar" -}} - name: telemetry-sidecar - image: {{ .ctx.Values.telemetry.sidecar.image | default "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.61" | quote }} + image: {{ .ctx.Values.telemetry.sidecar.image | default "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62" | quote }} imagePullPolicy: {{ .ctx.Values.telemetry.sidecar.imagePullPolicy | default "Always" }} env: {{- include "telemetry.sidecar-env" . | nindent 4 }} @@ -136,7 +136,7 @@ not exit would block Job completion. */}} {{- define "telemetry.native-sidecar" -}} - name: telemetry-sidecar - image: {{ .ctx.Values.telemetry.sidecar.image | default "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.61" | quote }} + image: {{ .ctx.Values.telemetry.sidecar.image | default "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62" | quote }} imagePullPolicy: {{ .ctx.Values.telemetry.sidecar.imagePullPolicy | default "Always" }} restartPolicy: Always env: diff --git a/helm/fiftyone-teams-app/values.schema.json b/helm/fiftyone-teams-app/values.schema.json index a43c57ce9..a364e5f1f 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -4102,7 +4102,7 @@ "sidecar": { "properties": { "image": { - "default": "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.61", + "default": "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", "description": "Container image for the auto-injected `telemetry-sidecar` containers.\nThe image is built and published from voxel51/fiftyone-teams `sidecar/`\nto the internal Voxel51 GAR. Override for environments without access\nto that registry (e.g. external chart consumers).", "title": "image", "type": "string" diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index 77de6e3d3..dbd897fbe 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -1680,7 +1680,7 @@ telemetry: # The image is built and published from voxel51/fiftyone-teams `sidecar/` # to the internal Voxel51 GAR. Override for environments without access # to that registry (e.g. external chart consumers). - image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.61 + image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62 # -- Pull policy for the sidecar image. imagePullPolicy: Always # -- Resource requests/limits for each auto-injected sidecar container. diff --git a/utils/validate-docker-pulls.sh b/utils/validate-docker-pulls.sh index d4e619b0a..40e7d8e20 100755 --- a/utils/validate-docker-pulls.sh +++ b/utils/validate-docker-pulls.sh @@ -40,7 +40,7 @@ EXPECTED_IMAGES=( UNPUBLISHED_IMAGES=( # Built from voxel51/fiftyone-teams `sidecar/`, published to the # internal Voxel51 GAR. Requires GCP auth to pull. - "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.61" + "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62" ) . "$(dirname "$0")/common.sh" From 047041cf2813dedb35601aed9dd0942c6f46365b Mon Sep 17 00:00:00 2001 From: kacey Date: Tue, 19 May 2026 13:34:34 -0700 Subject: [PATCH 21/49] fix: runAsNonRoot override in sidecar --- helm/fiftyone-teams-app/templates/_telemetry.tpl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/helm/fiftyone-teams-app/templates/_telemetry.tpl b/helm/fiftyone-teams-app/templates/_telemetry.tpl index 4b91b88b7..690d1e65c 100644 --- a/helm/fiftyone-teams-app/templates/_telemetry.tpl +++ b/helm/fiftyone-teams-app/templates/_telemetry.tpl @@ -119,6 +119,11 @@ Inputs: same dict as telemetry.sidecar-env. {{- toYaml . | nindent 4 }} {{- end }} securityContext: + # The sidecar image runs as root (SYS_PTRACE + /proc//fd/1 access + # require it). Explicit container-level override so it works even when + # the pod's podSecurityContext sets runAsNonRoot: true. + runAsNonRoot: false + runAsUser: 0 capabilities: add: - SYS_PTRACE @@ -154,6 +159,11 @@ not exit would block Job completion. failureThreshold: 30 periodSeconds: 1 securityContext: + # The sidecar image runs as root (SYS_PTRACE + /proc//fd/1 access + # require it). Explicit container-level override so it works even when + # the pod's podSecurityContext sets runAsNonRoot: true. + runAsNonRoot: false + runAsUser: 0 capabilities: add: - SYS_PTRACE From c4013e9430469002d826ef697457d3bf34edb349 Mon Sep 17 00:00:00 2001 From: voxelbot Date: Tue, 19 May 2026 20:09:48 +0000 Subject: [PATCH 22/49] ci(fixtures): automated bump for integration test fixtures teams-app:v2.19.0-rc.5 api:v2.19.0rc6 app:v2.19.0rc6 cas:v2.19.0-rc.5 --- .../fixtures/docker/integration_internal_auth.env | 10 +++++----- tests/fixtures/docker/integration_legacy_auth.env | 10 +++++----- tests/fixtures/helm/integration_values.yaml | 14 +++++++------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/fixtures/docker/integration_internal_auth.env b/tests/fixtures/docker/integration_internal_auth.env index 48e46857a..e9bbce260 100644 --- a/tests/fixtures/docker/integration_internal_auth.env +++ b/tests/fixtures/docker/integration_internal_auth.env @@ -31,10 +31,10 @@ FIFTYONE_ENV=development CAS_DEBUG="cas:*" # Image tags for existing artifacts -VERSION=v2.19.0rc5 -FIFTYONE_APP_VERSION=v2.19.0rc5 -FIFTYONE_TEAMS_API_VERSION=v2.19.0rc5 -FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.4 -FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.4 +VERSION=v2.19.0rc6 +FIFTYONE_APP_VERSION=v2.19.0rc6 +FIFTYONE_TEAMS_API_VERSION=v2.19.0rc6 +FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.5 +FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.5 FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=1 diff --git a/tests/fixtures/docker/integration_legacy_auth.env b/tests/fixtures/docker/integration_legacy_auth.env index f1728dbd3..70b0c7463 100644 --- a/tests/fixtures/docker/integration_legacy_auth.env +++ b/tests/fixtures/docker/integration_legacy_auth.env @@ -39,10 +39,10 @@ FIFTYONE_ENV=development CAS_DEBUG="cas:*" # Image tags for existing artifacts -VERSION=v2.19.0rc5 -FIFTYONE_APP_VERSION=v2.19.0rc5 -FIFTYONE_TEAMS_API_VERSION=v2.19.0rc5 -FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.4 -FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.4 +VERSION=v2.19.0rc6 +FIFTYONE_APP_VERSION=v2.19.0rc6 +FIFTYONE_TEAMS_API_VERSION=v2.19.0rc6 +FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.5 +FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.5 FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=1 diff --git a/tests/fixtures/helm/integration_values.yaml b/tests/fixtures/helm/integration_values.yaml index cb75b1268..54796228e 100644 --- a/tests/fixtures/helm/integration_values.yaml +++ b/tests/fixtures/helm/integration_values.yaml @@ -9,7 +9,7 @@ apiSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-teams-api pullPolicy: IfNotPresent - tag: v2.19.0rc5 + tag: v2.19.0rc6 service: liveness: initialDelaySeconds: 15 @@ -27,7 +27,7 @@ appSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc5 + tag: v2.19.0rc6 # TODO: Test `minikube addons configure registry-creds` or # When using minikube's addon registry-creds, we may also need to create the k8s secret `regcred` # See https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ @@ -42,21 +42,21 @@ casSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-teams-cas pullPolicy: IfNotPresent - tag: v2.19.0-rc.4 + tag: v2.19.0-rc.5 delegatedOperatorDeployments: deployments: teamsDoCpuDefault: image: repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc5 + tag: v2.19.0rc6 delegatedOperatorTemplates: jobs: teamsDoCpuDefaultK8s: image: repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc5 + tag: v2.19.0rc6 ingress: annotations: # For using the nginx-ingress controller with cert-manager self signed certificates @@ -121,7 +121,7 @@ pluginsSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc5 + tag: v2.19.0rc6 teamsAppSettings: dnsName: local.fiftyone.ai # env: @@ -138,4 +138,4 @@ teamsAppSettings: # The others are `vW.X.Y.devZ` (note `.devZ` vs `-dev.Z`). # This is a byproduct of `npm` versioning versus Python PEP 440. pullPolicy: IfNotPresent - tag: v2.19.0-rc.4 + tag: v2.19.0-rc.5 From 20655e2dbca535643540d25446be82d1dd8397a2 Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Tue, 19 May 2026 17:12:04 -0400 Subject: [PATCH 23/49] feat(helm): support external Redis via `telemetry.redis.external.url` Add an opt-in escape hatch for operators running managed Redis (ElastiCache, MemoryStore, etc.) or an existing shared Redis. When `telemetry.redis.external.url` is set, the chart skips the bundled Redis `Deployment`/`Service`/`PersistentVolumeClaim` and wires both consumer workloads and auto-injected sidecars at the external URL. Threads through a new `telemetry.redis.url` helper that returns either the external URL or the in-cluster `redis://-telemetry-redis` URL, replacing the inline `printf` previously used in two places. Tests in tests/unit/helm/telemetry-redis_test.go cover: bundled Redis NOT rendered when external.url set; api deployment env var points at the external URL on every container (workload + sidecar); the default release-scoped URL still wires correctly. Co-Authored-By: Claude Opus 4.7 --- helm/fiftyone-teams-app/README.md | 1 + .../templates/_telemetry.tpl | 23 ++++++- .../templates/telemetry-redis.yaml | 2 +- helm/fiftyone-teams-app/values.schema.json | 14 ++++ helm/fiftyone-teams-app/values.yaml | 9 +++ tests/unit/helm/telemetry-redis_test.go | 67 +++++++++++++++++++ 6 files changed, 113 insertions(+), 3 deletions(-) diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 1e45d1ff1..aaac48116 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -1096,6 +1096,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | teamsAppSettings.volumeMounts | list | `[]` | Volume mounts for `teams-app` pods. [Reference][volumes]. | | teamsAppSettings.volumes | list | `[]` | Volumes for `teams-app` pods. [Reference][volumes]. | | telemetry.enabled | bool | `true` | Master switch. When `true` (default), all telemetry resources and auto-injected sidecars are rendered. When `false`, none are. | +| telemetry.redis.external.url | string | `""` | URL of an external Redis to use instead of the bundled one (e.g. `redis://my-redis.example.com:6379`). | | telemetry.redis.image | string | `"redis:7-alpine"` | Container image for the telemetry Redis Deployment. | | telemetry.redis.maxmemory | string | `"400mb"` | `--maxmemory` flag passed to `redis-server`. | | telemetry.redis.maxmemoryPolicy | string | `"allkeys-lru"` | `--maxmemory-policy` flag passed to `redis-server`. | diff --git a/helm/fiftyone-teams-app/templates/_telemetry.tpl b/helm/fiftyone-teams-app/templates/_telemetry.tpl index 690d1e65c..b55046c33 100644 --- a/helm/fiftyone-teams-app/templates/_telemetry.tpl +++ b/helm/fiftyone-teams-app/templates/_telemetry.tpl @@ -5,6 +5,25 @@ Name of the telemetry redis Deployment/Service/PVC. {{- printf "%s-telemetry-redis" .Release.Name | trunc 63 | trimSuffix "-" -}} {{- end }} +{{/* +Resolves the URL for the telemetry Redis backend. + +If `telemetry.redis.external.url` is set, returns it (chart skips the +bundled Redis Deployment/Service/PVC and consumer workloads + sidecars +are wired at the external URL instead — e.g. for managed Redis like +ElastiCache or MemoryStore). Otherwise returns the in-cluster Service +URL of the bundled Redis. + +Always returns a non-empty URL when telemetry is enabled. +*/}} +{{- define "telemetry.redis.url" -}} +{{- if .Values.telemetry.redis.external.url -}} +{{- .Values.telemetry.redis.external.url -}} +{{- else -}} +{{- printf "redis://%s:6379" (include "telemetry.redis.name" .) -}} +{{- end -}} +{{- end }} + {{/* Selector labels for the telemetry redis Deployment/Service. */}} @@ -91,7 +110,7 @@ Inputs (dict): value: /tmp/telemetry/agent.sock {{- end }} - name: FIFTYONE_TELEMETRY_REDIS_URL - value: {{ printf "redis://%s:6379" (include "telemetry.redis.name" .ctx) | quote }} + value: {{ include "telemetry.redis.url" .ctx | quote }} - name: FIFTYONE_DATABASE_URI valueFrom: secretKeyRef: @@ -180,7 +199,7 @@ env-vars-list helpers. {{- define "telemetry.redis-url-env" -}} {{- if .Values.telemetry.enabled }} - name: FIFTYONE_TELEMETRY_REDIS_URL - value: {{ printf "redis://%s:6379" (include "telemetry.redis.name" .) | quote }} + value: {{ include "telemetry.redis.url" . | quote }} {{- end }} {{- end }} diff --git a/helm/fiftyone-teams-app/templates/telemetry-redis.yaml b/helm/fiftyone-teams-app/templates/telemetry-redis.yaml index 03c611da7..ed4976201 100644 --- a/helm/fiftyone-teams-app/templates/telemetry-redis.yaml +++ b/helm/fiftyone-teams-app/templates/telemetry-redis.yaml @@ -1,4 +1,4 @@ -{{- if .Values.telemetry.enabled }} +{{- if and .Values.telemetry.enabled (not .Values.telemetry.redis.external.url) }} apiVersion: v1 kind: PersistentVolumeClaim metadata: diff --git a/helm/fiftyone-teams-app/values.schema.json b/helm/fiftyone-teams-app/values.schema.json index a364e5f1f..12f3b6697 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -4020,6 +4020,20 @@ }, "redis": { "properties": { + "external": { + "description": "When `external.url` is set, the chart does NOT render the bundled\nRedis `Deployment`/`Service`/`PersistentVolumeClaim`; consumer\nworkloads and auto-injected sidecars are wired at the external URL\ninstead. Use this for managed Redis (ElastiCache, MemoryStore, etc.)\nor an existing shared Redis you already operate.", + "properties": { + "url": { + "default": "", + "description": "URL of an external Redis to use instead of the bundled one\n(e.g. `redis://my-redis.example.com:6379`).", + "title": "url", + "type": "string" + } + }, + "required": [], + "title": "external", + "type": "object" + }, "image": { "default": "redis:7-alpine", "description": "Container image for the telemetry Redis Deployment.", diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index dbd897fbe..c17b6789d 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -1675,6 +1675,15 @@ telemetry: limits: cpu: 250m memory: 512Mi + # When `external.url` is set, the chart does NOT render the bundled + # Redis `Deployment`/`Service`/`PersistentVolumeClaim`; consumer + # workloads and auto-injected sidecars are wired at the external URL + # instead. Use this for managed Redis (ElastiCache, MemoryStore, etc.) + # or an existing shared Redis you already operate. + external: + # -- URL of an external Redis to use instead of the bundled one + # (e.g. `redis://my-redis.example.com:6379`). + url: "" sidecar: # -- Container image for the auto-injected `telemetry-sidecar` containers. # The image is built and published from voxel51/fiftyone-teams `sidecar/` diff --git a/tests/unit/helm/telemetry-redis_test.go b/tests/unit/helm/telemetry-redis_test.go index 8ce69c431..9babfe7f0 100644 --- a/tests/unit/helm/telemetry-redis_test.go +++ b/tests/unit/helm/telemetry-redis_test.go @@ -60,6 +60,73 @@ func (s *telemetryRedisTemplateTest) TestExplicitlyDisabled() { s.ErrorContains(err, "could not find template templates/telemetry-redis.yaml in chart") } +// TestExternalUrlSkipsBundled ensures that setting telemetry.redis.external.url +// causes the bundled Redis Deployment/Service/PVC to NOT be rendered. The +// chart should leave Redis provisioning to the operator in this case. +func (s *telemetryRedisTemplateTest) TestExternalUrlSkipsBundled() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.external.url": "redis://my-managed-redis:6379", + }} + + _, err := helm.RenderTemplateE(s.T(), options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/telemetry-redis.yaml in chart") +} + +// TestExternalUrlWiresApiDeployment ensures that setting external.url causes +// FIFTYONE_TELEMETRY_REDIS_URL on the api deployment to point at the external +// URL rather than the in-cluster Service. +func (s *telemetryRedisTemplateTest) TestExternalUrlWiresApiDeployment() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.external.url": "redis://my-managed-redis:6379", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, + []string{"templates/api-deployment.yaml"}) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(s.T(), output, &deployment) + s.Require().Len(deployment.Spec.Template.Spec.Containers, 2, + "Expected api + telemetry-sidecar containers") + + for _, container := range deployment.Spec.Template.Spec.Containers { + var found *corev1.EnvVar + for i, ev := range container.Env { + if ev.Name == "FIFTYONE_TELEMETRY_REDIS_URL" { + found = &container.Env[i] + break + } + } + s.Require().NotNil(found, + "FIFTYONE_TELEMETRY_REDIS_URL should be set on %s container", container.Name) + s.Equal("redis://my-managed-redis:6379", found.Value, + "FIFTYONE_TELEMETRY_REDIS_URL on %s should point at the external URL", + container.Name) + } +} + +// TestBundledUrlWiresApiDeployment ensures the default in-cluster URL is +// release-scoped on the api deployment's containers. +func (s *telemetryRedisTemplateTest) TestBundledUrlWiresApiDeployment() { + options := &helm.Options{SetValues: nil} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, + []string{"templates/api-deployment.yaml"}) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(s.T(), output, &deployment) + + expectedURL := fmt.Sprintf("redis://%s-telemetry-redis:6379", s.releaseName) + for _, container := range deployment.Spec.Template.Spec.Containers { + for _, ev := range container.Env { + if ev.Name == "FIFTYONE_TELEMETRY_REDIS_URL" { + s.Equal(expectedURL, ev.Value, + "FIFTYONE_TELEMETRY_REDIS_URL on %s should be release-scoped in-cluster URL", + container.Name) + } + } + } +} + // extractDeployment finds the Deployment document in the multi-doc render output. func (s *telemetryRedisTemplateTest) extractDeployment(output string) appsv1.Deployment { for _, doc := range splitYAMLDocs(output) { From bc877079e3a83d50734a0d4e6f75c3094316ad36 Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Tue, 19 May 2026 17:22:39 -0400 Subject: [PATCH 24/49] feat(helm): configurable telemetry Redis persistence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the single `telemetry.redis.storage` knob with a `telemetry.redis.persistence` block: - `persistence.enabled` (default `true`) — when `false`, skip the PVC and run Redis on an `emptyDir`. State is lost on pod restart; intended for dev/minikube clusters without a dynamic PV provisioner. - `persistence.size` — replaces `storage`. Same default (`1Gi`). - `persistence.storageClass` — passed through to the PVC. Leave unset to use the cluster's default `StorageClass`. Tests added: PVC has the expected `storageClassName` when set; `persistence.enabled=false` skips the PVC and the Deployment switches the `redis-data` volume to `emptyDir`. Co-Authored-By: Claude Opus 4.7 --- helm/fiftyone-teams-app/README.md | 4 ++- .../templates/telemetry-redis.yaml | 11 +++++- helm/fiftyone-teams-app/values.schema.json | 35 +++++++++++++++---- helm/fiftyone-teams-app/values.yaml | 16 +++++++-- tests/unit/helm/telemetry-redis_test.go | 30 ++++++++++++++-- 5 files changed, 84 insertions(+), 12 deletions(-) diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index aaac48116..dd8ed9fdd 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -1100,8 +1100,10 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | telemetry.redis.image | string | `"redis:7-alpine"` | Container image for the telemetry Redis Deployment. | | telemetry.redis.maxmemory | string | `"400mb"` | `--maxmemory` flag passed to `redis-server`. | | telemetry.redis.maxmemoryPolicy | string | `"allkeys-lru"` | `--maxmemory-policy` flag passed to `redis-server`. | +| telemetry.redis.persistence.enabled | bool | `true` | Controls whether a `PersistentVolumeClaim` is created for the bundled telemetry Redis. When `false`, Redis runs with an `emptyDir` volume and state is lost on pod restart — fine for dev clusters without a dynamic PV provisioner, not for production. | +| telemetry.redis.persistence.size | string | `"1Gi"` | Storage size for the telemetry Redis `PersistentVolumeClaim`. | +| telemetry.redis.persistence.storageClass | string | `""` | `StorageClass` name for the telemetry Redis `PersistentVolumeClaim`. Leave unset to use the cluster's default `StorageClass`. | | telemetry.redis.resources | object | `{"limits":{"cpu":"250m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"256Mi"}}` | Resource requests/limits for the telemetry Redis container. [Reference][resources]. | -| telemetry.redis.storage | string | `"1Gi"` | Persistent volume size for the telemetry Redis PVC. | | telemetry.serviceAccounts | list | `[]` | ServiceAccount names (in `namespace.name`) bound to the telemetry pod-logs Role. Each entry becomes a `ServiceAccount` subject on the generated RoleBinding. When empty, the RoleBinding subjects default to the chart's main app service account and the teams-api RBAC service account so auto-injected sidecars can read their target's logs. | | telemetry.sidecar.image | string | `"us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62"` | Container image for the auto-injected `telemetry-sidecar` containers. The image is built and published from voxel51/fiftyone-teams `sidecar/` to the internal Voxel51 GAR. Override for environments without access to that registry (e.g. external chart consumers). | | telemetry.sidecar.imagePullPolicy | string | `"Always"` | Pull policy for the sidecar image. | diff --git a/helm/fiftyone-teams-app/templates/telemetry-redis.yaml b/helm/fiftyone-teams-app/templates/telemetry-redis.yaml index ed4976201..e07559479 100644 --- a/helm/fiftyone-teams-app/templates/telemetry-redis.yaml +++ b/helm/fiftyone-teams-app/templates/telemetry-redis.yaml @@ -1,4 +1,5 @@ {{- if and .Values.telemetry.enabled (not .Values.telemetry.redis.external.url) }} +{{- if .Values.telemetry.redis.persistence.enabled }} apiVersion: v1 kind: PersistentVolumeClaim metadata: @@ -9,10 +10,14 @@ metadata: spec: accessModes: - ReadWriteOnce + {{- with .Values.telemetry.redis.persistence.storageClass }} + storageClassName: {{ . | quote }} + {{- end }} resources: requests: - storage: {{ .Values.telemetry.redis.storage | default "1Gi" }} + storage: {{ .Values.telemetry.redis.persistence.size | default "1Gi" }} --- +{{- end }} apiVersion: apps/v1 kind: Deployment metadata: @@ -55,8 +60,12 @@ spec: mountPath: /data volumes: - name: redis-data + {{- if .Values.telemetry.redis.persistence.enabled }} persistentVolumeClaim: claimName: {{ include "telemetry.redis.name" . }}-data + {{- else }} + emptyDir: {} + {{- end }} --- apiVersion: v1 kind: Service diff --git a/helm/fiftyone-teams-app/values.schema.json b/helm/fiftyone-teams-app/values.schema.json index 12f3b6697..08748895a 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -4052,6 +4052,35 @@ "title": "maxmemoryPolicy", "type": "string" }, + "persistence": { + "properties": { + "enabled": { + "default": true, + "description": "Controls whether a `PersistentVolumeClaim` is created for the\nbundled telemetry Redis. When `false`, Redis runs with an `emptyDir`\nvolume and state is lost on pod restart — fine for dev clusters\nwithout a dynamic PV provisioner, not for production.", + "title": "enabled", + "type": "boolean" + }, + "size": { + "default": "1Gi", + "description": "Storage size for the telemetry Redis `PersistentVolumeClaim`.", + "title": "size", + "type": "string" + }, + "storageClass": { + "default": "", + "description": "`StorageClass` name for the telemetry Redis `PersistentVolumeClaim`.\nLeave unset to use the cluster's default `StorageClass`.", + "required": [], + "title": "storageClass", + "type": [ + "string", + "null" + ] + } + }, + "required": [], + "title": "persistence", + "type": "object" + }, "resources": { "description": "Resource requests/limits for the telemetry Redis container. [Reference][resources].", "properties": { @@ -4093,12 +4122,6 @@ "required": [], "title": "resources", "type": "object" - }, - "storage": { - "default": "1Gi", - "description": "Persistent volume size for the telemetry Redis PVC.", - "title": "storage", - "type": "string" } }, "required": [], diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index c17b6789d..a926f0997 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -1661,8 +1661,20 @@ telemetry: redis: # -- Container image for the telemetry Redis Deployment. image: redis:7-alpine - # -- Persistent volume size for the telemetry Redis PVC. - storage: 1Gi + persistence: + # -- Controls whether a `PersistentVolumeClaim` is created for the + # bundled telemetry Redis. When `false`, Redis runs with an `emptyDir` + # volume and state is lost on pod restart — fine for dev clusters + # without a dynamic PV provisioner, not for production. + enabled: true + # -- Storage size for the telemetry Redis `PersistentVolumeClaim`. + size: 1Gi + # @schema + # type: ["string", "null"] + # @schema + # -- `StorageClass` name for the telemetry Redis `PersistentVolumeClaim`. + # Leave unset to use the cluster's default `StorageClass`. + storageClass: "" # -- `--maxmemory` flag passed to `redis-server`. maxmemory: 400mb # -- `--maxmemory-policy` flag passed to `redis-server`. diff --git a/tests/unit/helm/telemetry-redis_test.go b/tests/unit/helm/telemetry-redis_test.go index 9babfe7f0..2fe3ced2e 100644 --- a/tests/unit/helm/telemetry-redis_test.go +++ b/tests/unit/helm/telemetry-redis_test.go @@ -235,8 +235,9 @@ func (s *telemetryRedisTemplateTest) TestServiceMetadata() { func (s *telemetryRedisTemplateTest) TestPVCMetadata() { options := &helm.Options{SetValues: map[string]string{ - "telemetry.enabled": "true", - "telemetry.redis.storage": "5Gi", + "telemetry.enabled": "true", + "telemetry.redis.persistence.size": "5Gi", + "telemetry.redis.persistence.storageClass": "gp3", }} output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) @@ -251,7 +252,32 @@ func (s *telemetryRedisTemplateTest) TestPVCMetadata() { s.Equal(expectedName, pvc.ObjectMeta.Name) req := pvc.Spec.Resources.Requests[corev1.ResourceStorage] s.Equal("5Gi", req.String()) + s.Require().NotNil(pvc.Spec.StorageClassName, "StorageClassName should be set") + s.Equal("gp3", *pvc.Spec.StorageClassName) return } s.Fail("PVC document not found in rendered output") } + +// TestPersistenceDisabledSkipsPVCAndUsesEmptyDir ensures that with +// persistence.enabled=false, the PVC is not rendered and the Deployment +// switches the redis-data volume to emptyDir. +func (s *telemetryRedisTemplateTest) TestPersistenceDisabledSkipsPVCAndUsesEmptyDir() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.persistence.enabled": "false", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + + s.NotContains(output, "kind: PersistentVolumeClaim", + "PVC should not render when persistence.enabled=false") + + deployment := s.extractDeployment(output) + volumes := deployment.Spec.Template.Spec.Volumes + s.Require().Len(volumes, 1, "Deployment should have exactly one volume") + s.Equal("redis-data", volumes[0].Name) + s.NotNil(volumes[0].EmptyDir, + "redis-data volume should be emptyDir when persistence is disabled") + s.Nil(volumes[0].PersistentVolumeClaim, + "redis-data volume should NOT reference a PVC when persistence is disabled") +} From 0581d39b17cdf72ce1b697d4ad0765d8c705970d Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Tue, 19 May 2026 18:13:56 -0400 Subject: [PATCH 25/49] fix(helm): route DO env vars through `telemetry.redis.url` helper The delegated-operator workload containers and DO Job templates were still building `FIFTYONE_TELEMETRY_REDIS_URL` inline via `printf "redis://%s:6379" (include "telemetry.redis.name" .ctx)`, bypassing the `telemetry.redis.url` helper added in the external-Redis commit. As a result, setting `telemetry.redis.external.url` correctly pointed api/app/cas/plugins/teams-app at the external URL but left the DO workloads pointing at the in-cluster Service. Both sites now go through `telemetry.redis.url .ctx`. Regression tests added to tests/unit/helm/telemetry-redis_test.go cover both the DO deployment workload container and the DO Job ConfigMap. Co-Authored-By: Claude Opus 4.7 --- .../templates/_do_targets.tpl | 2 +- .../fiftyone-teams-app/templates/_helpers.tpl | 2 +- tests/unit/helm/telemetry-redis_test.go | 61 +++++++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/helm/fiftyone-teams-app/templates/_do_targets.tpl b/helm/fiftyone-teams-app/templates/_do_targets.tpl index 3a90d039b..1466435aa 100644 --- a/helm/fiftyone-teams-app/templates/_do_targets.tpl +++ b/helm/fiftyone-teams-app/templates/_do_targets.tpl @@ -52,7 +52,7 @@ Create a merged list of environment variables for delegated-operator templates key: encryptionKey {{- if and .ctx .ctx.Values.telemetry.enabled }} - name: FIFTYONE_TELEMETRY_REDIS_URL - value: {{ printf "redis://%s:6379" (include "telemetry.redis.name" .ctx) | quote }} + value: {{ include "telemetry.redis.url" .ctx | quote }} - name: TELEMETRY_SOCKET value: /tmp/telemetry/agent.sock {{- end }} diff --git a/helm/fiftyone-teams-app/templates/_helpers.tpl b/helm/fiftyone-teams-app/templates/_helpers.tpl index 8b5e1ac9f..848051e24 100644 --- a/helm/fiftyone-teams-app/templates/_helpers.tpl +++ b/helm/fiftyone-teams-app/templates/_helpers.tpl @@ -326,7 +326,7 @@ Create a merged list of environment variables for delegated-operator-executor key: encryptionKey {{- if and .ctx .ctx.Values.telemetry.enabled }} - name: FIFTYONE_TELEMETRY_REDIS_URL - value: {{ printf "redis://%s:6379" (include "telemetry.redis.name" .ctx) | quote }} + value: {{ include "telemetry.redis.url" .ctx | quote }} - name: TELEMETRY_SOCKET value: /tmp/telemetry/agent.sock {{- end }} diff --git a/tests/unit/helm/telemetry-redis_test.go b/tests/unit/helm/telemetry-redis_test.go index 2fe3ced2e..0562bcde0 100644 --- a/tests/unit/helm/telemetry-redis_test.go +++ b/tests/unit/helm/telemetry-redis_test.go @@ -127,6 +127,67 @@ func (s *telemetryRedisTemplateTest) TestBundledUrlWiresApiDeployment() { } } +// TestExternalUrlWiresDelegatedOperatorDeployment regression-tests that the +// delegated-operator deployment's workload-container env honors external.url. +// Earlier the DO env-vars helper built the URL inline via `printf "redis://..."` +// rather than going through the `telemetry.redis.url` helper, so it always +// pointed at the in-cluster Service even when external.url was set. +func (s *telemetryRedisTemplateTest) TestExternalUrlWiresDelegatedOperatorDeployment() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.external.url": "redis://my-managed-redis:6379", + "delegatedOperatorDeployments.deployments.teamsDoCpuDefault.enabled": "true", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, + []string{"templates/delegated-operator-instance-deployment.yaml"}) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(s.T(), output, &deployment) + + // Both the workload container and the auto-injected sidecar should see + // the external URL. + s.Require().GreaterOrEqual(len(deployment.Spec.Template.Spec.Containers), 1, + "Expected at least one container in DO deployment") + for _, container := range deployment.Spec.Template.Spec.Containers { + var found *corev1.EnvVar + for i, ev := range container.Env { + if ev.Name == "FIFTYONE_TELEMETRY_REDIS_URL" { + found = &container.Env[i] + break + } + } + s.Require().NotNil(found, + "FIFTYONE_TELEMETRY_REDIS_URL should be set on %s container", container.Name) + s.Equal("redis://my-managed-redis:6379", found.Value, + "FIFTYONE_TELEMETRY_REDIS_URL on %s should point at the external URL", + container.Name) + } +} + +// TestExternalUrlWiresDelegatedOperatorJobConfigMap regression-tests that the +// DO Job template (rendered into the do-templates ConfigMap) honors +// external.url. Same root cause as the DO deployment bug above — the +// templates env-vars helper built the URL inline rather than going through +// `telemetry.redis.url`. +func (s *telemetryRedisTemplateTest) TestExternalUrlWiresDelegatedOperatorJobConfigMap() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.external.url": "redis://my-managed-redis:6379", + "delegatedOperatorJobTemplates.jobs.test-job.enabled": "true", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, + []string{"templates/delegated-operator-job-configmap.yaml"}) + + // The ConfigMap embeds the Job spec as a multi-line YAML string in + // .data.. The render output is plain text, so we just need to + // ensure no occurrence of the in-cluster URL leaks through. + inClusterURL := fmt.Sprintf("redis://%s-telemetry-redis:6379", s.releaseName) + s.NotContains(output, inClusterURL, + "DO Job ConfigMap must not embed the in-cluster Redis URL when external.url is set") + s.Contains(output, "redis://my-managed-redis:6379", + "DO Job ConfigMap should embed the external Redis URL") +} + // extractDeployment finds the Deployment document in the multi-doc render output. func (s *telemetryRedisTemplateTest) extractDeployment(output string) appsv1.Deployment { for _, doc := range splitYAMLDocs(output) { From bf7d29750df8fcc85e956608bdf83fd52f4e0e3f Mon Sep 17 00:00:00 2001 From: voxelbot Date: Tue, 19 May 2026 21:21:53 +0000 Subject: [PATCH 26/49] ci(fixtures): automated bump for integration test fixtures teams-app:v2.19.0-rc.6 api:v2.19.0rc7 app:v2.19.0rc7 cas:v2.19.0-rc.6 --- .../fixtures/docker/integration_internal_auth.env | 10 +++++----- tests/fixtures/docker/integration_legacy_auth.env | 10 +++++----- tests/fixtures/helm/integration_values.yaml | 14 +++++++------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/fixtures/docker/integration_internal_auth.env b/tests/fixtures/docker/integration_internal_auth.env index e9bbce260..d0919314e 100644 --- a/tests/fixtures/docker/integration_internal_auth.env +++ b/tests/fixtures/docker/integration_internal_auth.env @@ -31,10 +31,10 @@ FIFTYONE_ENV=development CAS_DEBUG="cas:*" # Image tags for existing artifacts -VERSION=v2.19.0rc6 -FIFTYONE_APP_VERSION=v2.19.0rc6 -FIFTYONE_TEAMS_API_VERSION=v2.19.0rc6 -FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.5 -FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.5 +VERSION=v2.19.0rc7 +FIFTYONE_APP_VERSION=v2.19.0rc7 +FIFTYONE_TEAMS_API_VERSION=v2.19.0rc7 +FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.6 +FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.6 FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=1 diff --git a/tests/fixtures/docker/integration_legacy_auth.env b/tests/fixtures/docker/integration_legacy_auth.env index 70b0c7463..e833b77b8 100644 --- a/tests/fixtures/docker/integration_legacy_auth.env +++ b/tests/fixtures/docker/integration_legacy_auth.env @@ -39,10 +39,10 @@ FIFTYONE_ENV=development CAS_DEBUG="cas:*" # Image tags for existing artifacts -VERSION=v2.19.0rc6 -FIFTYONE_APP_VERSION=v2.19.0rc6 -FIFTYONE_TEAMS_API_VERSION=v2.19.0rc6 -FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.5 -FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.5 +VERSION=v2.19.0rc7 +FIFTYONE_APP_VERSION=v2.19.0rc7 +FIFTYONE_TEAMS_API_VERSION=v2.19.0rc7 +FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.6 +FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.6 FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=1 diff --git a/tests/fixtures/helm/integration_values.yaml b/tests/fixtures/helm/integration_values.yaml index 54796228e..2c6973f65 100644 --- a/tests/fixtures/helm/integration_values.yaml +++ b/tests/fixtures/helm/integration_values.yaml @@ -9,7 +9,7 @@ apiSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-teams-api pullPolicy: IfNotPresent - tag: v2.19.0rc6 + tag: v2.19.0rc7 service: liveness: initialDelaySeconds: 15 @@ -27,7 +27,7 @@ appSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc6 + tag: v2.19.0rc7 # TODO: Test `minikube addons configure registry-creds` or # When using minikube's addon registry-creds, we may also need to create the k8s secret `regcred` # See https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ @@ -42,21 +42,21 @@ casSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-teams-cas pullPolicy: IfNotPresent - tag: v2.19.0-rc.5 + tag: v2.19.0-rc.6 delegatedOperatorDeployments: deployments: teamsDoCpuDefault: image: repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc6 + tag: v2.19.0rc7 delegatedOperatorTemplates: jobs: teamsDoCpuDefaultK8s: image: repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc6 + tag: v2.19.0rc7 ingress: annotations: # For using the nginx-ingress controller with cert-manager self signed certificates @@ -121,7 +121,7 @@ pluginsSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc6 + tag: v2.19.0rc7 teamsAppSettings: dnsName: local.fiftyone.ai # env: @@ -138,4 +138,4 @@ teamsAppSettings: # The others are `vW.X.Y.devZ` (note `.devZ` vs `-dev.Z`). # This is a byproduct of `npm` versioning versus Python PEP 440. pullPolicy: IfNotPresent - tag: v2.19.0-rc.5 + tag: v2.19.0-rc.6 From 57f1cc6b8ea1e145cbb82c3e028bd2c66802aa90 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 19 May 2026 23:45:16 +0000 Subject: [PATCH 27/49] feat(compose): bring telemetry config to parity with helm chart - pin sidecar image default to v0.1.62 (matches helm `telemetry.sidecar.image`); update unit-test expectations in lockstep - add `TELEMETRY_REDIS_MAXMEMORY_POLICY` env knob (was hardcoded to `allkeys-lru`; now mirrors helm `telemetry.redis.maxmemoryPolicy`) - add `deploy.resources` cpu/memory limits + reservations to telemetry Redis and every sidecar service to match the helm defaults (Redis: 0.25/512M lim, 0.10/256M res; sidecars: 0.10/512M) - rewrite env.template telemetry section: drop stale `compose.telemetry.yaml overlay` reference (bundled by default since the sidecar work landed), document the new policy knob and the v0.1.62 pin, and clarify when to override - expand `docs/configuring-telemetry.md` with the new env var and a resource-limits table cross-referencing the helm defaults Applies symmetrically to internal-auth and legacy-auth, including the plugins / dedicated-plugins / delegated-operators / delegated-operators GPU overlay variants. --- docker/docs/configuring-telemetry.md | 45 +++++++++++++------ .../compose.dedicated-plugins.yaml | 40 +++++++++++++++-- .../compose.delegated-operators.gpu.yaml | 7 ++- .../compose.delegated-operators.yaml | 10 ++++- docker/internal-auth/compose.plugins.yaml | 30 +++++++++++-- docker/internal-auth/compose.yaml | 30 +++++++++++-- docker/internal-auth/env.template | 26 +++++++---- .../compose.dedicated-plugins.yaml | 40 +++++++++++++++-- .../compose.delegated-operators.gpu.yaml | 7 ++- .../compose.delegated-operators.yaml | 10 ++++- docker/legacy-auth/compose.plugins.yaml | 30 +++++++++++-- docker/legacy-auth/compose.yaml | 30 +++++++++++-- docker/legacy-auth/env.template | 26 +++++++---- .../docker-compose-internal-auth_test.go | 8 ++-- .../docker-compose-legacy-auth_test.go | 8 ++-- 15 files changed, 285 insertions(+), 62 deletions(-) diff --git a/docker/docs/configuring-telemetry.md b/docker/docs/configuring-telemetry.md index 99f7d0bb1..898fd736d 100644 --- a/docker/docs/configuring-telemetry.md +++ b/docker/docs/configuring-telemetry.md @@ -149,20 +149,37 @@ Kubernetes pod-restart behavior. All knobs live in your `.env` — see `env.template` for the full list: -| Variable | Default | Purpose | -| ------------------------------ | ----------------------------------- | ---------------------------------------------------- | -| `FIFTYONE_TELEMETRY_REDIS_URL` | `redis://telemetry-redis:6379` | Override to point at an external Redis if desired | -| `TELEMETRY_SIDECAR_IMAGE` | `voxel51/telemetry-sidecar:latest` | Pin sidecar to a specific tag for production | -| `TELEMETRY_REDIS_IMAGE` | `redis:7-alpine` | Alternate redis image | -| `TELEMETRY_REDIS_MAXMEMORY` | `400mb` | Redis maxmemory budget | -| `TELEMETRY_NAMESPACE` | `docker` | Namespace label attached to each registered pod | -| `FIFTYONE_APP_TARGET_NAME` | `hypercorn` | Substring used to locate the fiftyone-app process | -| `TEAMS_API_TARGET_NAME` | `fiftyone-teams-api` | Substring used to locate the teams-api process | -| `TEAMS_PLUGINS_TARGET_NAME` | `hypercorn` | Substring used to locate the teams-plugins process | -| `TEAMS_DO_TARGET_NAME` | `fiftyone delegated` | Substring used to locate the teams-do process | -| `NVIDIA_GPU_COUNT` | `1` | GPU reservation for the GPU DO worker + sidecar | -| `NVIDIA_VISIBLE_DEVICES` | `all` | Pass-through to teams-do-gpu / sidecar | -| `NVIDIA_DRIVER_CAPABILITIES` | `compute,utility` | Must include `utility` so NVML is available | +| Variable | Default | Purpose | +| ---------------------------------- | ----------------------------------- | ---------------------------------------------------- | +| `FIFTYONE_TELEMETRY_REDIS_URL` | `redis://telemetry-redis:6379` | Override to point at an external Redis if desired | +| `TELEMETRY_SIDECAR_IMAGE` | `voxel51/telemetry-sidecar:v0.1.62` | Pinned to match the helm chart's `telemetry.sidecar.image` tag | +| `TELEMETRY_REDIS_IMAGE` | `redis:7-alpine` | Alternate redis image | +| `TELEMETRY_REDIS_MAXMEMORY` | `400mb` | Redis maxmemory budget | +| `TELEMETRY_REDIS_MAXMEMORY_POLICY` | `allkeys-lru` | Redis eviction policy (mirrors helm `telemetry.redis.maxmemoryPolicy`) | +| `TELEMETRY_NAMESPACE` | `docker` | Namespace label attached to each registered pod | +| `FIFTYONE_APP_TARGET_NAME` | `hypercorn` | Substring used to locate the fiftyone-app process | +| `TEAMS_API_TARGET_NAME` | `fiftyone-teams-api` | Substring used to locate the teams-api process | +| `TEAMS_PLUGINS_TARGET_NAME` | `hypercorn` | Substring used to locate the teams-plugins process | +| `TEAMS_DO_TARGET_NAME` | `fiftyone delegated` | Substring used to locate the teams-do process | +| `NVIDIA_GPU_COUNT` | `1` | GPU reservation for the GPU DO worker + sidecar | +| `NVIDIA_VISIBLE_DEVICES` | `all` | Pass-through to teams-do-gpu / sidecar | +| `NVIDIA_DRIVER_CAPABILITIES` | `compute,utility` | Must include `utility` so NVML is available | + +## Resource limits + +Telemetry containers ship with conservative CPU and memory limits that +mirror the helm chart's defaults — sized so the sidecars do not starve +the workloads they observe. The values are declared under each +service's `deploy.resources` block in the compose files; compose v2 +honors `cpus` and `memory` limits/reservations outside swarm mode. + +| Service | CPU limit | Memory limit | Notes | +| -------------------- | --------- | ------------ | ------------------------------------ | +| `telemetry-redis` | `0.25` | `512M` | Reserves `0.10` CPU / `256M` memory | +| `*-telemetry` (any sidecar) | `0.10` | `512M` | Reserved == limit | + +To tune these for your hardware, override `deploy.resources` in a +`compose.override.yaml` (the override merges with the base entry). ## Access control diff --git a/docker/internal-auth/compose.dedicated-plugins.yaml b/docker/internal-auth/compose.dedicated-plugins.yaml index e037e6ef0..cba1870ce 100644 --- a/docker/internal-auth/compose.dedicated-plugins.yaml +++ b/docker/internal-auth/compose.dedicated-plugins.yaml @@ -51,13 +51,21 @@ services: - --maxmemory - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} - --maxmemory-policy - - allkeys-lru + - ${TELEMETRY_REDIS_MAXMEMORY_POLICY:-allkeys-lru} restart: always + deploy: + resources: + limits: + cpus: "0.25" + memory: 512M + reservations: + cpus: "0.10" + memory: 256M volumes: - telemetry-redis-data:/data fiftyone-app-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:fiftyone-app" cap_add: - SYS_PTRACE @@ -67,6 +75,14 @@ services: SERVICE_TYPE: fiftyone-app TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M depends_on: fiftyone-app: condition: service_started @@ -76,7 +92,7 @@ services: restart: always teams-api-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:teams-api" cap_add: - SYS_PTRACE @@ -86,6 +102,14 @@ services: SERVICE_TYPE: teams-api TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M depends_on: teams-api: condition: service_started @@ -95,7 +119,7 @@ services: restart: always teams-plugins-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:teams-plugins" cap_add: - SYS_PTRACE @@ -105,6 +129,14 @@ services: SERVICE_TYPE: teams-plugins TARGET_NAME: ${TEAMS_PLUGINS_TARGET_NAME:-hypercorn} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M depends_on: teams-plugins: condition: service_started diff --git a/docker/internal-auth/compose.delegated-operators.gpu.yaml b/docker/internal-auth/compose.delegated-operators.gpu.yaml index a6de978d8..75aba04d8 100644 --- a/docker/internal-auth/compose.delegated-operators.gpu.yaml +++ b/docker/internal-auth/compose.delegated-operators.gpu.yaml @@ -49,14 +49,19 @@ services: restart: always teams-do-gpu-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:teams-do-gpu" profiles: ["gpu"] cap_add: - SYS_PTRACE deploy: resources: + limits: + cpus: "0.10" + memory: 512M reservations: + cpus: "0.10" + memory: 512M devices: - driver: nvidia count: ${NVIDIA_GPU_COUNT:-1} diff --git a/docker/internal-auth/compose.delegated-operators.yaml b/docker/internal-auth/compose.delegated-operators.yaml index 2daddc633..65d4bd216 100644 --- a/docker/internal-auth/compose.delegated-operators.yaml +++ b/docker/internal-auth/compose.delegated-operators.yaml @@ -16,7 +16,7 @@ services: service: teams-do-common teams-do-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:teams-do" cap_add: - SYS_PTRACE @@ -30,6 +30,14 @@ services: FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M volumes: - telemetry-socket:/tmp/telemetry depends_on: diff --git a/docker/internal-auth/compose.plugins.yaml b/docker/internal-auth/compose.plugins.yaml index 66fb916a4..431d4cab1 100644 --- a/docker/internal-auth/compose.plugins.yaml +++ b/docker/internal-auth/compose.plugins.yaml @@ -46,13 +46,21 @@ services: - --maxmemory - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} - --maxmemory-policy - - allkeys-lru + - ${TELEMETRY_REDIS_MAXMEMORY_POLICY:-allkeys-lru} restart: always + deploy: + resources: + limits: + cpus: "0.25" + memory: 512M + reservations: + cpus: "0.10" + memory: 256M volumes: - telemetry-redis-data:/data fiftyone-app-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:fiftyone-app" cap_add: - SYS_PTRACE @@ -62,6 +70,14 @@ services: SERVICE_TYPE: fiftyone-app TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M depends_on: fiftyone-app: condition: service_started @@ -71,7 +87,7 @@ services: restart: always teams-api-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:teams-api" cap_add: - SYS_PTRACE @@ -81,6 +97,14 @@ services: SERVICE_TYPE: teams-api TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M depends_on: teams-api: condition: service_started diff --git a/docker/internal-auth/compose.yaml b/docker/internal-auth/compose.yaml index b2a9a83fd..0e5656f5e 100644 --- a/docker/internal-auth/compose.yaml +++ b/docker/internal-auth/compose.yaml @@ -43,8 +43,16 @@ services: - --maxmemory - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} - --maxmemory-policy - - allkeys-lru + - ${TELEMETRY_REDIS_MAXMEMORY_POLICY:-allkeys-lru} restart: always + deploy: + resources: + limits: + cpus: "0.25" + memory: 512M + reservations: + cpus: "0.10" + memory: 256M volumes: - telemetry-redis-data:/data @@ -52,7 +60,7 @@ services: # target's PID namespace so the sidecar can read /proc//fd/1 and # psutil can see the target's process. fiftyone-app-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:fiftyone-app" cap_add: - SYS_PTRACE @@ -62,6 +70,14 @@ services: SERVICE_TYPE: fiftyone-app TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M depends_on: fiftyone-app: condition: service_started @@ -71,7 +87,7 @@ services: restart: always teams-api-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:teams-api" cap_add: - SYS_PTRACE @@ -81,6 +97,14 @@ services: SERVICE_TYPE: teams-api TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M depends_on: teams-api: condition: service_started diff --git a/docker/internal-auth/env.template b/docker/internal-auth/env.template index 270780446..7d23a6a2b 100644 --- a/docker/internal-auth/env.template +++ b/docker/internal-auth/env.template @@ -99,19 +99,29 @@ FIFTYONE_APP_ENABLE_QUERY_PERFORMANCE=true # Set to 3 by default, meaning delegated operations can be executed without an external executor system. FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=3 -# ─── Telemetry (optional; requires compose.telemetry.yaml overlay) ───── -# Redis URL used by the telemetry sidecars and the teams-api / teams-app -# services. Leave blank to use the default in-stack Redis defined by -# compose.telemetry.yaml (redis://telemetry-redis:6379). +# ─── Telemetry (bundled by default; overrides below are optional) ────── +# Telemetry (`telemetry-redis` + per-service sidecars) is included in +# every compose file by default. The variables below only need to be set +# when overriding the bundled defaults. See +# docker/docs/configuring-telemetry.md to opt out or swap in an external +# Redis. + +# Redis URL used by the telemetry sidecars and the main fiftyone-app / +# teams-api / teams-app / teams-plugins / teams-do containers. Leave +# blank to use the bundled in-stack Redis (redis://telemetry-redis:6379). +# Override to point at a managed Redis (e.g. ElastiCache, MemoryStore). # FIFTYONE_TELEMETRY_REDIS_URL= -# Sidecar image. Default is voxel51/telemetry-sidecar:latest; pin to a -# specific tag in production. -# TELEMETRY_SIDECAR_IMAGE=voxel51/telemetry-sidecar:latest +# Sidecar image. Default pins to v0.1.62 for parity with the helm chart; +# update both compose files and the helm `telemetry.sidecar.image` in +# lockstep when bumping. +# TELEMETRY_SIDECAR_IMAGE=voxel51/telemetry-sidecar:v0.1.62 -# Redis image for the in-stack telemetry backend. +# Redis image, max memory, and eviction policy for the bundled telemetry +# backend. The defaults mirror the helm `telemetry.redis.*` settings. # TELEMETRY_REDIS_IMAGE=redis:7-alpine # TELEMETRY_REDIS_MAXMEMORY=400mb +# TELEMETRY_REDIS_MAXMEMORY_POLICY=allkeys-lru # Namespace label applied to all telemetry pods registered by sidecars. # TELEMETRY_NAMESPACE=docker diff --git a/docker/legacy-auth/compose.dedicated-plugins.yaml b/docker/legacy-auth/compose.dedicated-plugins.yaml index aa58adbf1..e2f44f13a 100644 --- a/docker/legacy-auth/compose.dedicated-plugins.yaml +++ b/docker/legacy-auth/compose.dedicated-plugins.yaml @@ -50,13 +50,21 @@ services: - --maxmemory - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} - --maxmemory-policy - - allkeys-lru + - ${TELEMETRY_REDIS_MAXMEMORY_POLICY:-allkeys-lru} restart: always + deploy: + resources: + limits: + cpus: "0.25" + memory: 512M + reservations: + cpus: "0.10" + memory: 256M volumes: - telemetry-redis-data:/data fiftyone-app-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:fiftyone-app" cap_add: - SYS_PTRACE @@ -66,6 +74,14 @@ services: SERVICE_TYPE: fiftyone-app TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M depends_on: fiftyone-app: condition: service_started @@ -75,7 +91,7 @@ services: restart: always teams-api-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:teams-api" cap_add: - SYS_PTRACE @@ -85,6 +101,14 @@ services: SERVICE_TYPE: teams-api TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M depends_on: teams-api: condition: service_started @@ -94,7 +118,7 @@ services: restart: always teams-plugins-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:teams-plugins" cap_add: - SYS_PTRACE @@ -104,6 +128,14 @@ services: SERVICE_TYPE: teams-plugins TARGET_NAME: ${TEAMS_PLUGINS_TARGET_NAME:-hypercorn} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M depends_on: teams-plugins: condition: service_started diff --git a/docker/legacy-auth/compose.delegated-operators.gpu.yaml b/docker/legacy-auth/compose.delegated-operators.gpu.yaml index a6de978d8..75aba04d8 100644 --- a/docker/legacy-auth/compose.delegated-operators.gpu.yaml +++ b/docker/legacy-auth/compose.delegated-operators.gpu.yaml @@ -49,14 +49,19 @@ services: restart: always teams-do-gpu-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:teams-do-gpu" profiles: ["gpu"] cap_add: - SYS_PTRACE deploy: resources: + limits: + cpus: "0.10" + memory: 512M reservations: + cpus: "0.10" + memory: 512M devices: - driver: nvidia count: ${NVIDIA_GPU_COUNT:-1} diff --git a/docker/legacy-auth/compose.delegated-operators.yaml b/docker/legacy-auth/compose.delegated-operators.yaml index 2daddc633..65d4bd216 100644 --- a/docker/legacy-auth/compose.delegated-operators.yaml +++ b/docker/legacy-auth/compose.delegated-operators.yaml @@ -16,7 +16,7 @@ services: service: teams-do-common teams-do-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:teams-do" cap_add: - SYS_PTRACE @@ -30,6 +30,14 @@ services: FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M volumes: - telemetry-socket:/tmp/telemetry depends_on: diff --git a/docker/legacy-auth/compose.plugins.yaml b/docker/legacy-auth/compose.plugins.yaml index f2bea08cb..d7e19a3ec 100644 --- a/docker/legacy-auth/compose.plugins.yaml +++ b/docker/legacy-auth/compose.plugins.yaml @@ -45,13 +45,21 @@ services: - --maxmemory - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} - --maxmemory-policy - - allkeys-lru + - ${TELEMETRY_REDIS_MAXMEMORY_POLICY:-allkeys-lru} restart: always + deploy: + resources: + limits: + cpus: "0.25" + memory: 512M + reservations: + cpus: "0.10" + memory: 256M volumes: - telemetry-redis-data:/data fiftyone-app-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:fiftyone-app" cap_add: - SYS_PTRACE @@ -61,6 +69,14 @@ services: SERVICE_TYPE: fiftyone-app TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M depends_on: fiftyone-app: condition: service_started @@ -70,7 +86,7 @@ services: restart: always teams-api-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:teams-api" cap_add: - SYS_PTRACE @@ -80,6 +96,14 @@ services: SERVICE_TYPE: teams-api TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M depends_on: teams-api: condition: service_started diff --git a/docker/legacy-auth/compose.yaml b/docker/legacy-auth/compose.yaml index 447c1ddf3..64c077a56 100644 --- a/docker/legacy-auth/compose.yaml +++ b/docker/legacy-auth/compose.yaml @@ -42,8 +42,16 @@ services: - --maxmemory - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} - --maxmemory-policy - - allkeys-lru + - ${TELEMETRY_REDIS_MAXMEMORY_POLICY:-allkeys-lru} restart: always + deploy: + resources: + limits: + cpus: "0.25" + memory: 512M + reservations: + cpus: "0.10" + memory: 256M volumes: - telemetry-redis-data:/data @@ -51,7 +59,7 @@ services: # target's PID namespace so the sidecar can read /proc//fd/1 and # psutil can see the target's process. fiftyone-app-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:fiftyone-app" cap_add: - SYS_PTRACE @@ -61,6 +69,14 @@ services: SERVICE_TYPE: fiftyone-app TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M depends_on: fiftyone-app: condition: service_started @@ -70,7 +86,7 @@ services: restart: always teams-api-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:latest} + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} pid: "service:teams-api" cap_add: - SYS_PTRACE @@ -80,6 +96,14 @@ services: SERVICE_TYPE: teams-api TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M depends_on: teams-api: condition: service_started diff --git a/docker/legacy-auth/env.template b/docker/legacy-auth/env.template index ef62237a1..c897e8a8a 100644 --- a/docker/legacy-auth/env.template +++ b/docker/legacy-auth/env.template @@ -104,19 +104,29 @@ FIFTYONE_APP_ENABLE_QUERY_PERFORMANCE=true # Set to 3 by default, meaning delegated operations can be executed without an external executor system. FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=3 -# ─── Telemetry (optional; requires compose.telemetry.yaml overlay) ───── -# Redis URL used by the telemetry sidecars and the teams-api / teams-app -# services. Leave blank to use the default in-stack Redis defined by -# compose.telemetry.yaml (redis://telemetry-redis:6379). +# ─── Telemetry (bundled by default; overrides below are optional) ────── +# Telemetry (`telemetry-redis` + per-service sidecars) is included in +# every compose file by default. The variables below only need to be set +# when overriding the bundled defaults. See +# docker/docs/configuring-telemetry.md to opt out or swap in an external +# Redis. + +# Redis URL used by the telemetry sidecars and the main fiftyone-app / +# teams-api / teams-app / teams-plugins / teams-do containers. Leave +# blank to use the bundled in-stack Redis (redis://telemetry-redis:6379). +# Override to point at a managed Redis (e.g. ElastiCache, MemoryStore). # FIFTYONE_TELEMETRY_REDIS_URL= -# Sidecar image. Default is voxel51/telemetry-sidecar:latest; pin to a -# specific tag in production. -# TELEMETRY_SIDECAR_IMAGE=voxel51/telemetry-sidecar:latest +# Sidecar image. Default pins to v0.1.62 for parity with the helm chart; +# update both compose files and the helm `telemetry.sidecar.image` in +# lockstep when bumping. +# TELEMETRY_SIDECAR_IMAGE=voxel51/telemetry-sidecar:v0.1.62 -# Redis image for the in-stack telemetry backend. +# Redis image, max memory, and eviction policy for the bundled telemetry +# backend. The defaults mirror the helm `telemetry.redis.*` settings. # TELEMETRY_REDIS_IMAGE=redis:7-alpine # TELEMETRY_REDIS_MAXMEMORY=400mb +# TELEMETRY_REDIS_MAXMEMORY_POLICY=allkeys-lru # Namespace label applied to all telemetry pods registered by sidecars. # TELEMETRY_NAMESPACE=docker diff --git a/tests/unit/compose/docker-compose-internal-auth_test.go b/tests/unit/compose/docker-compose-internal-auth_test.go index 8de86dc86..781ef09b8 100644 --- a/tests/unit/compose/docker-compose-internal-auth_test.go +++ b/tests/unit/compose/docker-compose-internal-auth_test.go @@ -247,14 +247,14 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceImage() { "fiftyone-app-telemetry", []string{internalAuthComposeFile}, s.dotEnvFiles, - "voxel51/telemetry-sidecar:latest", + "voxel51/telemetry-sidecar:v0.1.62", }, { "telemetrySidecarTeamsApi", "teams-api-telemetry", []string{internalAuthComposeFile}, s.dotEnvFiles, - "voxel51/telemetry-sidecar:latest", + "voxel51/telemetry-sidecar:v0.1.62", }, { "telemetrySidecarTeamsDo", @@ -264,14 +264,14 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceImage() { internalAuthComposeDelegatedOperationsFile, }, s.dotEnvFiles, - "voxel51/telemetry-sidecar:latest", + "voxel51/telemetry-sidecar:v0.1.62", }, { "telemetrySidecarTeamsPlugins", "teams-plugins-telemetry", []string{internalAuthComposeDedicatedPluginsFile}, s.dotEnvFiles, - "voxel51/telemetry-sidecar:latest", + "voxel51/telemetry-sidecar:v0.1.62", }, } diff --git a/tests/unit/compose/docker-compose-legacy-auth_test.go b/tests/unit/compose/docker-compose-legacy-auth_test.go index 240a86dc8..5a8d97dcb 100644 --- a/tests/unit/compose/docker-compose-legacy-auth_test.go +++ b/tests/unit/compose/docker-compose-legacy-auth_test.go @@ -247,14 +247,14 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceImage() { "fiftyone-app-telemetry", []string{legacyAuthComposeFile}, s.dotEnvFiles, - "voxel51/telemetry-sidecar:latest", + "voxel51/telemetry-sidecar:v0.1.62", }, { "telemetrySidecarTeamsApi", "teams-api-telemetry", []string{legacyAuthComposeFile}, s.dotEnvFiles, - "voxel51/telemetry-sidecar:latest", + "voxel51/telemetry-sidecar:v0.1.62", }, { "telemetrySidecarTeamsDo", @@ -264,14 +264,14 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceImage() { legacyAuthComposeDelegatedOperationsFile, }, s.dotEnvFiles, - "voxel51/telemetry-sidecar:latest", + "voxel51/telemetry-sidecar:v0.1.62", }, { "telemetrySidecarTeamsPlugins", "teams-plugins-telemetry", []string{legacyAuthComposeDedicatedPluginsFile}, s.dotEnvFiles, - "voxel51/telemetry-sidecar:latest", + "voxel51/telemetry-sidecar:v0.1.62", }, } From 71a945628a3c440b757bb2407680bc02f470e60f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 20 May 2026 00:09:06 +0000 Subject: [PATCH 28/49] refactor(compose): consolidate telemetry services into common-services.yaml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The docker README documents that `compose.plugins.yaml` and `compose.dedicated-plugins.yaml` are used *instead of* `compose.yaml` (not layered on top of it), which previously forced each of the three base files to redefine `telemetry-redis`, `fiftyone-app-telemetry`, and `teams-api-telemetry` verbatim. Bumping image tags or resource limits meant editing the same block three times — and missing one would silently desync the deployments. Move the telemetry services into `common-services.yaml` with `-common` suffixes (mirroring the existing pattern for `fiftyone-app-common`, `teams-api-common`, etc.), then have each compose file pull them in via `extends:`. Each child redeclares `depends_on` because compose's `extends` intentionally does not propagate it. The rendered `docker compose config` output is byte-identical to the pre-refactor version for every combo across both auth flavors; this is a pure structural cleanup with no runtime behavior change. `compose.delegated-operators.gpu.yaml` keeps its standalone sidecar definition: it's only used in one file, and the GPU device reservation diverges enough from the regular DO sidecar to make `extends` more trouble than it saves. --- docker/common-services.yaml | 128 ++++++++++++++++++ .../compose.dedicated-plugins.yaml | 92 ++----------- .../compose.delegated-operators.yaml | 28 +--- docker/internal-auth/compose.plugins.yaml | 70 ++-------- docker/internal-auth/compose.yaml | 73 ++-------- .../compose.dedicated-plugins.yaml | 92 ++----------- .../compose.delegated-operators.yaml | 28 +--- docker/legacy-auth/compose.plugins.yaml | 70 ++-------- docker/legacy-auth/compose.yaml | 73 ++-------- 9 files changed, 194 insertions(+), 460 deletions(-) diff --git a/docker/common-services.yaml b/docker/common-services.yaml index 04067e940..43489fc67 100644 --- a/docker/common-services.yaml +++ b/docker/common-services.yaml @@ -185,3 +185,131 @@ services: volumes: - plugins-vol:/opt/plugins:ro - telemetry-socket:/tmp/telemetry + + # ─── Telemetry common services ───────────────────────────────────────── + # Each base compose file (compose.yaml, compose.plugins.yaml, + # compose.dedicated-plugins.yaml) is a standalone alternative to the + # others — they can't be layered. Defining the telemetry services once + # here lets all three pull them in via `extends:` without duplicating + # image, env, and resource blocks. + # + # `extends` does NOT propagate `depends_on`; each extending service + # redeclares it locally. + + telemetry-redis-common: + image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} + command: + - redis-server + - --save + - "60" + - "1" + - --dir + - /data + - --maxmemory + - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} + - --maxmemory-policy + - ${TELEMETRY_REDIS_MAXMEMORY_POLICY:-allkeys-lru} + restart: always + deploy: + resources: + limits: + cpus: "0.25" + memory: 512M + reservations: + cpus: "0.10" + memory: 256M + volumes: + - telemetry-redis-data:/data + + # One sidecar per observed service. `pid: "service:"` joins the + # target's PID namespace so the sidecar can read /proc//fd/1 and + # psutil can see the target's process. + fiftyone-app-telemetry-common: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} + pid: "service:fiftyone-app" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: fiftyone-app + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: fiftyone-app + TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M + restart: always + + teams-api-telemetry-common: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} + pid: "service:teams-api" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: teams-api + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: teams-api + TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M + restart: always + + teams-plugins-telemetry-common: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} + pid: "service:teams-plugins" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: teams-plugins + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: teams-plugins + TARGET_NAME: ${TEAMS_PLUGINS_TARGET_NAME:-hypercorn} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M + restart: always + + teams-do-telemetry-common: + image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} + pid: "service:teams-do" + cap_add: + - SYS_PTRACE + environment: + POD_NAME: teams-do + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: delegated-operator + TARGET_NAME: ${TEAMS_DO_TARGET_NAME:-fiftyone delegated} + EXECUTOR_SIDECAR: "true" + TELEMETRY_SOCKET: /tmp/telemetry/agent.sock + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} + FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M + volumes: + - telemetry-socket:/tmp/telemetry + restart: always diff --git a/docker/internal-auth/compose.dedicated-plugins.yaml b/docker/internal-auth/compose.dedicated-plugins.yaml index cba1870ce..503ff802e 100644 --- a/docker/internal-auth/compose.dedicated-plugins.yaml +++ b/docker/internal-auth/compose.dedicated-plugins.yaml @@ -40,110 +40,42 @@ services: - plugins-vol:/opt/plugins:ro telemetry-redis: - image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} - command: - - redis-server - - --save - - "60" - - "1" - - --dir - - /data - - --maxmemory - - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} - - --maxmemory-policy - - ${TELEMETRY_REDIS_MAXMEMORY_POLICY:-allkeys-lru} - restart: always - deploy: - resources: - limits: - cpus: "0.25" - memory: 512M - reservations: - cpus: "0.10" - memory: 256M - volumes: - - telemetry-redis-data:/data + extends: + file: ../common-services.yaml + service: telemetry-redis-common fiftyone-app-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} - pid: "service:fiftyone-app" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: fiftyone-app - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: fiftyone-app - TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - deploy: - resources: - limits: - cpus: "0.10" - memory: 512M - reservations: - cpus: "0.10" - memory: 512M + extends: + file: ../common-services.yaml + service: fiftyone-app-telemetry-common depends_on: fiftyone-app: condition: service_started restart: true telemetry-redis: condition: service_started - restart: always teams-api-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} - pid: "service:teams-api" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: teams-api - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: teams-api - TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - deploy: - resources: - limits: - cpus: "0.10" - memory: 512M - reservations: - cpus: "0.10" - memory: 512M + extends: + file: ../common-services.yaml + service: teams-api-telemetry-common depends_on: teams-api: condition: service_started restart: true telemetry-redis: condition: service_started - restart: always teams-plugins-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} - pid: "service:teams-plugins" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: teams-plugins - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: teams-plugins - TARGET_NAME: ${TEAMS_PLUGINS_TARGET_NAME:-hypercorn} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - deploy: - resources: - limits: - cpus: "0.10" - memory: 512M - reservations: - cpus: "0.10" - memory: 512M + extends: + file: ../common-services.yaml + service: teams-plugins-telemetry-common depends_on: teams-plugins: condition: service_started restart: true telemetry-redis: condition: service_started - restart: always volumes: plugins-vol: diff --git a/docker/internal-auth/compose.delegated-operators.yaml b/docker/internal-auth/compose.delegated-operators.yaml index 65d4bd216..5846577e9 100644 --- a/docker/internal-auth/compose.delegated-operators.yaml +++ b/docker/internal-auth/compose.delegated-operators.yaml @@ -16,37 +16,15 @@ services: service: teams-do-common teams-do-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} - pid: "service:teams-do" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: teams-do - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: delegated-operator - TARGET_NAME: ${TEAMS_DO_TARGET_NAME:-fiftyone delegated} - EXECUTOR_SIDECAR: "true" - TELEMETRY_SOCKET: /tmp/telemetry/agent.sock - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} - FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} - deploy: - resources: - limits: - cpus: "0.10" - memory: 512M - reservations: - cpus: "0.10" - memory: 512M - volumes: - - telemetry-socket:/tmp/telemetry + extends: + file: ../common-services.yaml + service: teams-do-telemetry-common depends_on: teams-do: condition: service_started restart: true telemetry-redis: condition: service_started - restart: always volumes: plugins-vol: diff --git a/docker/internal-auth/compose.plugins.yaml b/docker/internal-auth/compose.plugins.yaml index 431d4cab1..7e8d116e6 100644 --- a/docker/internal-auth/compose.plugins.yaml +++ b/docker/internal-auth/compose.plugins.yaml @@ -35,83 +35,31 @@ services: service: teams-cas-common telemetry-redis: - image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} - command: - - redis-server - - --save - - "60" - - "1" - - --dir - - /data - - --maxmemory - - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} - - --maxmemory-policy - - ${TELEMETRY_REDIS_MAXMEMORY_POLICY:-allkeys-lru} - restart: always - deploy: - resources: - limits: - cpus: "0.25" - memory: 512M - reservations: - cpus: "0.10" - memory: 256M - volumes: - - telemetry-redis-data:/data + extends: + file: ../common-services.yaml + service: telemetry-redis-common fiftyone-app-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} - pid: "service:fiftyone-app" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: fiftyone-app - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: fiftyone-app - TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - deploy: - resources: - limits: - cpus: "0.10" - memory: 512M - reservations: - cpus: "0.10" - memory: 512M + extends: + file: ../common-services.yaml + service: fiftyone-app-telemetry-common depends_on: fiftyone-app: condition: service_started restart: true telemetry-redis: condition: service_started - restart: always teams-api-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} - pid: "service:teams-api" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: teams-api - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: teams-api - TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - deploy: - resources: - limits: - cpus: "0.10" - memory: 512M - reservations: - cpus: "0.10" - memory: 512M + extends: + file: ../common-services.yaml + service: teams-api-telemetry-common depends_on: teams-api: condition: service_started restart: true telemetry-redis: condition: service_started - restart: always volumes: plugins-vol: diff --git a/docker/internal-auth/compose.yaml b/docker/internal-auth/compose.yaml index 0e5656f5e..5ad3967a9 100644 --- a/docker/internal-auth/compose.yaml +++ b/docker/internal-auth/compose.yaml @@ -32,86 +32,31 @@ services: service: teams-cas-common telemetry-redis: - image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} - command: - - redis-server - - --save - - "60" - - "1" - - --dir - - /data - - --maxmemory - - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} - - --maxmemory-policy - - ${TELEMETRY_REDIS_MAXMEMORY_POLICY:-allkeys-lru} - restart: always - deploy: - resources: - limits: - cpus: "0.25" - memory: 512M - reservations: - cpus: "0.10" - memory: 256M - volumes: - - telemetry-redis-data:/data + extends: + file: ../common-services.yaml + service: telemetry-redis-common - # One sidecar per observed service. `pid: "service:"` joins the - # target's PID namespace so the sidecar can read /proc//fd/1 and - # psutil can see the target's process. fiftyone-app-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} - pid: "service:fiftyone-app" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: fiftyone-app - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: fiftyone-app - TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - deploy: - resources: - limits: - cpus: "0.10" - memory: 512M - reservations: - cpus: "0.10" - memory: 512M + extends: + file: ../common-services.yaml + service: fiftyone-app-telemetry-common depends_on: fiftyone-app: condition: service_started restart: true telemetry-redis: condition: service_started - restart: always teams-api-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} - pid: "service:teams-api" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: teams-api - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: teams-api - TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - deploy: - resources: - limits: - cpus: "0.10" - memory: 512M - reservations: - cpus: "0.10" - memory: 512M + extends: + file: ../common-services.yaml + service: teams-api-telemetry-common depends_on: teams-api: condition: service_started restart: true telemetry-redis: condition: service_started - restart: always volumes: telemetry-redis-data: diff --git a/docker/legacy-auth/compose.dedicated-plugins.yaml b/docker/legacy-auth/compose.dedicated-plugins.yaml index e2f44f13a..7e679a4d5 100644 --- a/docker/legacy-auth/compose.dedicated-plugins.yaml +++ b/docker/legacy-auth/compose.dedicated-plugins.yaml @@ -39,110 +39,42 @@ services: - plugins-vol:/opt/plugins:ro telemetry-redis: - image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} - command: - - redis-server - - --save - - "60" - - "1" - - --dir - - /data - - --maxmemory - - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} - - --maxmemory-policy - - ${TELEMETRY_REDIS_MAXMEMORY_POLICY:-allkeys-lru} - restart: always - deploy: - resources: - limits: - cpus: "0.25" - memory: 512M - reservations: - cpus: "0.10" - memory: 256M - volumes: - - telemetry-redis-data:/data + extends: + file: ../common-services.yaml + service: telemetry-redis-common fiftyone-app-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} - pid: "service:fiftyone-app" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: fiftyone-app - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: fiftyone-app - TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - deploy: - resources: - limits: - cpus: "0.10" - memory: 512M - reservations: - cpus: "0.10" - memory: 512M + extends: + file: ../common-services.yaml + service: fiftyone-app-telemetry-common depends_on: fiftyone-app: condition: service_started restart: true telemetry-redis: condition: service_started - restart: always teams-api-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} - pid: "service:teams-api" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: teams-api - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: teams-api - TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - deploy: - resources: - limits: - cpus: "0.10" - memory: 512M - reservations: - cpus: "0.10" - memory: 512M + extends: + file: ../common-services.yaml + service: teams-api-telemetry-common depends_on: teams-api: condition: service_started restart: true telemetry-redis: condition: service_started - restart: always teams-plugins-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} - pid: "service:teams-plugins" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: teams-plugins - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: teams-plugins - TARGET_NAME: ${TEAMS_PLUGINS_TARGET_NAME:-hypercorn} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - deploy: - resources: - limits: - cpus: "0.10" - memory: 512M - reservations: - cpus: "0.10" - memory: 512M + extends: + file: ../common-services.yaml + service: teams-plugins-telemetry-common depends_on: teams-plugins: condition: service_started restart: true telemetry-redis: condition: service_started - restart: always volumes: plugins-vol: diff --git a/docker/legacy-auth/compose.delegated-operators.yaml b/docker/legacy-auth/compose.delegated-operators.yaml index 65d4bd216..5846577e9 100644 --- a/docker/legacy-auth/compose.delegated-operators.yaml +++ b/docker/legacy-auth/compose.delegated-operators.yaml @@ -16,37 +16,15 @@ services: service: teams-do-common teams-do-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} - pid: "service:teams-do" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: teams-do - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: delegated-operator - TARGET_NAME: ${TEAMS_DO_TARGET_NAME:-fiftyone delegated} - EXECUTOR_SIDECAR: "true" - TELEMETRY_SOCKET: /tmp/telemetry/agent.sock - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} - FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} - deploy: - resources: - limits: - cpus: "0.10" - memory: 512M - reservations: - cpus: "0.10" - memory: 512M - volumes: - - telemetry-socket:/tmp/telemetry + extends: + file: ../common-services.yaml + service: teams-do-telemetry-common depends_on: teams-do: condition: service_started restart: true telemetry-redis: condition: service_started - restart: always volumes: plugins-vol: diff --git a/docker/legacy-auth/compose.plugins.yaml b/docker/legacy-auth/compose.plugins.yaml index d7e19a3ec..89794d4c7 100644 --- a/docker/legacy-auth/compose.plugins.yaml +++ b/docker/legacy-auth/compose.plugins.yaml @@ -34,83 +34,31 @@ services: NEXTAUTH_URL: ${AUTH0_BASE_URL}/cas/api/auth telemetry-redis: - image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} - command: - - redis-server - - --save - - "60" - - "1" - - --dir - - /data - - --maxmemory - - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} - - --maxmemory-policy - - ${TELEMETRY_REDIS_MAXMEMORY_POLICY:-allkeys-lru} - restart: always - deploy: - resources: - limits: - cpus: "0.25" - memory: 512M - reservations: - cpus: "0.10" - memory: 256M - volumes: - - telemetry-redis-data:/data + extends: + file: ../common-services.yaml + service: telemetry-redis-common fiftyone-app-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} - pid: "service:fiftyone-app" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: fiftyone-app - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: fiftyone-app - TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - deploy: - resources: - limits: - cpus: "0.10" - memory: 512M - reservations: - cpus: "0.10" - memory: 512M + extends: + file: ../common-services.yaml + service: fiftyone-app-telemetry-common depends_on: fiftyone-app: condition: service_started restart: true telemetry-redis: condition: service_started - restart: always teams-api-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} - pid: "service:teams-api" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: teams-api - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: teams-api - TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - deploy: - resources: - limits: - cpus: "0.10" - memory: 512M - reservations: - cpus: "0.10" - memory: 512M + extends: + file: ../common-services.yaml + service: teams-api-telemetry-common depends_on: teams-api: condition: service_started restart: true telemetry-redis: condition: service_started - restart: always volumes: plugins-vol: diff --git a/docker/legacy-auth/compose.yaml b/docker/legacy-auth/compose.yaml index 64c077a56..ed70bbda4 100644 --- a/docker/legacy-auth/compose.yaml +++ b/docker/legacy-auth/compose.yaml @@ -31,86 +31,31 @@ services: service: teams-cas-common telemetry-redis: - image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} - command: - - redis-server - - --save - - "60" - - "1" - - --dir - - /data - - --maxmemory - - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} - - --maxmemory-policy - - ${TELEMETRY_REDIS_MAXMEMORY_POLICY:-allkeys-lru} - restart: always - deploy: - resources: - limits: - cpus: "0.25" - memory: 512M - reservations: - cpus: "0.10" - memory: 256M - volumes: - - telemetry-redis-data:/data + extends: + file: ../common-services.yaml + service: telemetry-redis-common - # One sidecar per observed service. `pid: "service:"` joins the - # target's PID namespace so the sidecar can read /proc//fd/1 and - # psutil can see the target's process. fiftyone-app-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} - pid: "service:fiftyone-app" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: fiftyone-app - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: fiftyone-app - TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - deploy: - resources: - limits: - cpus: "0.10" - memory: 512M - reservations: - cpus: "0.10" - memory: 512M + extends: + file: ../common-services.yaml + service: fiftyone-app-telemetry-common depends_on: fiftyone-app: condition: service_started restart: true telemetry-redis: condition: service_started - restart: always teams-api-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} - pid: "service:teams-api" - cap_add: - - SYS_PTRACE - environment: - POD_NAME: teams-api - POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} - SERVICE_TYPE: teams-api - TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} - FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} - deploy: - resources: - limits: - cpus: "0.10" - memory: 512M - reservations: - cpus: "0.10" - memory: 512M + extends: + file: ../common-services.yaml + service: teams-api-telemetry-common depends_on: teams-api: condition: service_started restart: true telemetry-redis: condition: service_started - restart: always volumes: telemetry-redis-data: From 1161abfdc57f2a940a9271de3cde9ecaf69b1652 Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Tue, 19 May 2026 22:57:43 -0400 Subject: [PATCH 29/49] fix(compose): default sidecar to GAR image; fix verify-section Redis keys --- docker/common-services.yaml | 8 ++-- docker/docs/configuring-telemetry.md | 45 ++++++++++--------- .../compose.delegated-operators.gpu.yaml | 2 +- docker/internal-auth/env.template | 9 ++-- .../compose.delegated-operators.gpu.yaml | 2 +- docker/legacy-auth/env.template | 9 ++-- .../docker-compose-internal-auth_test.go | 8 ++-- .../docker-compose-legacy-auth_test.go | 8 ++-- 8 files changed, 48 insertions(+), 43 deletions(-) diff --git a/docker/common-services.yaml b/docker/common-services.yaml index 43489fc67..bfed0ba80 100644 --- a/docker/common-services.yaml +++ b/docker/common-services.yaml @@ -225,7 +225,7 @@ services: # target's PID namespace so the sidecar can read /proc//fd/1 and # psutil can see the target's process. fiftyone-app-telemetry-common: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} + image: ${TELEMETRY_SIDECAR_IMAGE:-us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62} pid: "service:fiftyone-app" cap_add: - SYS_PTRACE @@ -246,7 +246,7 @@ services: restart: always teams-api-telemetry-common: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} + image: ${TELEMETRY_SIDECAR_IMAGE:-us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62} pid: "service:teams-api" cap_add: - SYS_PTRACE @@ -267,7 +267,7 @@ services: restart: always teams-plugins-telemetry-common: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} + image: ${TELEMETRY_SIDECAR_IMAGE:-us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62} pid: "service:teams-plugins" cap_add: - SYS_PTRACE @@ -288,7 +288,7 @@ services: restart: always teams-do-telemetry-common: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} + image: ${TELEMETRY_SIDECAR_IMAGE:-us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62} pid: "service:teams-do" cap_add: - SYS_PTRACE diff --git a/docker/docs/configuring-telemetry.md b/docker/docs/configuring-telemetry.md index 898fd736d..ef2f8d755 100644 --- a/docker/docs/configuring-telemetry.md +++ b/docker/docs/configuring-telemetry.md @@ -149,21 +149,24 @@ Kubernetes pod-restart behavior. All knobs live in your `.env` — see `env.template` for the full list: -| Variable | Default | Purpose | -| ---------------------------------- | ----------------------------------- | ---------------------------------------------------- | -| `FIFTYONE_TELEMETRY_REDIS_URL` | `redis://telemetry-redis:6379` | Override to point at an external Redis if desired | -| `TELEMETRY_SIDECAR_IMAGE` | `voxel51/telemetry-sidecar:v0.1.62` | Pinned to match the helm chart's `telemetry.sidecar.image` tag | -| `TELEMETRY_REDIS_IMAGE` | `redis:7-alpine` | Alternate redis image | -| `TELEMETRY_REDIS_MAXMEMORY` | `400mb` | Redis maxmemory budget | -| `TELEMETRY_REDIS_MAXMEMORY_POLICY` | `allkeys-lru` | Redis eviction policy (mirrors helm `telemetry.redis.maxmemoryPolicy`) | -| `TELEMETRY_NAMESPACE` | `docker` | Namespace label attached to each registered pod | -| `FIFTYONE_APP_TARGET_NAME` | `hypercorn` | Substring used to locate the fiftyone-app process | -| `TEAMS_API_TARGET_NAME` | `fiftyone-teams-api` | Substring used to locate the teams-api process | -| `TEAMS_PLUGINS_TARGET_NAME` | `hypercorn` | Substring used to locate the teams-plugins process | -| `TEAMS_DO_TARGET_NAME` | `fiftyone delegated` | Substring used to locate the teams-do process | -| `NVIDIA_GPU_COUNT` | `1` | GPU reservation for the GPU DO worker + sidecar | -| `NVIDIA_VISIBLE_DEVICES` | `all` | Pass-through to teams-do-gpu / sidecar | -| `NVIDIA_DRIVER_CAPABILITIES` | `compute,utility` | Must include `utility` so NVML is available | +| Variable | Default | Purpose | +| ---------------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------- | +| `FIFTYONE_TELEMETRY_REDIS_URL` | `redis://telemetry-redis:6379` | Override to point at an external Redis if desired | +| `TELEMETRY_SIDECAR_IMAGE` | (Voxel51 GAR — see note below) | Same default image as the helm chart. External consumers without GAR access must override. | +| `TELEMETRY_REDIS_IMAGE` | `redis:7-alpine` | Alternate redis image | +| `TELEMETRY_REDIS_MAXMEMORY` | `400mb` | Redis maxmemory budget | +| `TELEMETRY_REDIS_MAXMEMORY_POLICY` | `allkeys-lru` | Redis eviction policy (mirrors helm `telemetry.redis.maxmemoryPolicy`) | +| `TELEMETRY_NAMESPACE` | `docker` | Namespace label attached to each registered pod | +| `FIFTYONE_APP_TARGET_NAME` | `hypercorn` | Substring used to locate the fiftyone-app process | +| `TEAMS_API_TARGET_NAME` | `fiftyone-teams-api` | Substring used to locate the teams-api process | +| `TEAMS_PLUGINS_TARGET_NAME` | `hypercorn` | Substring used to locate the teams-plugins process | +| `TEAMS_DO_TARGET_NAME` | `fiftyone delegated` | Substring used to locate the teams-do process | +| `NVIDIA_GPU_COUNT` | `1` | GPU reservation for the GPU DO worker + sidecar | +| `NVIDIA_VISIBLE_DEVICES` | `all` | Pass-through to teams-do-gpu / sidecar | +| `NVIDIA_DRIVER_CAPABILITIES` | `compute,utility` | Must include `utility` so NVML is available | + +Default `TELEMETRY_SIDECAR_IMAGE`: +`us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62` ## Resource limits @@ -173,10 +176,10 @@ the workloads they observe. The values are declared under each service's `deploy.resources` block in the compose files; compose v2 honors `cpus` and `memory` limits/reservations outside swarm mode. -| Service | CPU limit | Memory limit | Notes | -| -------------------- | --------- | ------------ | ------------------------------------ | -| `telemetry-redis` | `0.25` | `512M` | Reserves `0.10` CPU / `256M` memory | -| `*-telemetry` (any sidecar) | `0.10` | `512M` | Reserved == limit | +| Service | CPU limit | Memory limit | Notes | +| ----------------------------- | --------- | ------------ | ----------------------------------- | +| `telemetry-redis` | `0.25` | `512M` | Reserves `0.10` CPU / `256M` memory | +| `*-telemetry` (any sidecar) | `0.10` | `512M` | Reserved == limit | To tune these for your hardware, override `deploy.resources` in a `compose.override.yaml` (the override merges with the base entry). @@ -190,8 +193,8 @@ the `ADMIN` role. Non-admin users and unauthenticated requests receive 401/403. ## Verify ```shell -docker compose exec telemetry-redis redis-cli HGETALL active_pods -docker compose exec telemetry-redis redis-cli XLEN metrics:docker:fiftyone-app +docker compose exec telemetry-redis redis-cli HGETALL active_targets +docker compose exec telemetry-redis redis-cli XLEN metrics:fiftyone-app ``` `XLEN` should increase over time. If it does not, check the sidecar logs: diff --git a/docker/internal-auth/compose.delegated-operators.gpu.yaml b/docker/internal-auth/compose.delegated-operators.gpu.yaml index 75aba04d8..abb45b486 100644 --- a/docker/internal-auth/compose.delegated-operators.gpu.yaml +++ b/docker/internal-auth/compose.delegated-operators.gpu.yaml @@ -49,7 +49,7 @@ services: restart: always teams-do-gpu-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} + image: ${TELEMETRY_SIDECAR_IMAGE:-us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62} pid: "service:teams-do-gpu" profiles: ["gpu"] cap_add: diff --git a/docker/internal-auth/env.template b/docker/internal-auth/env.template index 7d23a6a2b..9d7436bd9 100644 --- a/docker/internal-auth/env.template +++ b/docker/internal-auth/env.template @@ -112,10 +112,11 @@ FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=3 # Override to point at a managed Redis (e.g. ElastiCache, MemoryStore). # FIFTYONE_TELEMETRY_REDIS_URL= -# Sidecar image. Default pins to v0.1.62 for parity with the helm chart; -# update both compose files and the helm `telemetry.sidecar.image` in -# lockstep when bumping. -# TELEMETRY_SIDECAR_IMAGE=voxel51/telemetry-sidecar:v0.1.62 +# Sidecar image. Default is the internal Voxel51 GAR image — same default +# as the helm chart's `telemetry.sidecar.image`. External consumers +# without GAR access must override this with an image they can pull. +# Update compose and helm in lockstep when bumping the tag. +# TELEMETRY_SIDECAR_IMAGE=us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62 # Redis image, max memory, and eviction policy for the bundled telemetry # backend. The defaults mirror the helm `telemetry.redis.*` settings. diff --git a/docker/legacy-auth/compose.delegated-operators.gpu.yaml b/docker/legacy-auth/compose.delegated-operators.gpu.yaml index 75aba04d8..abb45b486 100644 --- a/docker/legacy-auth/compose.delegated-operators.gpu.yaml +++ b/docker/legacy-auth/compose.delegated-operators.gpu.yaml @@ -49,7 +49,7 @@ services: restart: always teams-do-gpu-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-voxel51/telemetry-sidecar:v0.1.62} + image: ${TELEMETRY_SIDECAR_IMAGE:-us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62} pid: "service:teams-do-gpu" profiles: ["gpu"] cap_add: diff --git a/docker/legacy-auth/env.template b/docker/legacy-auth/env.template index c897e8a8a..f09223f56 100644 --- a/docker/legacy-auth/env.template +++ b/docker/legacy-auth/env.template @@ -117,10 +117,11 @@ FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=3 # Override to point at a managed Redis (e.g. ElastiCache, MemoryStore). # FIFTYONE_TELEMETRY_REDIS_URL= -# Sidecar image. Default pins to v0.1.62 for parity with the helm chart; -# update both compose files and the helm `telemetry.sidecar.image` in -# lockstep when bumping. -# TELEMETRY_SIDECAR_IMAGE=voxel51/telemetry-sidecar:v0.1.62 +# Sidecar image. Default is the internal Voxel51 GAR image — same default +# as the helm chart's `telemetry.sidecar.image`. External consumers +# without GAR access must override this with an image they can pull. +# Update compose and helm in lockstep when bumping the tag. +# TELEMETRY_SIDECAR_IMAGE=us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62 # Redis image, max memory, and eviction policy for the bundled telemetry # backend. The defaults mirror the helm `telemetry.redis.*` settings. diff --git a/tests/unit/compose/docker-compose-internal-auth_test.go b/tests/unit/compose/docker-compose-internal-auth_test.go index 781ef09b8..879ad0c36 100644 --- a/tests/unit/compose/docker-compose-internal-auth_test.go +++ b/tests/unit/compose/docker-compose-internal-auth_test.go @@ -247,14 +247,14 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceImage() { "fiftyone-app-telemetry", []string{internalAuthComposeFile}, s.dotEnvFiles, - "voxel51/telemetry-sidecar:v0.1.62", + "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", }, { "telemetrySidecarTeamsApi", "teams-api-telemetry", []string{internalAuthComposeFile}, s.dotEnvFiles, - "voxel51/telemetry-sidecar:v0.1.62", + "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", }, { "telemetrySidecarTeamsDo", @@ -264,14 +264,14 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceImage() { internalAuthComposeDelegatedOperationsFile, }, s.dotEnvFiles, - "voxel51/telemetry-sidecar:v0.1.62", + "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", }, { "telemetrySidecarTeamsPlugins", "teams-plugins-telemetry", []string{internalAuthComposeDedicatedPluginsFile}, s.dotEnvFiles, - "voxel51/telemetry-sidecar:v0.1.62", + "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", }, } diff --git a/tests/unit/compose/docker-compose-legacy-auth_test.go b/tests/unit/compose/docker-compose-legacy-auth_test.go index 5a8d97dcb..3bf9705a9 100644 --- a/tests/unit/compose/docker-compose-legacy-auth_test.go +++ b/tests/unit/compose/docker-compose-legacy-auth_test.go @@ -247,14 +247,14 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceImage() { "fiftyone-app-telemetry", []string{legacyAuthComposeFile}, s.dotEnvFiles, - "voxel51/telemetry-sidecar:v0.1.62", + "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", }, { "telemetrySidecarTeamsApi", "teams-api-telemetry", []string{legacyAuthComposeFile}, s.dotEnvFiles, - "voxel51/telemetry-sidecar:v0.1.62", + "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", }, { "telemetrySidecarTeamsDo", @@ -264,14 +264,14 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceImage() { legacyAuthComposeDelegatedOperationsFile, }, s.dotEnvFiles, - "voxel51/telemetry-sidecar:v0.1.62", + "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", }, { "telemetrySidecarTeamsPlugins", "teams-plugins-telemetry", []string{legacyAuthComposeDedicatedPluginsFile}, s.dotEnvFiles, - "voxel51/telemetry-sidecar:v0.1.62", + "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", }, } From 703d4c7286cc6fbe9bd35e6cd2119c320ab4e9c0 Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Wed, 20 May 2026 01:39:32 -0400 Subject: [PATCH 30/49] fix(helm): use Recreate strategy for telemetry-redis Deployment The default RollingUpdate strategy spins up a new pod before the old one terminates, which fails with a Multi-Attach error on the RWO PVC. Recreate serializes pod replacement so the volume can detach first. Co-Authored-By: Claude Opus 4.7 (1M context) --- helm/fiftyone-teams-app/templates/telemetry-redis.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/helm/fiftyone-teams-app/templates/telemetry-redis.yaml b/helm/fiftyone-teams-app/templates/telemetry-redis.yaml index e07559479..7771a047c 100644 --- a/helm/fiftyone-teams-app/templates/telemetry-redis.yaml +++ b/helm/fiftyone-teams-app/templates/telemetry-redis.yaml @@ -27,6 +27,8 @@ metadata: {{- include "telemetry.redis.labels" . | nindent 4 }} spec: replicas: 1 + strategy: + type: Recreate selector: matchLabels: {{- include "telemetry.redis.selectorLabels" . | nindent 6 }} From ebd2edb4a33727ad6414c21c6c01e9af92b0981f Mon Sep 17 00:00:00 2001 From: voxelbot Date: Wed, 20 May 2026 13:36:20 +0000 Subject: [PATCH 31/49] ci(fixtures): automated bump for integration test fixtures teams-app:v2.19.0-rc.7 api:v2.19.0rc8 app:v2.19.0rc8 cas:v2.19.0-rc.7 --- .../fixtures/docker/integration_internal_auth.env | 10 +++++----- tests/fixtures/docker/integration_legacy_auth.env | 10 +++++----- tests/fixtures/helm/integration_values.yaml | 14 +++++++------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/fixtures/docker/integration_internal_auth.env b/tests/fixtures/docker/integration_internal_auth.env index d0919314e..ec95cd4b3 100644 --- a/tests/fixtures/docker/integration_internal_auth.env +++ b/tests/fixtures/docker/integration_internal_auth.env @@ -31,10 +31,10 @@ FIFTYONE_ENV=development CAS_DEBUG="cas:*" # Image tags for existing artifacts -VERSION=v2.19.0rc7 -FIFTYONE_APP_VERSION=v2.19.0rc7 -FIFTYONE_TEAMS_API_VERSION=v2.19.0rc7 -FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.6 -FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.6 +VERSION=v2.19.0rc8 +FIFTYONE_APP_VERSION=v2.19.0rc8 +FIFTYONE_TEAMS_API_VERSION=v2.19.0rc8 +FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.7 +FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.7 FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=1 diff --git a/tests/fixtures/docker/integration_legacy_auth.env b/tests/fixtures/docker/integration_legacy_auth.env index e833b77b8..215a28e07 100644 --- a/tests/fixtures/docker/integration_legacy_auth.env +++ b/tests/fixtures/docker/integration_legacy_auth.env @@ -39,10 +39,10 @@ FIFTYONE_ENV=development CAS_DEBUG="cas:*" # Image tags for existing artifacts -VERSION=v2.19.0rc7 -FIFTYONE_APP_VERSION=v2.19.0rc7 -FIFTYONE_TEAMS_API_VERSION=v2.19.0rc7 -FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.6 -FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.6 +VERSION=v2.19.0rc8 +FIFTYONE_APP_VERSION=v2.19.0rc8 +FIFTYONE_TEAMS_API_VERSION=v2.19.0rc8 +FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.7 +FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.7 FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=1 diff --git a/tests/fixtures/helm/integration_values.yaml b/tests/fixtures/helm/integration_values.yaml index 2c6973f65..cf82402cd 100644 --- a/tests/fixtures/helm/integration_values.yaml +++ b/tests/fixtures/helm/integration_values.yaml @@ -9,7 +9,7 @@ apiSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-teams-api pullPolicy: IfNotPresent - tag: v2.19.0rc7 + tag: v2.19.0rc8 service: liveness: initialDelaySeconds: 15 @@ -27,7 +27,7 @@ appSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc7 + tag: v2.19.0rc8 # TODO: Test `minikube addons configure registry-creds` or # When using minikube's addon registry-creds, we may also need to create the k8s secret `regcred` # See https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ @@ -42,21 +42,21 @@ casSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-teams-cas pullPolicy: IfNotPresent - tag: v2.19.0-rc.6 + tag: v2.19.0-rc.7 delegatedOperatorDeployments: deployments: teamsDoCpuDefault: image: repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc7 + tag: v2.19.0rc8 delegatedOperatorTemplates: jobs: teamsDoCpuDefaultK8s: image: repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc7 + tag: v2.19.0rc8 ingress: annotations: # For using the nginx-ingress controller with cert-manager self signed certificates @@ -121,7 +121,7 @@ pluginsSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc7 + tag: v2.19.0rc8 teamsAppSettings: dnsName: local.fiftyone.ai # env: @@ -138,4 +138,4 @@ teamsAppSettings: # The others are `vW.X.Y.devZ` (note `.devZ` vs `-dev.Z`). # This is a byproduct of `npm` versioning versus Python PEP 440. pullPolicy: IfNotPresent - tag: v2.19.0-rc.6 + tag: v2.19.0-rc.7 From b4afaafdf1d9201fbedb527b3c1f2115a9624328 Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Wed, 20 May 2026 16:03:44 -0400 Subject: [PATCH 32/49] refactor: align telemetry-sidecar image versioning with other voxel51 images Tie the sidecar tag to the chart appVersion via the same `repository`/`tag` split used by `appSettings.image` et al., and wire it into the existing bump-fixtures helm script so releases bump it in lockstep with the rest. Co-Authored-By: Claude Opus 4.7 (1M context) --- docker/common-services.yaml | 8 ++-- docker/docs/configuring-telemetry.md | 4 -- .../compose.delegated-operators.gpu.yaml | 2 +- docker/internal-auth/env.template | 6 --- .../compose.delegated-operators.gpu.yaml | 2 +- docker/legacy-auth/env.template | 6 --- helm/fiftyone-teams-app/README.md | 5 ++- .../templates/_telemetry.tpl | 8 ++-- helm/fiftyone-teams-app/values.schema.json | 41 +++++++++++++++---- helm/fiftyone-teams-app/values.yaml | 19 +++++---- .../docker/compose.override.mongodb.yaml | 4 ++ .../docker/compose.override.mongodb_do.yaml | 2 + .../compose.override.mongodb_plugins.yaml | 2 + tests/fixtures/docker/compose.override.yaml | 22 ---------- tests/fixtures/helm/integration_values.yaml | 6 +++ .../docker-compose-internal-auth_test.go | 8 ++-- .../docker-compose-legacy-auth_test.go | 8 ++-- utils/bump-fixtures-helm.sh | 4 ++ utils/validate-docker-pulls.sh | 22 ++-------- 19 files changed, 86 insertions(+), 93 deletions(-) diff --git a/docker/common-services.yaml b/docker/common-services.yaml index bfed0ba80..7b122d119 100644 --- a/docker/common-services.yaml +++ b/docker/common-services.yaml @@ -225,7 +225,7 @@ services: # target's PID namespace so the sidecar can read /proc//fd/1 and # psutil can see the target's process. fiftyone-app-telemetry-common: - image: ${TELEMETRY_SIDECAR_IMAGE:-us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62} + image: voxel51/telemetry-sidecar:v2.19.0 pid: "service:fiftyone-app" cap_add: - SYS_PTRACE @@ -246,7 +246,7 @@ services: restart: always teams-api-telemetry-common: - image: ${TELEMETRY_SIDECAR_IMAGE:-us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62} + image: voxel51/telemetry-sidecar:v2.19.0 pid: "service:teams-api" cap_add: - SYS_PTRACE @@ -267,7 +267,7 @@ services: restart: always teams-plugins-telemetry-common: - image: ${TELEMETRY_SIDECAR_IMAGE:-us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62} + image: voxel51/telemetry-sidecar:v2.19.0 pid: "service:teams-plugins" cap_add: - SYS_PTRACE @@ -288,7 +288,7 @@ services: restart: always teams-do-telemetry-common: - image: ${TELEMETRY_SIDECAR_IMAGE:-us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62} + image: voxel51/telemetry-sidecar:v2.19.0 pid: "service:teams-do" cap_add: - SYS_PTRACE diff --git a/docker/docs/configuring-telemetry.md b/docker/docs/configuring-telemetry.md index ef2f8d755..33f351add 100644 --- a/docker/docs/configuring-telemetry.md +++ b/docker/docs/configuring-telemetry.md @@ -152,7 +152,6 @@ All knobs live in your `.env` — see `env.template` for the full list: | Variable | Default | Purpose | | ---------------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------- | | `FIFTYONE_TELEMETRY_REDIS_URL` | `redis://telemetry-redis:6379` | Override to point at an external Redis if desired | -| `TELEMETRY_SIDECAR_IMAGE` | (Voxel51 GAR — see note below) | Same default image as the helm chart. External consumers without GAR access must override. | | `TELEMETRY_REDIS_IMAGE` | `redis:7-alpine` | Alternate redis image | | `TELEMETRY_REDIS_MAXMEMORY` | `400mb` | Redis maxmemory budget | | `TELEMETRY_REDIS_MAXMEMORY_POLICY` | `allkeys-lru` | Redis eviction policy (mirrors helm `telemetry.redis.maxmemoryPolicy`) | @@ -165,9 +164,6 @@ All knobs live in your `.env` — see `env.template` for the full list: | `NVIDIA_VISIBLE_DEVICES` | `all` | Pass-through to teams-do-gpu / sidecar | | `NVIDIA_DRIVER_CAPABILITIES` | `compute,utility` | Must include `utility` so NVML is available | -Default `TELEMETRY_SIDECAR_IMAGE`: -`us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62` - ## Resource limits Telemetry containers ship with conservative CPU and memory limits that diff --git a/docker/internal-auth/compose.delegated-operators.gpu.yaml b/docker/internal-auth/compose.delegated-operators.gpu.yaml index abb45b486..b7128de58 100644 --- a/docker/internal-auth/compose.delegated-operators.gpu.yaml +++ b/docker/internal-auth/compose.delegated-operators.gpu.yaml @@ -49,7 +49,7 @@ services: restart: always teams-do-gpu-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62} + image: voxel51/telemetry-sidecar:v2.19.0 pid: "service:teams-do-gpu" profiles: ["gpu"] cap_add: diff --git a/docker/internal-auth/env.template b/docker/internal-auth/env.template index 9d7436bd9..5760196a9 100644 --- a/docker/internal-auth/env.template +++ b/docker/internal-auth/env.template @@ -112,12 +112,6 @@ FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=3 # Override to point at a managed Redis (e.g. ElastiCache, MemoryStore). # FIFTYONE_TELEMETRY_REDIS_URL= -# Sidecar image. Default is the internal Voxel51 GAR image — same default -# as the helm chart's `telemetry.sidecar.image`. External consumers -# without GAR access must override this with an image they can pull. -# Update compose and helm in lockstep when bumping the tag. -# TELEMETRY_SIDECAR_IMAGE=us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62 - # Redis image, max memory, and eviction policy for the bundled telemetry # backend. The defaults mirror the helm `telemetry.redis.*` settings. # TELEMETRY_REDIS_IMAGE=redis:7-alpine diff --git a/docker/legacy-auth/compose.delegated-operators.gpu.yaml b/docker/legacy-auth/compose.delegated-operators.gpu.yaml index abb45b486..b7128de58 100644 --- a/docker/legacy-auth/compose.delegated-operators.gpu.yaml +++ b/docker/legacy-auth/compose.delegated-operators.gpu.yaml @@ -49,7 +49,7 @@ services: restart: always teams-do-gpu-telemetry: - image: ${TELEMETRY_SIDECAR_IMAGE:-us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62} + image: voxel51/telemetry-sidecar:v2.19.0 pid: "service:teams-do-gpu" profiles: ["gpu"] cap_add: diff --git a/docker/legacy-auth/env.template b/docker/legacy-auth/env.template index f09223f56..49af6ec57 100644 --- a/docker/legacy-auth/env.template +++ b/docker/legacy-auth/env.template @@ -117,12 +117,6 @@ FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=3 # Override to point at a managed Redis (e.g. ElastiCache, MemoryStore). # FIFTYONE_TELEMETRY_REDIS_URL= -# Sidecar image. Default is the internal Voxel51 GAR image — same default -# as the helm chart's `telemetry.sidecar.image`. External consumers -# without GAR access must override this with an image they can pull. -# Update compose and helm in lockstep when bumping the tag. -# TELEMETRY_SIDECAR_IMAGE=us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62 - # Redis image, max memory, and eviction policy for the bundled telemetry # backend. The defaults mirror the helm `telemetry.redis.*` settings. # TELEMETRY_REDIS_IMAGE=redis:7-alpine diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index dd8ed9fdd..26a57ad67 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -1105,8 +1105,9 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | telemetry.redis.persistence.storageClass | string | `""` | `StorageClass` name for the telemetry Redis `PersistentVolumeClaim`. Leave unset to use the cluster's default `StorageClass`. | | telemetry.redis.resources | object | `{"limits":{"cpu":"250m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"256Mi"}}` | Resource requests/limits for the telemetry Redis container. [Reference][resources]. | | telemetry.serviceAccounts | list | `[]` | ServiceAccount names (in `namespace.name`) bound to the telemetry pod-logs Role. Each entry becomes a `ServiceAccount` subject on the generated RoleBinding. When empty, the RoleBinding subjects default to the chart's main app service account and the teams-api RBAC service account so auto-injected sidecars can read their target's logs. | -| telemetry.sidecar.image | string | `"us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62"` | Container image for the auto-injected `telemetry-sidecar` containers. The image is built and published from voxel51/fiftyone-teams `sidecar/` to the internal Voxel51 GAR. Override for environments without access to that registry (e.g. external chart consumers). | -| telemetry.sidecar.imagePullPolicy | string | `"Always"` | Pull policy for the sidecar image. | +| telemetry.sidecar.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | +| telemetry.sidecar.image.repository | string | `"voxel51/telemetry-sidecar"` | Container image for `telemetry-sidecar`. | +| telemetry.sidecar.image.tag | string | `""` | Image tag for `telemetry-sidecar`. Defaults to the chart version. | | telemetry.sidecar.resources | object | `{"limits":{"cpu":"100m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"512Mi"}}` | Resource requests/limits for each auto-injected sidecar container. | diff --git a/helm/fiftyone-teams-app/templates/_telemetry.tpl b/helm/fiftyone-teams-app/templates/_telemetry.tpl index b55046c33..4a917a0f7 100644 --- a/helm/fiftyone-teams-app/templates/_telemetry.tpl +++ b/helm/fiftyone-teams-app/templates/_telemetry.tpl @@ -129,8 +129,8 @@ Inputs: same dict as telemetry.sidecar-env. */}} {{- define "telemetry.sidecar" -}} - name: telemetry-sidecar - image: {{ .ctx.Values.telemetry.sidecar.image | default "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62" | quote }} - imagePullPolicy: {{ .ctx.Values.telemetry.sidecar.imagePullPolicy | default "Always" }} + image: "{{ .ctx.Values.telemetry.sidecar.image.repository }}:{{ .ctx.Values.telemetry.sidecar.image.tag | default .ctx.Chart.AppVersion }}" + imagePullPolicy: {{ .ctx.Values.telemetry.sidecar.image.pullPolicy | default "Always" }} env: {{- include "telemetry.sidecar-env" . | nindent 4 }} {{- with .ctx.Values.telemetry.sidecar.resources }} @@ -160,8 +160,8 @@ not exit would block Job completion. */}} {{- define "telemetry.native-sidecar" -}} - name: telemetry-sidecar - image: {{ .ctx.Values.telemetry.sidecar.image | default "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62" | quote }} - imagePullPolicy: {{ .ctx.Values.telemetry.sidecar.imagePullPolicy | default "Always" }} + image: "{{ .ctx.Values.telemetry.sidecar.image.repository }}:{{ .ctx.Values.telemetry.sidecar.image.tag | default .ctx.Chart.AppVersion }}" + imagePullPolicy: {{ .ctx.Values.telemetry.sidecar.image.pullPolicy | default "Always" }} restartPolicy: Always env: {{- include "telemetry.sidecar-env" . | nindent 4 }} diff --git a/helm/fiftyone-teams-app/values.schema.json b/helm/fiftyone-teams-app/values.schema.json index 08748895a..3da844e71 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -4139,16 +4139,39 @@ "sidecar": { "properties": { "image": { - "default": "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", - "description": "Container image for the auto-injected `telemetry-sidecar` containers.\nThe image is built and published from voxel51/fiftyone-teams `sidecar/`\nto the internal Voxel51 GAR. Override for environments without access\nto that registry (e.g. external chart consumers).", + "properties": { + "pullPolicy": { + "default": "Always", + "description": "Instruct when the kubelet should pull (download) the specified image.\nOne of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy].", + "oneOf": [ + { + "enum": [ + "Always", + "IfNotPresent", + "Never" + ], + "required": [] + } + ], + "required": [], + "title": "pullPolicy" + }, + "repository": { + "default": "voxel51/telemetry-sidecar", + "description": "Container image for `telemetry-sidecar`.", + "title": "repository", + "type": "string" + }, + "tag": { + "default": "", + "description": "Image tag for `telemetry-sidecar`. Defaults to the chart version.", + "title": "tag", + "type": "string" + } + }, + "required": [], "title": "image", - "type": "string" - }, - "imagePullPolicy": { - "default": "Always", - "description": "Pull policy for the sidecar image.", - "title": "imagePullPolicy", - "type": "string" + "type": "object" }, "resources": { "description": "Resource requests/limits for each auto-injected sidecar container.", diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index a926f0997..d11b05fae 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -1697,13 +1697,18 @@ telemetry: # (e.g. `redis://my-redis.example.com:6379`). url: "" sidecar: - # -- Container image for the auto-injected `telemetry-sidecar` containers. - # The image is built and published from voxel51/fiftyone-teams `sidecar/` - # to the internal Voxel51 GAR. Override for environments without access - # to that registry (e.g. external chart consumers). - image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62 - # -- Pull policy for the sidecar image. - imagePullPolicy: Always + image: + # @schema + # oneOf: + # - enum: ["Always", "IfNotPresent", "Never"] + # @schema + # -- Instruct when the kubelet should pull (download) the specified image. + # One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. + pullPolicy: Always + # -- Container image for `telemetry-sidecar`. + repository: voxel51/telemetry-sidecar + # -- Image tag for `telemetry-sidecar`. Defaults to the chart version. + tag: "" # -- Resource requests/limits for each auto-injected sidecar container. resources: requests: diff --git a/tests/fixtures/docker/compose.override.mongodb.yaml b/tests/fixtures/docker/compose.override.mongodb.yaml index 406789402..a853438f1 100644 --- a/tests/fixtures/docker/compose.override.mongodb.yaml +++ b/tests/fixtures/docker/compose.override.mongodb.yaml @@ -11,6 +11,10 @@ services: teams-cas: depends_on: [mongodb] image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-teams-cas:${FIFTYONE_TEAMS_CAS_VERSION} + fiftyone-app-telemetry: + image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/telemetry-sidecar:${FIFTYONE_APP_VERSION} + teams-api-telemetry: + image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/telemetry-sidecar:${FIFTYONE_APP_VERSION} mongodb: image: "mongo:8.0.0" restart: always diff --git a/tests/fixtures/docker/compose.override.mongodb_do.yaml b/tests/fixtures/docker/compose.override.mongodb_do.yaml index a9d83928d..76770b55f 100644 --- a/tests/fixtures/docker/compose.override.mongodb_do.yaml +++ b/tests/fixtures/docker/compose.override.mongodb_do.yaml @@ -2,3 +2,5 @@ services: teams-do: depends_on: [mongodb] image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app:${FIFTYONE_APP_VERSION} + teams-do-telemetry: + image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/telemetry-sidecar:${FIFTYONE_APP_VERSION} diff --git a/tests/fixtures/docker/compose.override.mongodb_plugins.yaml b/tests/fixtures/docker/compose.override.mongodb_plugins.yaml index 0e28fbd06..c4318c2f8 100644 --- a/tests/fixtures/docker/compose.override.mongodb_plugins.yaml +++ b/tests/fixtures/docker/compose.override.mongodb_plugins.yaml @@ -2,3 +2,5 @@ services: teams-plugins: depends_on: [mongodb] image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app:${FIFTYONE_APP_VERSION} + teams-plugins-telemetry: + image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/telemetry-sidecar:${FIFTYONE_APP_VERSION} diff --git a/tests/fixtures/docker/compose.override.yaml b/tests/fixtures/docker/compose.override.yaml index d1b1f4cdd..7b1bcfccc 100644 --- a/tests/fixtures/docker/compose.override.yaml +++ b/tests/fixtures/docker/compose.override.yaml @@ -2,25 +2,3 @@ services: fiftyone-app: environment: FIFTYONE_DATABASE_ADMIN: false - - # Integration tests don't have access to the private - # `voxel51/telemetry-sidecar` image. Replace each sidecar with a - # public idle alpine container so the stack still composes correctly - # but no private-registry pull is required. Sidecars are not exercised - # by these tests — only the main services are. - fiftyone-app-telemetry: - image: alpine:3.20 - command: ["tail", "-f", "/dev/null"] - pull_policy: missing - teams-api-telemetry: - image: alpine:3.20 - command: ["tail", "-f", "/dev/null"] - pull_policy: missing - teams-plugins-telemetry: - image: alpine:3.20 - command: ["tail", "-f", "/dev/null"] - pull_policy: missing - teams-do-telemetry: - image: alpine:3.20 - command: ["tail", "-f", "/dev/null"] - pull_policy: missing diff --git a/tests/fixtures/helm/integration_values.yaml b/tests/fixtures/helm/integration_values.yaml index cf82402cd..e2534840b 100644 --- a/tests/fixtures/helm/integration_values.yaml +++ b/tests/fixtures/helm/integration_values.yaml @@ -122,6 +122,12 @@ pluginsSettings: repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent tag: v2.19.0rc8 +telemetry: + sidecar: + image: + repository: voxel51/telemetry-sidecar + pullPolicy: IfNotPresent + tag: v2.19.0rc8 teamsAppSettings: dnsName: local.fiftyone.ai # env: diff --git a/tests/unit/compose/docker-compose-internal-auth_test.go b/tests/unit/compose/docker-compose-internal-auth_test.go index 879ad0c36..81c20c314 100644 --- a/tests/unit/compose/docker-compose-internal-auth_test.go +++ b/tests/unit/compose/docker-compose-internal-auth_test.go @@ -247,14 +247,14 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceImage() { "fiftyone-app-telemetry", []string{internalAuthComposeFile}, s.dotEnvFiles, - "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", + "voxel51/telemetry-sidecar:v2.19.0", }, { "telemetrySidecarTeamsApi", "teams-api-telemetry", []string{internalAuthComposeFile}, s.dotEnvFiles, - "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", + "voxel51/telemetry-sidecar:v2.19.0", }, { "telemetrySidecarTeamsDo", @@ -264,14 +264,14 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceImage() { internalAuthComposeDelegatedOperationsFile, }, s.dotEnvFiles, - "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", + "voxel51/telemetry-sidecar:v2.19.0", }, { "telemetrySidecarTeamsPlugins", "teams-plugins-telemetry", []string{internalAuthComposeDedicatedPluginsFile}, s.dotEnvFiles, - "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", + "voxel51/telemetry-sidecar:v2.19.0", }, } diff --git a/tests/unit/compose/docker-compose-legacy-auth_test.go b/tests/unit/compose/docker-compose-legacy-auth_test.go index 3bf9705a9..375559b2c 100644 --- a/tests/unit/compose/docker-compose-legacy-auth_test.go +++ b/tests/unit/compose/docker-compose-legacy-auth_test.go @@ -247,14 +247,14 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceImage() { "fiftyone-app-telemetry", []string{legacyAuthComposeFile}, s.dotEnvFiles, - "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", + "voxel51/telemetry-sidecar:v2.19.0", }, { "telemetrySidecarTeamsApi", "teams-api-telemetry", []string{legacyAuthComposeFile}, s.dotEnvFiles, - "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", + "voxel51/telemetry-sidecar:v2.19.0", }, { "telemetrySidecarTeamsDo", @@ -264,14 +264,14 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceImage() { legacyAuthComposeDelegatedOperationsFile, }, s.dotEnvFiles, - "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", + "voxel51/telemetry-sidecar:v2.19.0", }, { "telemetrySidecarTeamsPlugins", "teams-plugins-telemetry", []string{legacyAuthComposeDedicatedPluginsFile}, s.dotEnvFiles, - "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62", + "voxel51/telemetry-sidecar:v2.19.0", }, } diff --git a/utils/bump-fixtures-helm.sh b/utils/bump-fixtures-helm.sh index 9e0eed242..90db16d30 100755 --- a/utils/bump-fixtures-helm.sh +++ b/utils/bump-fixtures-helm.sh @@ -139,6 +139,10 @@ if yq -e ".delegatedOperatorTemplates.jobs" "${file}" >/dev/null; then yq "$yq_flags" ".delegatedOperatorTemplates.jobs.*.image.tag = \"${FIFTYONE_APP_VERSION}\"" "${file}" fi +if yq -e ".telemetry.sidecar.image" "${file}" >/dev/null; then + yq "$yq_flags" ".telemetry.sidecar.image.tag = \"${FIFTYONE_APP_VERSION}\"" "${file}" +fi + # Remove temporary file if dry-run if [[ $DRY_RUN == "true" ]]; then cat "${file}" diff --git a/utils/validate-docker-pulls.sh b/utils/validate-docker-pulls.sh index 40e7d8e20..73da62b3c 100755 --- a/utils/validate-docker-pulls.sh +++ b/utils/validate-docker-pulls.sh @@ -26,21 +26,7 @@ EXPECTED_IMAGES=( "voxel51/fiftyone-teams-app" "voxel51/fiftyone-teams-cas" "voxel51/fiftyone-teams-cv-full" -) - -# Images that the chart references by default but that are NOT publicly -# pullable. They bypass the docker_pull validation step but still count -# as "expected" for the in-helm-template check (so their presence doesn't -# trip the "in helm template but not in our published images" guard). -# Add the explicit `:tag` here — no auto-tag. -# -# Move entries OUT of this list once the corresponding image has a -# publicly-pullable tag (e.g. once the telemetry sidecar starts -# publishing to Docker Hub instead of the private Voxel51 GAR). -UNPUBLISHED_IMAGES=( - # Built from voxel51/fiftyone-teams `sidecar/`, published to the - # internal Voxel51 GAR. Requires GCP auth to pull. - "us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-telemetry-sidecar:v0.1.62" + "voxel51/telemetry-sidecar" ) . "$(dirname "$0")/common.sh" @@ -120,7 +106,7 @@ expected_tag=$(yq '.appVersion' "${GIT_ROOT}/helm/fiftyone-teams-app/Chart.yaml" for img in "${EXPECTED_IMAGES[@]}"; do if [[ ${img} == *":"* ]]; then - # Image already carries an explicit tag (e.g. telemetry-sidecar:latest); + # Image already carries an explicit tag (e.g. redis:7-alpine); # use as-is. img_with_tag="${img}" elif [[ ${img} =~ "voxel51/" ]]; then @@ -144,9 +130,7 @@ helm_images=$( read -r -a helm_images_array <<<"${helm_images}" -# Images that are allowed in the helm template but skip docker_pull -# validation (e.g. not yet published to a public registry). -known_images_for_template_check=("${expected_images_with_tag[@]}" "${UNPUBLISHED_IMAGES[@]}") +known_images_for_template_check=("${expected_images_with_tag[@]}") pids=() rcs=() From 079c65a3feac57f14cd95f75a6f9df9c48fff528 Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Wed, 20 May 2026 16:42:39 -0400 Subject: [PATCH 33/49] refactor(helm): drop unused extraContainers/nativeSidecarContainers knobs These knobs were originally added as the consumer-supplied injection point for the telemetry sidecar (commits c2c9025, b0b39bd). Once telemetry switched to auto-injection (d35aa48) nothing in the chart populated them and the defaults have been `[]` since. Remove the values, template plumbing, and the now-orphaned native-sidecar merge logic in the DO Job ConfigMap. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../configuring-kubernetes-orchestrator.md | 11 +- helm/fiftyone-teams-app/README.md | 21 +-- helm/fiftyone-teams-app/README.md.gotmpl | 1 - .../templates/_telemetry.tpl | 4 +- .../templates/api-deployment.yaml | 12 +- .../templates/app-deployment.yaml | 12 +- ...elegated-operator-instance-deployment.yaml | 9 +- .../delegated-operator-job-configmap.yaml | 56 +------ .../templates/plugins-deployment.yaml | 12 +- helm/fiftyone-teams-app/values.schema.json | 90 +---------- helm/fiftyone-teams-app/values.yaml | 62 +------- .../delegated-operator-job-configmap_test.go | 144 ------------------ 12 files changed, 34 insertions(+), 400 deletions(-) diff --git a/docs/orchestrators/configuring-kubernetes-orchestrator.md b/docs/orchestrators/configuring-kubernetes-orchestrator.md index 8e614d5bb..b0eb34372 100644 --- a/docs/orchestrators/configuring-kubernetes-orchestrator.md +++ b/docs/orchestrators/configuring-kubernetes-orchestrator.md @@ -462,12 +462,11 @@ fom.register_orchestrator( ## Optional: Telemetry Sidecar -If your deployment runs telemetry (see the helm chart's -`extraContainers` / `nativeSidecarContainers` options, or the docker -compose files where it is bundled by default), you can attach a per-Job -telemetry sidecar to on-demand Kubernetes orchestrators as well. This -emits per-operation metrics back to the same Redis backend so the -Settings → Metrics page sees individual delegated runs. +If your deployment runs telemetry (the helm chart and docker compose +files include it by default), you can attach a per-Job telemetry sidecar +to on-demand Kubernetes orchestrators as well. This emits per-operation +metrics back to the same Redis backend so the Settings → Metrics page +sees individual delegated runs. Use a Kubernetes [native sidecar](https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/) diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 26a57ad67..c4f392412 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -728,7 +728,6 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | apiSettings.env.FIFTYONE_LOGGING_FORMAT | string | `"text"` | The format to use for log messages. Can be one of `json` or `text`. | | apiSettings.env.GRAPHQL_DEFAULT_LIMIT | int | `10` | Default number of returned items when listing in GraphQL queries. Can be overridden in the request. | | apiSettings.env.LOGGING_LEVEL | string | `"INFO"` | Logging level. Overrides the value of `FIFTYONE_ENV`. Can be one of "DEBUG", "INFO", "WARN", "ERROR", or "CRITICAL". | -| apiSettings.extraContainers | list | `[]` | Additional containers to run in the `teams-api` pod alongside the main container. When non-empty, the pod is configured with `shareProcessNamespace: true`. [Reference][sidecar-containers]. | | apiSettings.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | | apiSettings.image.repository | string | `"voxel51/fiftyone-teams-api"` | Container image for the `teams-api`. | | apiSettings.image.tag | string | `""` | Image tag for `teams-api`. Defaults to the chart version. | @@ -741,7 +740,6 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | apiSettings.liveness.failureThreshold | int | `5` | Number of times to retry the liveness probe for the `teams-api`. [Reference][probes]. | | apiSettings.liveness.periodSeconds | int | `15` | How often (in seconds) to perform the liveness probe for `teams-api`. [Reference][probes]. | | apiSettings.liveness.timeoutSeconds | int | `5` | Number of seconds after which the liveness probe times out for the `teams-api`. [Reference][probes]. | -| apiSettings.nativeSidecarContainers | list | `[]` | Native sidecar containers for the `teams-api` pod (Kubernetes 1.29+). Entries are rendered under `initContainers`; set `restartPolicy: Always` on each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers]. | | apiSettings.nodeSelector | object | `{}` | nodeSelector for `teams-api`. [Reference][node-selector]. | | apiSettings.podAnnotations | object | `{}` | Annotations for pods for `teams-api`. [Reference][annotations]. | | apiSettings.podDisruptionBudget | object | `{"enabled":false,"minAvailable":null}` | Pod Disruption Budget for pods for `teams-api`. [Reference][pod-disruption-budget]. | @@ -797,7 +795,6 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | appSettings.env.FIFTYONE_MEDIA_CACHE_APP_IMAGES | bool | `false` | Controls whether cloud media images will be downloaded and added to the local cache upon viewing media in the app. | | appSettings.env.FIFTYONE_MEDIA_CACHE_SIZE_BYTES | int | `-1` | Set the media cache size (in bytes) for the local FiftyOne App processes. When not set, the app's default value is 32 GiB. `-1` disables the media cache garbage collection (and cache size is unlimited). | | appSettings.env.FIFTYONE_SIGNED_URL_EXPIRATION | int | `24` | Set the time-to-live for signed URLs generated by the application in hours | -| appSettings.extraContainers | list | `[]` | Additional containers to run in the `fiftyone-app` pod alongside the main container. When non-empty, the pod is configured with `shareProcessNamespace: true`. [Reference][sidecar-containers]. | | appSettings.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | | appSettings.image.repository | string | `"voxel51/fiftyone-app"` | Container image for `fiftyone-app`. | | appSettings.image.tag | string | `""` | Image tag for `fiftyone-app`. Defaults to the chart version. | @@ -810,7 +807,6 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | appSettings.liveness.failureThreshold | int | `5` | Number of times to retry the liveness probe for the `fiftyone-app`. [Reference][probes]. | | appSettings.liveness.periodSeconds | int | `15` | How often (in seconds) to perform the liveness probe for `fiftyone-app`. [Reference][probes]. | | appSettings.liveness.timeoutSeconds | int | `5` | Timeout for the liveness probe for the `fiftyone-app`. [Reference][probes]. | -| appSettings.nativeSidecarContainers | list | `[]` | Native sidecar containers for the `fiftyone-app` pod (Kubernetes 1.29+). Entries are rendered under `initContainers`; set `restartPolicy: Always` on each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers]. | | appSettings.nodeSelector | object | `{}` | nodeSelector for `fiftyone-app`. [Reference][node-selector]. | | appSettings.podAnnotations | object | `{}` | Annotations for pods for `fiftyone-app`. [Reference][annotations]. | | appSettings.podDisruptionBudget | object | `{"enabled":false,"minAvailable":null}` | Pod Disruption Budget for pods for `fiftyone-app`. [Reference][pod-disruption-budget]. | @@ -889,14 +885,13 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | casSettings.volumes | list | `[]` | Volumes for `teams-cas`. [Reference][volumes]. | | delegatedOperatorDeployments.deployments | object | `{"teamsDoCpuDefault":{"enabled":false,"env":{"FIFTYONE_MEDIA_CACHE_DIR":"/opt/media_cache","FIFTYONE_MEDIA_CACHE_SIZE_BYTES":"2147483648"},"replicaCount":1,"resources":{"limits":{"cpu":4,"ephemeral-storage":"1Gi","memory":"16Gi"},"requests":{"cpu":4,"ephemeral-storage":"1Gi","memory":"16Gi"}},"volumeMounts":[{"mountPath":"/dev/shm","name":"shm-vol"},{"mountPath":"/opt/media_cache","name":"memory-media-cache-vol"}],"volumes":[{"emptyDir":{"medium":"Memory","sizeLimit":"2Gi"},"name":"shm-vol"},{"emptyDir":{"medium":"Memory","sizeLimit":"2.5Gi"},"name":"memory-media-cache-vol"}]}}` | Additional deployments to configure. Each template will use .Values.delegatedOperatorDeployments.template as a base. Each template value may be overridden. Maps/dictionaries will be merged key-wise, with the deployment instance taking precedence. List values will not be merged, but be overridden completely by the deployment instance. | | delegatedOperatorDeployments.deployments.teamsDoCpuDefault | object | `{"enabled":false,"env":{"FIFTYONE_MEDIA_CACHE_DIR":"/opt/media_cache","FIFTYONE_MEDIA_CACHE_SIZE_BYTES":"2147483648"},"replicaCount":1,"resources":{"limits":{"cpu":4,"ephemeral-storage":"1Gi","memory":"16Gi"},"requests":{"cpu":4,"ephemeral-storage":"1Gi","memory":"16Gi"}},"volumeMounts":[{"mountPath":"/dev/shm","name":"shm-vol"},{"mountPath":"/opt/media_cache","name":"memory-media-cache-vol"}],"volumes":[{"emptyDir":{"medium":"Memory","sizeLimit":"2Gi"},"name":"shm-vol"},{"emptyDir":{"medium":"Memory","sizeLimit":"2.5Gi"},"name":"memory-media-cache-vol"}]}` | Default (CPU-only) delegated operator runner. Defaults to an 4vCPU, 16Gi RAM runner. The deployment is backed with a 2.5Gi in-memory volume for caching media and an extra 2Gi of shared memory. | -| delegatedOperatorDeployments.template | object | `{"affinity":{},"deploymentAnnotations":{},"description":"","env":{"FIFTYONE_DELEGATED_OPERATION_LOG_PATH":"","FIFTYONE_INTERNAL_SERVICE":true,"FIFTYONE_MEDIA_CACHE_SIZE_BYTES":-1},"extraContainers":[],"image":{"pullPolicy":"Always","repository":"voxel51/fiftyone-teams-cv-full","tag":""},"labels":{},"liveness":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"nativeSidecarContainers":[],"nodeSelector":{},"podAnnotations":{},"podDisruptionBudget":{"enabled":false,"minAvailable":null},"podSecurityContext":{},"readiness":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"replicaCount":3,"resources":{"limits":{},"requests":{}},"secretEnv":{},"securityContext":{},"startup":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"tolerations":[],"topologySpreadConstraints":[],"updateStrategy":{"type":"RollingUpdate"},"volumeMounts":[],"volumes":[]}` | A common template applied to all deployments. Each deployment can then override individual fields as needed by the operator. | +| delegatedOperatorDeployments.template | object | `{"affinity":{},"deploymentAnnotations":{},"description":"","env":{"FIFTYONE_DELEGATED_OPERATION_LOG_PATH":"","FIFTYONE_INTERNAL_SERVICE":true,"FIFTYONE_MEDIA_CACHE_SIZE_BYTES":-1},"image":{"pullPolicy":"Always","repository":"voxel51/fiftyone-teams-cv-full","tag":""},"labels":{},"liveness":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"nodeSelector":{},"podAnnotations":{},"podDisruptionBudget":{"enabled":false,"minAvailable":null},"podSecurityContext":{},"readiness":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"replicaCount":3,"resources":{"limits":{},"requests":{}},"secretEnv":{},"securityContext":{},"startup":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"tolerations":[],"topologySpreadConstraints":[],"updateStrategy":{"type":"RollingUpdate"},"volumeMounts":[],"volumes":[]}` | A common template applied to all deployments. Each deployment can then override individual fields as needed by the operator. | | delegatedOperatorDeployments.template.affinity | object | `{}` | Affinity and anti-affinity for `delegated-operator-executor`. [Reference][affinity]. | | delegatedOperatorDeployments.template.deploymentAnnotations | object | `{}` | Annotations for the `teams-do` deployment. [Reference][annotations]. | | delegatedOperatorDeployments.template.description | string | `""` | A description for the delegated operator instance. This is unused in the template context. Each operator should either set their own description or, optionally, use the default. If unset at the operator context, it will be defaulted to `Long running operations delegated to $name` where `$name` is the name of the Deployment object. | | delegatedOperatorDeployments.template.env.FIFTYONE_DELEGATED_OPERATION_LOG_PATH | string | `""` | Full path to a network-mounted file system or a cloud storage path to use for storing logs generated by delegated operation runs, one file per job. The default `""` means log upload is disabled. | | delegatedOperatorDeployments.template.env.FIFTYONE_INTERNAL_SERVICE | bool | `true` | Whether the SDK is running in an internal service context. When running in FiftyOne Enterprise, set to `true`. | | delegatedOperatorDeployments.template.env.FIFTYONE_MEDIA_CACHE_SIZE_BYTES | int | `-1` | Set the media cache size (in bytes) for the local FiftyOne Delegated Operator Executor processes. When not set, the app's default value is 32 GiB. `-1` disables the media cache garbage collection (and cache size is unlimited). | -| delegatedOperatorDeployments.template.extraContainers | list | `[]` | Additional containers to run in each delegated-operator-executor pod alongside the main container. When non-empty, the pod is configured with `shareProcessNamespace: true`. [Reference][sidecar-containers]. | | delegatedOperatorDeployments.template.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | | delegatedOperatorDeployments.template.image.repository | string | `"voxel51/fiftyone-teams-cv-full"` | Container image for `delegated-operator-executor`. | | delegatedOperatorDeployments.template.image.tag | string | `""` | Image tag for `delegated-operator-executor`. Defaults to the chart version. | @@ -904,7 +899,6 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | delegatedOperatorDeployments.template.liveness.failureThreshold | int | `5` | Number of times to retry the liveness probe for the `teams-do`. [Reference][probes]. | | delegatedOperatorDeployments.template.liveness.periodSeconds | int | `30` | How often (in seconds) to perform the liveness probe for `teams-do`. [Reference][probes]. | | delegatedOperatorDeployments.template.liveness.timeoutSeconds | int | `30` | Timeout for the liveness probe for the `teams-do`. [Reference][probes]. | -| delegatedOperatorDeployments.template.nativeSidecarContainers | list | `[]` | Native sidecar containers for delegated-operator-executor pods (Kubernetes 1.29+). Entries are rendered under `initContainers`; set `restartPolicy: Always` on each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers]. | | delegatedOperatorDeployments.template.nodeSelector | object | `{}` | nodeSelector for `delegated-operator-executor`. [Reference][node-selector]. | | delegatedOperatorDeployments.template.podAnnotations | object | `{}` | Annotations for delegated-operator-executor pods. [Reference][annotations]. | | delegatedOperatorDeployments.template.podDisruptionBudget.enabled | bool | `false` | Whether a pod disruption budget is enabled for `teams-plugins`. | @@ -930,7 +924,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | delegatedOperatorJobTemplates.configMap.labels | object | `{}` | Additional labels for the generated `ConfigMap`. [Reference][labels-and-selectors]. | | delegatedOperatorJobTemplates.configMap.name | string | `""` | Name of the `ConfigMap` (existing or to be created) in the namespace `namespace.name` used for DO templates. Defaults to `release-name-fiftyone-teams-app-do-templates`. | | delegatedOperatorJobTemplates.jobs | object | `{}` | On-Demand Delegated Operator Jobs. | -| delegatedOperatorJobTemplates.template | object | `{"activeDeadlineSeconds":null,"affinity":{},"backoffLimit":null,"completions":null,"containerSecurityContext":{},"env":{"FIFTYONE_DELEGATED_OPERATION_LOG_PATH":"","FIFTYONE_MEDIA_CACHE_SIZE_BYTES":-1},"extraContainers":[],"image":{"pullPolicy":"Always","repository":"voxel51/fiftyone-teams-cv-full","tag":""},"jobAnnotations":{},"labels":{},"nativeSidecarContainers":[],"nodeSelector":{},"podAnnotations":{},"podSecurityContext":{},"resources":{"limits":{},"requests":{}},"secretEnv":{},"tolerations":[],"ttlSecondsAfterFinished":null,"volumeMounts":[],"volumes":[]}` | A common template applied to all deployments. Each deployment can then override individual fields as needed by the operator. | +| delegatedOperatorJobTemplates.template | object | `{"activeDeadlineSeconds":null,"affinity":{},"backoffLimit":null,"completions":null,"containerSecurityContext":{},"env":{"FIFTYONE_DELEGATED_OPERATION_LOG_PATH":"","FIFTYONE_MEDIA_CACHE_SIZE_BYTES":-1},"image":{"pullPolicy":"Always","repository":"voxel51/fiftyone-teams-cv-full","tag":""},"jobAnnotations":{},"labels":{},"nodeSelector":{},"podAnnotations":{},"podSecurityContext":{},"resources":{"limits":{},"requests":{}},"secretEnv":{},"tolerations":[],"ttlSecondsAfterFinished":null,"volumeMounts":[],"volumes":[]}` | A common template applied to all deployments. Each deployment can then override individual fields as needed by the operator. | | delegatedOperatorJobTemplates.template.activeDeadlineSeconds | optional | `nil` | Maximum of seconds a job should be able to run. [Reference][job-termination-and-cleanup]. | | delegatedOperatorJobTemplates.template.affinity | object | `{}` | Affinity and anti-affinity for `delegated-operator-executor`. [Reference][affinity]. | | delegatedOperatorJobTemplates.template.backoffLimit | optional | `nil` | Amount of retries for the job to try before being marked as "Failed". [Reference][job-backoff-failure-policy]. | @@ -938,13 +932,11 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | delegatedOperatorJobTemplates.template.containerSecurityContext | object | `{}` | Container security configuration for generated delegated-operator containers. [Reference][container-security-context]. | | delegatedOperatorJobTemplates.template.env.FIFTYONE_DELEGATED_OPERATION_LOG_PATH | string | `""` | Full path to a network-mounted file system or a cloud storage path to use for storing logs generated by delegated operation runs, one file per job. The default `""` means log upload is disabled. | | delegatedOperatorJobTemplates.template.env.FIFTYONE_MEDIA_CACHE_SIZE_BYTES | int | `-1` | Set the media cache size (in bytes) for the local FiftyOne Delegated Operator Executor processes. When not set, the app's default value is 32 GiB. `-1` disables the media cache garbage collection (and cache size is unlimited). | -| delegatedOperatorJobTemplates.template.extraContainers | list | `[]` | Additional containers to run in each on-demand delegated-operator Job pod alongside the executor container. When non-empty, the pod is configured with `shareProcessNamespace: true`. Note: a regular container that does not exit will block Job completion; for telemetry sidecars use `nativeSidecarContainers` instead. [Reference][sidecar-containers]. | | delegatedOperatorJobTemplates.template.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | | delegatedOperatorJobTemplates.template.image.repository | string | `"voxel51/fiftyone-teams-cv-full"` | Container image for `delegated-operator-executor`. | | delegatedOperatorJobTemplates.template.image.tag | string | `""` | Image tag for `delegated-operator-executor`. Defaults to the chart version. | | delegatedOperatorJobTemplates.template.jobAnnotations | object | `{}` | Annotations for the `teams-do` deployment. [Reference][annotations]. | | delegatedOperatorJobTemplates.template.labels | object | `{}` | Additional labels for the `delegated-operator-executor` related objects. [Reference][labels-and-selectors]. | -| delegatedOperatorJobTemplates.template.nativeSidecarContainers | list | `[]` | Native sidecar containers for on-demand delegated-operator Job pods (Kubernetes 1.29+). Entries are rendered under `initContainers`; set `restartPolicy: Always` on each entry to enable the native sidecar lifecycle. Kubelet auto-terminates these when the executor container finishes, so the Job completes cleanly. The chart auto-injects `TARGET_CONTAINER=executor` on each entry (override by setting it explicitly). [Reference][sidecar-containers]. | | delegatedOperatorJobTemplates.template.nodeSelector | object | `{}` | nodeSelector for `delegated-operator-executor`. [Reference][node-selector]. | | delegatedOperatorJobTemplates.template.podAnnotations | object | `{}` | Annotations for delegated-operator-executor pods. [Reference][annotations]. | | delegatedOperatorJobTemplates.template.podSecurityContext | object | `{}` | Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context]. | @@ -985,7 +977,6 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | pluginsSettings.env.FIFTYONE_INTERNAL_SERVICE | bool | `true` | Whether the SDK is running in an internal service context. When running in FiftyOne Enterprise, set to `true`. | | pluginsSettings.env.FIFTYONE_MEDIA_CACHE_APP_IMAGES | bool | `false` | Controls whether cloud media images will be downloaded and added to the local cache upon viewing media in the app. | | pluginsSettings.env.FIFTYONE_MEDIA_CACHE_SIZE_BYTES | int | `-1` | Set the media cache size (in bytes) for the local FiftyOne Plugins processes. When not set, the app's default value is 32 GiB. `-1` disables the media cache garbage collection (and cache size is unlimited). | -| pluginsSettings.extraContainers | list | `[]` | Additional containers to run in the `teams-plugins` pod alongside the main container. When non-empty, the pod is configured with `shareProcessNamespace: true`. [Reference][sidecar-containers]. | | pluginsSettings.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | | pluginsSettings.image.repository | string | `"voxel51/fiftyone-app"` | Container image for `teams-plugins`. | | pluginsSettings.image.tag | string | `""` | Image tag for `teams-plugins`. Defaults to the chart version. | @@ -998,7 +989,6 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | pluginsSettings.liveness.failureThreshold | int | `5` | Number of times to retry the liveness probe for the `teams-plugins`. [Reference][probes]. | | pluginsSettings.liveness.periodSeconds | int | `15` | How often (in seconds) to perform the liveness probe for `teams-plugins`. [Reference][probes]. | | pluginsSettings.liveness.timeoutSeconds | int | `5` | Timeout for the liveness probe for the `teams-plugins`. [Reference][probes]. | -| pluginsSettings.nativeSidecarContainers | list | `[]` | Native sidecar containers for the `teams-plugins` pod (Kubernetes 1.29+). Entries are rendered under `initContainers`; set `restartPolicy: Always` on each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers]. | | pluginsSettings.nodeSelector | object | `{}` | nodeSelector for `teams-plugins`. [Reference][node-selector]. | | pluginsSettings.podAnnotations | object | `{}` | Annotations for `teams-plugins` pods. [Reference][annotations]. | | pluginsSettings.podDisruptionBudget | object | `{"enabled":false,"minAvailable":null}` | Pod Disruption Budget for pods for `teams-plugins`. [Reference][pod-disruption-budget]. | @@ -1095,7 +1085,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | teamsAppSettings.updateStrategy | object | `{"type":"RollingUpdate"}` | Control how `teams-app` pods are redeployed during an upgrade. [Reference][upgrade-strategies] | | teamsAppSettings.volumeMounts | list | `[]` | Volume mounts for `teams-app` pods. [Reference][volumes]. | | teamsAppSettings.volumes | list | `[]` | Volumes for `teams-app` pods. [Reference][volumes]. | -| telemetry.enabled | bool | `true` | Master switch. When `true` (default), all telemetry resources and auto-injected sidecars are rendered. When `false`, none are. | +| telemetry.enabled | bool | `true` | Master switch. When `true` (default), all telemetry resources and sidecars are rendered. When `false`, none are. | | telemetry.redis.external.url | string | `""` | URL of an external Redis to use instead of the bundled one (e.g. `redis://my-redis.example.com:6379`). | | telemetry.redis.image | string | `"redis:7-alpine"` | Container image for the telemetry Redis Deployment. | | telemetry.redis.maxmemory | string | `"400mb"` | `--maxmemory` flag passed to `redis-server`. | @@ -1104,11 +1094,11 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | telemetry.redis.persistence.size | string | `"1Gi"` | Storage size for the telemetry Redis `PersistentVolumeClaim`. | | telemetry.redis.persistence.storageClass | string | `""` | `StorageClass` name for the telemetry Redis `PersistentVolumeClaim`. Leave unset to use the cluster's default `StorageClass`. | | telemetry.redis.resources | object | `{"limits":{"cpu":"250m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"256Mi"}}` | Resource requests/limits for the telemetry Redis container. [Reference][resources]. | -| telemetry.serviceAccounts | list | `[]` | ServiceAccount names (in `namespace.name`) bound to the telemetry pod-logs Role. Each entry becomes a `ServiceAccount` subject on the generated RoleBinding. When empty, the RoleBinding subjects default to the chart's main app service account and the teams-api RBAC service account so auto-injected sidecars can read their target's logs. | +| telemetry.serviceAccounts | list | `[]` | ServiceAccount names (in `namespace.name`) bound to the telemetry pod-logs Role. Each entry becomes a `ServiceAccount` subject on the generated RoleBinding. When empty, the RoleBinding subjects default to the chart's main app service account and the teams-api RBAC service account so the telemetry sidecars can read their target's logs. | | telemetry.sidecar.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | | telemetry.sidecar.image.repository | string | `"voxel51/telemetry-sidecar"` | Container image for `telemetry-sidecar`. | | telemetry.sidecar.image.tag | string | `""` | Image tag for `telemetry-sidecar`. Defaults to the chart version. | -| telemetry.sidecar.resources | object | `{"limits":{"cpu":"100m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"512Mi"}}` | Resource requests/limits for each auto-injected sidecar container. | +| telemetry.sidecar.resources | object | `{"limits":{"cpu":"100m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"512Mi"}}` | Resource requests/limits for each `telemetry-sidecar` container. | @@ -1142,7 +1132,6 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): [security-context]: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ [service-account]: https://kubernetes.io/docs/concepts/security/service-accounts/ [service-type]: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types -[sidecar-containers]: https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/ [taints-and-tolerations]: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ [topology-spread-constraints]: https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ [upgrade-strategies]: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy diff --git a/helm/fiftyone-teams-app/README.md.gotmpl b/helm/fiftyone-teams-app/README.md.gotmpl index c350c8138..cbf38123b 100644 --- a/helm/fiftyone-teams-app/README.md.gotmpl +++ b/helm/fiftyone-teams-app/README.md.gotmpl @@ -754,7 +754,6 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): [security-context]: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ [service-account]: https://kubernetes.io/docs/concepts/security/service-accounts/ [service-type]: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types -[sidecar-containers]: https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/ [taints-and-tolerations]: https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ [topology-spread-constraints]: https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/ [upgrade-strategies]: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy diff --git a/helm/fiftyone-teams-app/templates/_telemetry.tpl b/helm/fiftyone-teams-app/templates/_telemetry.tpl index 4a917a0f7..8b7b00c3c 100644 --- a/helm/fiftyone-teams-app/templates/_telemetry.tpl +++ b/helm/fiftyone-teams-app/templates/_telemetry.tpl @@ -155,8 +155,8 @@ Inputs: same dict as telemetry.sidecar-env. {{/* A native-sidecar (initContainer with restartPolicy: Always) variant — for use under -spec.initContainers, primarily in DO Jobs where a regular extraContainer that does -not exit would block Job completion. +spec.initContainers in DO Jobs, where a regular sidecar container that does not exit +would block Job completion. */}} {{- define "telemetry.native-sidecar" -}} - name: telemetry-sidecar diff --git a/helm/fiftyone-teams-app/templates/api-deployment.yaml b/helm/fiftyone-teams-app/templates/api-deployment.yaml index 37635c293..04ce551c6 100644 --- a/helm/fiftyone-teams-app/templates/api-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/api-deployment.yaml @@ -44,7 +44,7 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ $serviceAccountName }} - {{- if or .Values.apiSettings.extraContainers .Values.apiSettings.nativeSidecarContainers .Values.telemetry.enabled }} + {{- if .Values.telemetry.enabled }} shareProcessNamespace: true {{- end }} securityContext: @@ -92,15 +92,11 @@ spec: {{- with .Values.apiSettings.volumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} - {{- with .Values.apiSettings.extraContainers }} - {{- toYaml . | nindent 8 }} - {{- end }} {{- if .Values.telemetry.enabled }} {{- include "telemetry.sidecar" (dict "ctx" . "serviceType" "teams-api" "targetName" "fiftyone-teams-api") | nindent 8 }} {{- end }} - {{- if or .Values.apiSettings.initContainers.enabled .Values.apiSettings.nativeSidecarContainers }} + {{- if .Values.apiSettings.initContainers.enabled }} initContainers: - {{- if .Values.apiSettings.initContainers.enabled }} {{- include "fiftyone-teams-app.commonInitContainers" ( @@ -111,10 +107,6 @@ spec: "tag" .Values.apiSettings.initContainers.image.tag ) | nindent 8 }} - {{- end }} - {{- with .Values.apiSettings.nativeSidecarContainers }} - {{- toYaml . | nindent 8 }} - {{- end }} {{- end }} {{- with .Values.apiSettings.nodeSelector }} nodeSelector: diff --git a/helm/fiftyone-teams-app/templates/app-deployment.yaml b/helm/fiftyone-teams-app/templates/app-deployment.yaml index b071e0a13..03ca186f8 100644 --- a/helm/fiftyone-teams-app/templates/app-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/app-deployment.yaml @@ -35,7 +35,7 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" . }} - {{- if or .Values.appSettings.extraContainers .Values.appSettings.nativeSidecarContainers .Values.telemetry.enabled }} + {{- if .Values.telemetry.enabled }} shareProcessNamespace: true {{- end }} securityContext: @@ -76,15 +76,11 @@ spec: volumeMounts: {{- toYaml . | nindent 12 }} {{- end }} - {{- with .Values.appSettings.extraContainers }} - {{- toYaml . | nindent 8 }} - {{- end }} {{- if .Values.telemetry.enabled }} {{- include "telemetry.sidecar" (dict "ctx" . "serviceType" "fiftyone-app" "targetName" "hypercorn") | nindent 8 }} {{- end }} - {{- if or .Values.appSettings.initContainers.enabled .Values.appSettings.nativeSidecarContainers }} + {{- if .Values.appSettings.initContainers.enabled }} initContainers: - {{- if .Values.appSettings.initContainers.enabled }} {{- include "fiftyone-teams-app.commonInitContainers" ( @@ -95,10 +91,6 @@ spec: "tag" .Values.appSettings.initContainers.image.tag ) | nindent 8 }} - {{- end }} - {{- with .Values.appSettings.nativeSidecarContainers }} - {{- toYaml . | nindent 8 }} - {{- end }} {{- end }} {{- with .Values.appSettings.nodeSelector }} nodeSelector: diff --git a/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml b/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml index a628858d4..ed264a941 100644 --- a/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml @@ -51,7 +51,7 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" $ }} - {{- if or ($v.extraContainers | default $baseTpl.extraContainers) ($v.nativeSidecarContainers | default $baseTpl.nativeSidecarContainers) $.Values.telemetry.enabled }} + {{- if $.Values.telemetry.enabled }} shareProcessNamespace: true {{- end }} securityContext: @@ -120,16 +120,9 @@ spec: failureThreshold: {{ ($v.startup).failureThreshold | default $baseTpl.startup.failureThreshold }} periodSeconds: {{ ($v.startup).periodSeconds | default $baseTpl.startup.periodSeconds }} timeoutSeconds: {{ ($v.startup).timeoutSeconds | default $baseTpl.startup.timeoutSeconds }} - {{- with $v.extraContainers | default $baseTpl.extraContainers }} - {{- toYaml . | nindent 8 }} - {{- end }} {{- if $.Values.telemetry.enabled }} {{- include "telemetry.sidecar" (dict "ctx" $ "serviceType" "delegated-operator" "targetName" "fiftyone delegated" "executor" true) | nindent 8 }} {{- end }} - {{- with $v.nativeSidecarContainers | default $baseTpl.nativeSidecarContainers }} - initContainers: - {{- toYaml . | nindent 8 }} - {{- end }} {{- with (merge (dict) ($v.nodeSelector | default dict) ($baseTpl.nodeSelector)) }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml b/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml index adf148452..22d2d9ef2 100644 --- a/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml +++ b/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml @@ -43,30 +43,8 @@ data: {{- $mergedTolerations := $jobConfig.tolerations | default $baseTpl.tolerations }} {{- $mergedVolumeMounts := $jobConfig.volumeMounts | default $baseTpl.volumeMounts }} {{- $mergedVolumes := $jobConfig.volumes | default $baseTpl.volumes }} - {{- $mergedExtraContainers := $jobConfig.extraContainers | default $baseTpl.extraContainers }} - {{- $mergedNativeSidecarContainers := $jobConfig.nativeSidecarContainers | default $baseTpl.nativeSidecarContainers | default list }} - {{- /* Auto-inject the telemetry native-sidecar when telemetry is enabled - and the consumer hasn't supplied one of their own. */ -}} {{- if $.Values.telemetry.enabled }} - {{- $hasTelemetrySidecar := false }} - {{- range $sc := $mergedNativeSidecarContainers }} - {{- if eq $sc.name "telemetry-sidecar" }}{{- $hasTelemetrySidecar = true }}{{- end }} - {{- end }} - {{- if not $hasTelemetrySidecar }} - {{- $telemetrySidecarYaml := include "telemetry.native-sidecar" (dict "ctx" $ "serviceType" "delegated-operator" "targetName" "fiftyone delegated" "targetContainer" "executor" "executor" true) }} - {{- $telemetrySidecar := $telemetrySidecarYaml | fromYamlArray | first }} - {{- $mergedNativeSidecarContainers = append $mergedNativeSidecarContainers $telemetrySidecar }} - {{- end }} - {{- end }} - - {{- $hasExecutorSidecar := false }} - {{- range $sidecar := $mergedNativeSidecarContainers }} - {{- range $env := ($sidecar.env | default list) }} - {{- if and (eq $env.name "EXECUTOR_SIDECAR") (eq (toString $env.value) "true") }}{{- $hasExecutorSidecar = true }}{{- end }} - {{- end }} - {{- end }} - {{- if $hasExecutorSidecar }} {{- $hasVolume := false }} {{- range $v := ($mergedVolumes | default list) }} {{- if eq $v.name "telemetry-socket" }}{{- $hasVolume = true }}{{- end }} @@ -128,7 +106,7 @@ data: spec: restartPolicy: {{ $jobConfig.restartPolicy | default $baseTpl.restartPolicy | default "Never" }} serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" $ }} - {{- if or $mergedExtraContainers $mergedNativeSidecarContainers }} + {{- if $.Values.telemetry.enabled }} shareProcessNamespace: true {{- end }} {{- with $mergedPodSecurityContext }} @@ -163,37 +141,9 @@ data: volumeMounts: {{- toYaml . | nindent 16 }} {{- end }} - {{- with $mergedExtraContainers }} - {{- toYaml . | nindent 12 }} - {{- end }} - {{- with $mergedNativeSidecarContainers }} + {{- if $.Values.telemetry.enabled }} initContainers: - {{- range $sidecar := . }} - {{- $isExecutor := false }} - {{- $hasTargetContainer := false }} - {{- range $env := ($sidecar.env | default list) }} - {{- if eq $env.name "TARGET_CONTAINER" }}{{- $hasTargetContainer = true }}{{- end }} - {{- if and (eq $env.name "EXECUTOR_SIDECAR") (eq (toString $env.value) "true") }}{{- $isExecutor = true }}{{- end }} - {{- end }} - {{- $rendered := deepCopy $sidecar }} - {{- if not $hasTargetContainer }} - {{- $_ := set $rendered "env" (prepend ($sidecar.env | default list) (dict "name" "TARGET_CONTAINER" "value" "executor")) }} - {{- end }} - {{- if $isExecutor }} - {{- $hasSocketMount := false }} - {{- range $vm := ($sidecar.volumeMounts | default list) }} - {{- if eq $vm.name "telemetry-socket" }}{{- $hasSocketMount = true }}{{- end }} - {{- end }} - {{- if not $hasSocketMount }} - {{- $_ := set $rendered "volumeMounts" (append ($sidecar.volumeMounts | default list) (dict "name" "telemetry-socket" "mountPath" "/tmp/telemetry")) }} - {{- end }} - {{- /* Gate the main container on the socket being bound — native sidecar readiness alone doesn't wait. */ -}} - {{- if not (hasKey $sidecar "readinessProbe") }} - {{- $_ := set $rendered "readinessProbe" (dict "exec" (dict "command" (list "sh" "-c" "test -S /tmp/telemetry/agent.sock")) "periodSeconds" 1 "failureThreshold" 30) }} - {{- end }} - {{- end }} - {{- list $rendered | toYaml | nindent 12 }} - {{- end }} + {{- include "telemetry.native-sidecar" (dict "ctx" $ "serviceType" "delegated-operator" "targetName" "fiftyone delegated" "targetContainer" "executor" "executor" true) | nindent 12 }} {{- end }} {{- with $mergedNodeSelector }} nodeSelector: diff --git a/helm/fiftyone-teams-app/templates/plugins-deployment.yaml b/helm/fiftyone-teams-app/templates/plugins-deployment.yaml index baa9762e9..b564f9d67 100644 --- a/helm/fiftyone-teams-app/templates/plugins-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/plugins-deployment.yaml @@ -36,7 +36,7 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" . }} - {{- if or .Values.pluginsSettings.extraContainers .Values.pluginsSettings.nativeSidecarContainers .Values.telemetry.enabled }} + {{- if .Values.telemetry.enabled }} shareProcessNamespace: true {{- end }} securityContext: @@ -77,15 +77,11 @@ spec: volumeMounts: {{- toYaml . | nindent 12 }} {{- end }} - {{- with .Values.pluginsSettings.extraContainers }} - {{- toYaml . | nindent 8 }} - {{- end }} {{- if .Values.telemetry.enabled }} {{- include "telemetry.sidecar" (dict "ctx" . "serviceType" "teams-plugins" "targetName" "hypercorn") | nindent 8 }} {{- end }} - {{- if or .Values.pluginsSettings.initContainers.enabled .Values.pluginsSettings.nativeSidecarContainers }} + {{- if .Values.pluginsSettings.initContainers.enabled }} initContainers: - {{- if .Values.pluginsSettings.initContainers.enabled }} {{- include "fiftyone-teams-app.commonInitContainers" ( @@ -96,10 +92,6 @@ spec: "tag" .Values.pluginsSettings.initContainers.image.tag ) | nindent 8 }} - {{- end }} - {{- with .Values.pluginsSettings.nativeSidecarContainers }} - {{- toYaml . | nindent 8 }} - {{- end }} {{- end }} {{- with .Values.pluginsSettings.nodeSelector }} nodeSelector: diff --git a/helm/fiftyone-teams-app/values.schema.json b/helm/fiftyone-teams-app/values.schema.json index 3da844e71..df33645e2 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -102,14 +102,6 @@ "title": "env", "type": "object" }, - "extraContainers": { - "description": "Additional containers to run in the `teams-api` pod alongside the\nmain container. When non-empty, the pod is configured with\n`shareProcessNamespace: true`. [Reference][sidecar-containers].", - "items": { - "required": [] - }, - "title": "extraContainers", - "type": "array" - }, "image": { "properties": { "pullPolicy": { @@ -336,14 +328,6 @@ "title": "liveness", "type": "object" }, - "nativeSidecarContainers": { - "description": "Native sidecar containers for the `teams-api` pod (Kubernetes 1.29+).\nEntries are rendered under `initContainers`; set `restartPolicy: Always`\non each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers].", - "items": { - "required": [] - }, - "title": "nativeSidecarContainers", - "type": "array" - }, "nodeSelector": { "description": "nodeSelector for `teams-api`. [Reference][node-selector].", "required": [], @@ -810,14 +794,6 @@ "title": "env", "type": "object" }, - "extraContainers": { - "description": "Additional containers to run in the `fiftyone-app` pod alongside the\nmain container. When non-empty, the pod is configured with\n`shareProcessNamespace: true`. [Reference][sidecar-containers].", - "items": { - "required": [] - }, - "title": "extraContainers", - "type": "array" - }, "image": { "properties": { "pullPolicy": { @@ -1040,14 +1016,6 @@ "title": "liveness", "type": "object" }, - "nativeSidecarContainers": { - "description": "Native sidecar containers for the `fiftyone-app` pod (Kubernetes 1.29+).\nEntries are rendered under `initContainers`; set `restartPolicy: Always`\non each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers].", - "items": { - "required": [] - }, - "title": "nativeSidecarContainers", - "type": "array" - }, "nodeSelector": { "description": "nodeSelector for `fiftyone-app`. [Reference][node-selector].", "required": [], @@ -2057,14 +2025,6 @@ "title": "env", "type": "object" }, - "extraContainers": { - "description": "Additional containers to run in each delegated-operator-executor pod\nalongside the main container. When non-empty, the pod is configured\nwith `shareProcessNamespace: true`. [Reference][sidecar-containers].", - "items": { - "required": [] - }, - "title": "extraContainers", - "type": "array" - }, "image": { "properties": { "pullPolicy": { @@ -2131,14 +2091,6 @@ "title": "liveness", "type": "object" }, - "nativeSidecarContainers": { - "description": "Native sidecar containers for delegated-operator-executor pods\n(Kubernetes 1.29+). Entries are rendered under `initContainers`; set\n`restartPolicy: Always` on each entry to enable the native sidecar\nlifecycle. [Reference][sidecar-containers].", - "items": { - "required": [] - }, - "title": "nativeSidecarContainers", - "type": "array" - }, "nodeSelector": { "description": "nodeSelector for `delegated-operator-executor`. [Reference][node-selector].", "required": [], @@ -2433,14 +2385,6 @@ "title": "env", "type": "object" }, - "extraContainers": { - "description": "Additional containers to run in each on-demand delegated-operator Job\npod alongside the executor container. When non-empty, the pod is\nconfigured with `shareProcessNamespace: true`. Note: a regular\ncontainer that does not exit will block Job completion; for telemetry\nsidecars use `nativeSidecarContainers` instead. [Reference][sidecar-containers].", - "items": { - "required": [] - }, - "title": "extraContainers", - "type": "array" - }, "image": { "properties": { "pullPolicy": { @@ -2488,14 +2432,6 @@ "title": "labels", "type": "object" }, - "nativeSidecarContainers": { - "description": "Native sidecar containers for on-demand delegated-operator Job pods\n(Kubernetes 1.29+). Entries are rendered under `initContainers`; set\n`restartPolicy: Always` on each entry to enable the native sidecar\nlifecycle. Kubelet auto-terminates these when the executor container\nfinishes, so the Job completes cleanly. The chart auto-injects\n`TARGET_CONTAINER=executor` on each entry (override by setting it\nexplicitly). [Reference][sidecar-containers].", - "items": { - "required": [] - }, - "title": "nativeSidecarContainers", - "type": "array" - }, "nodeSelector": { "description": "nodeSelector for `delegated-operator-executor`. [Reference][node-selector].", "required": [], @@ -2855,14 +2791,6 @@ "title": "env", "type": "object" }, - "extraContainers": { - "description": "Additional containers to run in the `teams-plugins` pod alongside the\nmain container. When non-empty, the pod is configured with\n`shareProcessNamespace: true`. [Reference][sidecar-containers].", - "items": { - "required": [] - }, - "title": "extraContainers", - "type": "array" - }, "image": { "properties": { "pullPolicy": { @@ -3085,14 +3013,6 @@ "title": "liveness", "type": "object" }, - "nativeSidecarContainers": { - "description": "Native sidecar containers for the `teams-plugins` pod (Kubernetes 1.29+).\nEntries are rendered under `initContainers`; set `restartPolicy: Always`\non each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers].", - "items": { - "required": [] - }, - "title": "nativeSidecarContainers", - "type": "array" - }, "nodeSelector": { "description": "nodeSelector for `teams-plugins`. [Reference][node-selector].", "required": [], @@ -4010,18 +3930,18 @@ "type": "object" }, "telemetry": { - "description": "In-cluster telemetry. When enabled (the default), the chart renders:\n - a Redis Deployment + Service that collects per-service metric streams\n - a Role + RoleBinding granting the sidecar read access to `pods/log`\n - a `telemetry-sidecar` container auto-injected into the\n `teams-api`, `fiftyone-app`, `teams-plugins`, and delegated-operator\n workloads, plus a `nativeSidecarContainers` entry on the\n delegated-operator Job template\n - `FIFTYONE_TELEMETRY_REDIS_URL` on every workload that talks to the\n redis backend (api, app, plugins, teams-app, delegated operators)\n\nSet `enabled: false` to skip all telemetry resources and sidecar injection.", + "description": "In-cluster telemetry. When enabled (the default), the chart renders:\n - a Redis Deployment + Service that collects per-service metric streams\n - a Role + RoleBinding granting the sidecar read access to `pods/log`\n - a `telemetry-sidecar` container injected into the `teams-api`,\n `fiftyone-app`, `teams-plugins`, and delegated-operator workloads,\n and as a native sidecar `initContainer` on delegated-operator Jobs\n - `FIFTYONE_TELEMETRY_REDIS_URL` on every workload that talks to the\n redis backend (api, app, plugins, teams-app, delegated operators)\n\nSet `enabled: false` to skip all telemetry resources and sidecar injection.", "properties": { "enabled": { "default": true, - "description": "Master switch. When `true` (default), all telemetry resources and\nauto-injected sidecars are rendered. When `false`, none are.", + "description": "Master switch. When `true` (default), all telemetry resources and\nsidecars are rendered. When `false`, none are.", "title": "enabled", "type": "boolean" }, "redis": { "properties": { "external": { - "description": "When `external.url` is set, the chart does NOT render the bundled\nRedis `Deployment`/`Service`/`PersistentVolumeClaim`; consumer\nworkloads and auto-injected sidecars are wired at the external URL\ninstead. Use this for managed Redis (ElastiCache, MemoryStore, etc.)\nor an existing shared Redis you already operate.", + "description": "When `external.url` is set, the chart does NOT render the bundled\nRedis `Deployment`/`Service`/`PersistentVolumeClaim`; consumer\nworkloads and telemetry sidecars are wired at the external URL\ninstead. Use this for managed Redis (ElastiCache, MemoryStore, etc.)\nor an existing shared Redis you already operate.", "properties": { "url": { "default": "", @@ -4129,7 +4049,7 @@ "type": "object" }, "serviceAccounts": { - "description": "ServiceAccount names (in `namespace.name`) bound to the telemetry\npod-logs Role. Each entry becomes a `ServiceAccount` subject on the\ngenerated RoleBinding. When empty, the RoleBinding subjects default to\nthe chart's main app service account and the teams-api RBAC service\naccount so auto-injected sidecars can read their target's logs.", + "description": "ServiceAccount names (in `namespace.name`) bound to the telemetry\npod-logs Role. Each entry becomes a `ServiceAccount` subject on the\ngenerated RoleBinding. When empty, the RoleBinding subjects default to\nthe chart's main app service account and the teams-api RBAC service\naccount so the telemetry sidecars can read their target's logs.", "items": { "required": [] }, @@ -4174,7 +4094,7 @@ "type": "object" }, "resources": { - "description": "Resource requests/limits for each auto-injected sidecar container.", + "description": "Resource requests/limits for each `telemetry-sidecar` container.", "properties": { "limits": { "properties": { diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index d11b05fae..a51b1f73b 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -120,14 +120,6 @@ apiSettings: # -- Affinity and anti-affinity for `teams-api`. [Reference][affinity]. affinity: {} - # -- Additional containers to run in the `teams-api` pod alongside the - # main container. When non-empty, the pod is configured with - # `shareProcessNamespace: true`. [Reference][sidecar-containers]. - extraContainers: [] - # -- Native sidecar containers for the `teams-api` pod (Kubernetes 1.29+). - # Entries are rendered under `initContainers`; set `restartPolicy: Always` - # on each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers]. - nativeSidecarContainers: [] initContainers: # -- Container security configuration for `teams-api` `initContainers`. [Reference][container-security-context]. containerSecurityContext: @@ -398,14 +390,6 @@ appSettings: # -- Affinity and anti-affinity for `fiftyone-app`. [Reference][affinity]. affinity: {} - # -- Additional containers to run in the `fiftyone-app` pod alongside the - # main container. When non-empty, the pod is configured with - # `shareProcessNamespace: true`. [Reference][sidecar-containers]. - extraContainers: [] - # -- Native sidecar containers for the `fiftyone-app` pod (Kubernetes 1.29+). - # Entries are rendered under `initContainers`; set `restartPolicy: Always` - # on each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers]. - nativeSidecarContainers: [] initContainers: # -- Container security configuration for `fiftyone-app` `initContainers`. [Reference][container-security-context]. containerSecurityContext: @@ -836,15 +820,6 @@ delegatedOperatorDeployments: volumeMounts: [] # -- Volumes for `delegated-operator-executor`. [Reference][volumes]. volumes: [] - # -- Additional containers to run in each delegated-operator-executor pod - # alongside the main container. When non-empty, the pod is configured - # with `shareProcessNamespace: true`. [Reference][sidecar-containers]. - extraContainers: [] - # -- Native sidecar containers for delegated-operator-executor pods - # (Kubernetes 1.29+). Entries are rendered under `initContainers`; set - # `restartPolicy: Always` on each entry to enable the native sidecar - # lifecycle. [Reference][sidecar-containers]. - nativeSidecarContainers: [] # -- Additional deployments to configure. Each template # will use .Values.delegatedOperatorDeployments.template as a base. @@ -1021,20 +996,6 @@ delegatedOperatorJobTemplates: volumeMounts: [] # -- Volumes for `delegated-operator-executor`. [Reference][volumes]. volumes: [] - # -- Additional containers to run in each on-demand delegated-operator Job - # pod alongside the executor container. When non-empty, the pod is - # configured with `shareProcessNamespace: true`. Note: a regular - # container that does not exit will block Job completion; for telemetry - # sidecars use `nativeSidecarContainers` instead. [Reference][sidecar-containers]. - extraContainers: [] - # -- Native sidecar containers for on-demand delegated-operator Job pods - # (Kubernetes 1.29+). Entries are rendered under `initContainers`; set - # `restartPolicy: Always` on each entry to enable the native sidecar - # lifecycle. Kubelet auto-terminates these when the executor container - # finishes, so the Job completes cleanly. The chart auto-injects - # `TARGET_CONTAINER=executor` on each entry (override by setting it - # explicitly). [Reference][sidecar-containers]. - nativeSidecarContainers: [] # -- On-Demand Delegated Operator Jobs. jobs: {} # Default CPU-only batch job @@ -1266,14 +1227,6 @@ pluginsSettings: # -- Affinity and anti-affinity for `teams-plugins`. [Reference][affinity]. affinity: {} - # -- Additional containers to run in the `teams-plugins` pod alongside the - # main container. When non-empty, the pod is configured with - # `shareProcessNamespace: true`. [Reference][sidecar-containers]. - extraContainers: [] - # -- Native sidecar containers for the `teams-plugins` pod (Kubernetes 1.29+). - # Entries are rendered under `initContainers`; set `restartPolicy: Always` - # on each entry to enable the native sidecar lifecycle. [Reference][sidecar-containers]. - nativeSidecarContainers: [] initContainers: # -- Container security configuration for `teams-plugins` `initContainers`. [Reference][container-security-context]. containerSecurityContext: @@ -1646,17 +1599,16 @@ teamsAppSettings: # In-cluster telemetry. When enabled (the default), the chart renders: # - a Redis Deployment + Service that collects per-service metric streams # - a Role + RoleBinding granting the sidecar read access to `pods/log` -# - a `telemetry-sidecar` container auto-injected into the -# `teams-api`, `fiftyone-app`, `teams-plugins`, and delegated-operator -# workloads, plus a `nativeSidecarContainers` entry on the -# delegated-operator Job template +# - a `telemetry-sidecar` container injected into the `teams-api`, +# `fiftyone-app`, `teams-plugins`, and delegated-operator workloads, +# and as a native sidecar `initContainer` on delegated-operator Jobs # - `FIFTYONE_TELEMETRY_REDIS_URL` on every workload that talks to the # redis backend (api, app, plugins, teams-app, delegated operators) # # Set `enabled: false` to skip all telemetry resources and sidecar injection. telemetry: # -- Master switch. When `true` (default), all telemetry resources and - # auto-injected sidecars are rendered. When `false`, none are. + # sidecars are rendered. When `false`, none are. enabled: true redis: # -- Container image for the telemetry Redis Deployment. @@ -1689,7 +1641,7 @@ telemetry: memory: 512Mi # When `external.url` is set, the chart does NOT render the bundled # Redis `Deployment`/`Service`/`PersistentVolumeClaim`; consumer - # workloads and auto-injected sidecars are wired at the external URL + # workloads and telemetry sidecars are wired at the external URL # instead. Use this for managed Redis (ElastiCache, MemoryStore, etc.) # or an existing shared Redis you already operate. external: @@ -1709,7 +1661,7 @@ telemetry: repository: voxel51/telemetry-sidecar # -- Image tag for `telemetry-sidecar`. Defaults to the chart version. tag: "" - # -- Resource requests/limits for each auto-injected sidecar container. + # -- Resource requests/limits for each `telemetry-sidecar` container. resources: requests: cpu: 100m @@ -1721,5 +1673,5 @@ telemetry: # pod-logs Role. Each entry becomes a `ServiceAccount` subject on the # generated RoleBinding. When empty, the RoleBinding subjects default to # the chart's main app service account and the teams-api RBAC service - # account so auto-injected sidecars can read their target's logs. + # account so the telemetry sidecars can read their target's logs. serviceAccounts: [] diff --git a/tests/unit/helm/delegated-operator-job-configmap_test.go b/tests/unit/helm/delegated-operator-job-configmap_test.go index dcbb97d5c..0c28841f5 100644 --- a/tests/unit/helm/delegated-operator-job-configmap_test.go +++ b/tests/unit/helm/delegated-operator-job-configmap_test.go @@ -657,147 +657,3 @@ func (s *doK8sConfigMapTemplateTest) loadTestFile(filename, chartVersion string) result = strings.TrimSpace(result) return strings.TrimSpace(result) } - -func (s *doK8sConfigMapTemplateTest) TestNativeSidecarTelemetryAutoInject() { - testCases := []struct { - name string - values map[string]string - strValues map[string]string - expected func(subT gruntworkTesting.TestingT, job batchv1.Job) - }{ - { - "AutoInjectsTelemetrySocketWiringWhenSidecarHasExecutorEnv", - map[string]string{ - "delegatedOperatorJobTemplates.jobs.testJob.unused": "nil", - "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].name": "telemetry-sidecar", - "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].image": "telemetry-sidecar:latest", - "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].restartPolicy": "Always", - "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[0].name": "EXECUTOR_SIDECAR", - }, - map[string]string{ - "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[0].value": "true", - }, - func(subT gruntworkTesting.TestingT, job batchv1.Job) { - podSpec := job.Spec.Template.Spec - - s.Require().NotNil(podSpec.ShareProcessNamespace, "shareProcessNamespace must be set when nativeSidecarContainers present") - s.True(*podSpec.ShareProcessNamespace, "shareProcessNamespace must be true") - - s.Require().Len(podSpec.InitContainers, 1, "Should be one initContainer (the sidecar)") - sidecar := podSpec.InitContainers[0] - s.True(envContains(sidecar.Env, "TARGET_CONTAINER", "executor"), "Sidecar must have auto-injected TARGET_CONTAINER=executor") - s.True(volumeMountContains(sidecar.VolumeMounts, "telemetry-socket", "/tmp/telemetry"), "Sidecar must have auto-injected telemetry-socket volumeMount") - - executor := findContainer(podSpec.Containers, "executor") - s.Require().NotNil(executor, "executor container must exist") - s.True(volumeMountContains(executor.VolumeMounts, "telemetry-socket", "/tmp/telemetry"), "Executor must have auto-injected telemetry-socket volumeMount") - - s.True(volumeContains(podSpec.Volumes, "telemetry-socket"), "Pod must have auto-injected telemetry-socket emptyDir volume") - }, - }, - { - "PreservesExplicitTargetContainerOverride", - map[string]string{ - "delegatedOperatorJobTemplates.jobs.testJob.unused": "nil", - "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].name": "telemetry-sidecar", - "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].image": "telemetry-sidecar:latest", - "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].restartPolicy": "Always", - "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[0].name": "EXECUTOR_SIDECAR", - "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[1].name": "TARGET_CONTAINER", - "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[1].value": "custom-executor", - }, - map[string]string{ - "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].env[0].value": "true", - }, - func(subT gruntworkTesting.TestingT, job batchv1.Job) { - sidecar := job.Spec.Template.Spec.InitContainers[0] - s.True(envContains(sidecar.Env, "TARGET_CONTAINER", "custom-executor"), "User-set TARGET_CONTAINER must win") - s.False(envContains(sidecar.Env, "TARGET_CONTAINER", "executor"), "Auto-inject must not also append TARGET_CONTAINER=executor") - }, - }, - { - "DoesNotInjectTelemetryWiringForNonExecutorSidecar", - map[string]string{ - "delegatedOperatorJobTemplates.jobs.testJob.unused": "nil", - "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].name": "istio-proxy", - "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].image": "istio-proxy:latest", - "delegatedOperatorJobTemplates.template.nativeSidecarContainers[0].restartPolicy": "Always", - }, - nil, - func(subT gruntworkTesting.TestingT, job batchv1.Job) { - podSpec := job.Spec.Template.Spec - s.False(volumeContains(podSpec.Volumes, "telemetry-socket"), "Non-executor sidecar must not auto-inject telemetry-socket volume") - sidecar := podSpec.InitContainers[0] - s.False(volumeMountContains(sidecar.VolumeMounts, "telemetry-socket", "/tmp/telemetry"), "Non-executor sidecar must not auto-inject volumeMount") - executor := findContainer(podSpec.Containers, "executor") - s.Require().NotNil(executor) - s.False(volumeMountContains(executor.VolumeMounts, "telemetry-socket", "/tmp/telemetry"), "Executor must not get telemetry-socket mount when no executor sidecar") - }, - }, - } - - for _, testCase := range testCases { - testCase := testCase - s.Run(testCase.name, func() { - subT := s.T() - subT.Parallel() - - options := &helm.Options{SetValues: disableTelemetry(testCase.values), SetStrValues: testCase.strValues} - output := helm.RenderTemplate(subT, options, s.chartPath, s.releaseName, s.templates) - - var configMap corev1.ConfigMap - helm.UnmarshalK8SYaml(subT, output, &configMap) - - s.Require().NotEmpty(configMap.Data["testJob.yaml"], "testJob.yaml should be rendered in configmap data") - - jinjaArgs := map[string]interface{}{ - "_id": strings.ToLower(random.UniqueId()), - "_command": "fiftyone", - "_args": []string{"delegated", "launch"}, - } - actualJobYaml, err := convertJinjaToYAML(configMap.Data["testJob.yaml"], jinjaArgs) - s.NoError(err) - - var job batchv1.Job - helm.UnmarshalK8SYaml(subT, actualJobYaml, &job) - - testCase.expected(subT, job) - }) - } -} - -func envContains(envs []corev1.EnvVar, name, value string) bool { - for _, e := range envs { - if e.Name == name && e.Value == value { - return true - } - } - return false -} - -func volumeMountContains(vms []corev1.VolumeMount, name, mountPath string) bool { - for _, vm := range vms { - if vm.Name == name && vm.MountPath == mountPath { - return true - } - } - return false -} - -func volumeContains(vols []corev1.Volume, name string) bool { - for _, v := range vols { - if v.Name == name { - return true - } - } - return false -} - -func findContainer(containers []corev1.Container, name string) *corev1.Container { - for i, c := range containers { - if c.Name == name { - return &containers[i] - } - } - return nil -} From 97649d2d05ef98a2bad9da7ff2f40b7b188b0158 Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Wed, 20 May 2026 18:00:03 -0400 Subject: [PATCH 34/49] fix: telemetry-sidecar repo --- tests/fixtures/helm/integration_values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fixtures/helm/integration_values.yaml b/tests/fixtures/helm/integration_values.yaml index e2534840b..7ac637402 100644 --- a/tests/fixtures/helm/integration_values.yaml +++ b/tests/fixtures/helm/integration_values.yaml @@ -125,7 +125,7 @@ pluginsSettings: telemetry: sidecar: image: - repository: voxel51/telemetry-sidecar + repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/telemetry-sidecar pullPolicy: IfNotPresent tag: v2.19.0rc8 teamsAppSettings: From 5f413e884f9a4bea8461460ea8e769e504ff025e Mon Sep 17 00:00:00 2001 From: voxelbot Date: Wed, 20 May 2026 23:01:39 +0000 Subject: [PATCH 35/49] ci(fixtures): automated bump for integration test fixtures teams-app:v2.19.0-rc.9 api:v2.19.0rc10 app:v2.19.0rc10 cas:v2.19.0-rc.9 --- .../docker/integration_internal_auth.env | 10 +++++----- .../fixtures/docker/integration_legacy_auth.env | 10 +++++----- tests/fixtures/helm/integration_values.yaml | 16 ++++++++-------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/fixtures/docker/integration_internal_auth.env b/tests/fixtures/docker/integration_internal_auth.env index ec95cd4b3..e8eaaa3db 100644 --- a/tests/fixtures/docker/integration_internal_auth.env +++ b/tests/fixtures/docker/integration_internal_auth.env @@ -31,10 +31,10 @@ FIFTYONE_ENV=development CAS_DEBUG="cas:*" # Image tags for existing artifacts -VERSION=v2.19.0rc8 -FIFTYONE_APP_VERSION=v2.19.0rc8 -FIFTYONE_TEAMS_API_VERSION=v2.19.0rc8 -FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.7 -FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.7 +VERSION=v2.19.0rc10 +FIFTYONE_APP_VERSION=v2.19.0rc10 +FIFTYONE_TEAMS_API_VERSION=v2.19.0rc10 +FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.9 +FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.9 FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=1 diff --git a/tests/fixtures/docker/integration_legacy_auth.env b/tests/fixtures/docker/integration_legacy_auth.env index 215a28e07..329a09723 100644 --- a/tests/fixtures/docker/integration_legacy_auth.env +++ b/tests/fixtures/docker/integration_legacy_auth.env @@ -39,10 +39,10 @@ FIFTYONE_ENV=development CAS_DEBUG="cas:*" # Image tags for existing artifacts -VERSION=v2.19.0rc8 -FIFTYONE_APP_VERSION=v2.19.0rc8 -FIFTYONE_TEAMS_API_VERSION=v2.19.0rc8 -FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.7 -FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.7 +VERSION=v2.19.0rc10 +FIFTYONE_APP_VERSION=v2.19.0rc10 +FIFTYONE_TEAMS_API_VERSION=v2.19.0rc10 +FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.9 +FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.9 FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=1 diff --git a/tests/fixtures/helm/integration_values.yaml b/tests/fixtures/helm/integration_values.yaml index 7ac637402..ddb740664 100644 --- a/tests/fixtures/helm/integration_values.yaml +++ b/tests/fixtures/helm/integration_values.yaml @@ -9,7 +9,7 @@ apiSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-teams-api pullPolicy: IfNotPresent - tag: v2.19.0rc8 + tag: v2.19.0rc10 service: liveness: initialDelaySeconds: 15 @@ -27,7 +27,7 @@ appSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc8 + tag: v2.19.0rc10 # TODO: Test `minikube addons configure registry-creds` or # When using minikube's addon registry-creds, we may also need to create the k8s secret `regcred` # See https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ @@ -42,21 +42,21 @@ casSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-teams-cas pullPolicy: IfNotPresent - tag: v2.19.0-rc.7 + tag: v2.19.0-rc.9 delegatedOperatorDeployments: deployments: teamsDoCpuDefault: image: repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc8 + tag: v2.19.0rc10 delegatedOperatorTemplates: jobs: teamsDoCpuDefaultK8s: image: repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc8 + tag: v2.19.0rc10 ingress: annotations: # For using the nginx-ingress controller with cert-manager self signed certificates @@ -121,13 +121,13 @@ pluginsSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc8 + tag: v2.19.0rc10 telemetry: sidecar: image: repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/telemetry-sidecar pullPolicy: IfNotPresent - tag: v2.19.0rc8 + tag: v2.19.0rc10 teamsAppSettings: dnsName: local.fiftyone.ai # env: @@ -144,4 +144,4 @@ teamsAppSettings: # The others are `vW.X.Y.devZ` (note `.devZ` vs `-dev.Z`). # This is a byproduct of `npm` versioning versus Python PEP 440. pullPolicy: IfNotPresent - tag: v2.19.0-rc.7 + tag: v2.19.0-rc.9 From 660f544eadcdb5d20b2cd04b81621ac196023006 Mon Sep 17 00:00:00 2001 From: voxelbot Date: Thu, 21 May 2026 16:08:30 +0000 Subject: [PATCH 36/49] ci(fixtures): automated bump for integration test fixtures teams-app:v2.19.0-rc.10 api:v2.19.0rc11 app:v2.19.0rc11 cas:v2.19.0-rc.10 --- .../docker/integration_internal_auth.env | 10 +++++----- .../fixtures/docker/integration_legacy_auth.env | 10 +++++----- tests/fixtures/helm/integration_values.yaml | 16 ++++++++-------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/fixtures/docker/integration_internal_auth.env b/tests/fixtures/docker/integration_internal_auth.env index e8eaaa3db..9bae4792d 100644 --- a/tests/fixtures/docker/integration_internal_auth.env +++ b/tests/fixtures/docker/integration_internal_auth.env @@ -31,10 +31,10 @@ FIFTYONE_ENV=development CAS_DEBUG="cas:*" # Image tags for existing artifacts -VERSION=v2.19.0rc10 -FIFTYONE_APP_VERSION=v2.19.0rc10 -FIFTYONE_TEAMS_API_VERSION=v2.19.0rc10 -FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.9 -FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.9 +VERSION=v2.19.0rc11 +FIFTYONE_APP_VERSION=v2.19.0rc11 +FIFTYONE_TEAMS_API_VERSION=v2.19.0rc11 +FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.10 +FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.10 FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=1 diff --git a/tests/fixtures/docker/integration_legacy_auth.env b/tests/fixtures/docker/integration_legacy_auth.env index 329a09723..a9a9f022f 100644 --- a/tests/fixtures/docker/integration_legacy_auth.env +++ b/tests/fixtures/docker/integration_legacy_auth.env @@ -39,10 +39,10 @@ FIFTYONE_ENV=development CAS_DEBUG="cas:*" # Image tags for existing artifacts -VERSION=v2.19.0rc10 -FIFTYONE_APP_VERSION=v2.19.0rc10 -FIFTYONE_TEAMS_API_VERSION=v2.19.0rc10 -FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.9 -FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.9 +VERSION=v2.19.0rc11 +FIFTYONE_APP_VERSION=v2.19.0rc11 +FIFTYONE_TEAMS_API_VERSION=v2.19.0rc11 +FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.10 +FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.10 FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=1 diff --git a/tests/fixtures/helm/integration_values.yaml b/tests/fixtures/helm/integration_values.yaml index ddb740664..b25c0fe3b 100644 --- a/tests/fixtures/helm/integration_values.yaml +++ b/tests/fixtures/helm/integration_values.yaml @@ -9,7 +9,7 @@ apiSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-teams-api pullPolicy: IfNotPresent - tag: v2.19.0rc10 + tag: v2.19.0rc11 service: liveness: initialDelaySeconds: 15 @@ -27,7 +27,7 @@ appSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc10 + tag: v2.19.0rc11 # TODO: Test `minikube addons configure registry-creds` or # When using minikube's addon registry-creds, we may also need to create the k8s secret `regcred` # See https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ @@ -42,21 +42,21 @@ casSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-teams-cas pullPolicy: IfNotPresent - tag: v2.19.0-rc.9 + tag: v2.19.0-rc.10 delegatedOperatorDeployments: deployments: teamsDoCpuDefault: image: repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc10 + tag: v2.19.0rc11 delegatedOperatorTemplates: jobs: teamsDoCpuDefaultK8s: image: repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc10 + tag: v2.19.0rc11 ingress: annotations: # For using the nginx-ingress controller with cert-manager self signed certificates @@ -121,13 +121,13 @@ pluginsSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc10 + tag: v2.19.0rc11 telemetry: sidecar: image: repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/telemetry-sidecar pullPolicy: IfNotPresent - tag: v2.19.0rc10 + tag: v2.19.0rc11 teamsAppSettings: dnsName: local.fiftyone.ai # env: @@ -144,4 +144,4 @@ teamsAppSettings: # The others are `vW.X.Y.devZ` (note `.devZ` vs `-dev.Z`). # This is a byproduct of `npm` versioning versus Python PEP 440. pullPolicy: IfNotPresent - tag: v2.19.0-rc.9 + tag: v2.19.0-rc.10 From 5c8a53d617f47765d4e0ff26173fb1c7e36ffd7e Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Thu, 21 May 2026 14:10:12 -0400 Subject: [PATCH 37/49] docs(telemetry): fix sidecar examples; flag UI log-viewer dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In configuring-kubernetes-orchestrator.md, fix the standalone K8s orchestrator examples (pin sidecar image to v2.19.0, set TARGET_NAME to "fiftyone delegated" to match chart+compose, drop runAsNonRoot default, correct Redis service DNS) and drop "Optional" from the section heading. Add a NOTE — repeated in the helm values.yaml comment and the compose opt-out doc — explaining that disabling the sidecar empties the FiftyOne UI's log viewer for delegated-operator runs. Co-Authored-By: Claude Opus 4.7 (1M context) --- docker/docs/configuring-telemetry.md | 5 +++ .../configuring-kubernetes-orchestrator.md | 33 ++++++++++++++----- helm/fiftyone-teams-app/README.md | 3 +- helm/fiftyone-teams-app/values.schema.json | 4 +-- helm/fiftyone-teams-app/values.yaml | 9 +++-- 5 files changed, 41 insertions(+), 13 deletions(-) diff --git a/docker/docs/configuring-telemetry.md b/docker/docs/configuring-telemetry.md index 33f351add..3b97063ef 100644 --- a/docker/docs/configuring-telemetry.md +++ b/docker/docs/configuring-telemetry.md @@ -86,6 +86,11 @@ docker compose --profile gpu \ ## Opt out +> [!NOTE] +> The telemetry sidecar can be disabled, but doing so disables the +> FiftyOne UI's log viewer for delegated-operator runs — it depends +> on the sidecar to capture per-operation logs. + To run without telemetry, add a `compose.override.yaml` that scales the telemetry services to zero replicas: diff --git a/docs/orchestrators/configuring-kubernetes-orchestrator.md b/docs/orchestrators/configuring-kubernetes-orchestrator.md index b0eb34372..96abc7ae6 100644 --- a/docs/orchestrators/configuring-kubernetes-orchestrator.md +++ b/docs/orchestrators/configuring-kubernetes-orchestrator.md @@ -25,7 +25,7 @@ - [Template Storage Options](#template-storage-options) - [Secrets Options](#secrets-options) - [Separate CPU and GPU Templates](#separate-cpu-and-gpu-templates) -- [Optional: Telemetry Sidecar](#optional-telemetry-sidecar) +- [Telemetry Sidecar](#telemetry-sidecar) - [Refresh Orchestrator Operators](#refresh-orchestrator-operators) - [Additional Considerations](#additional-considerations) - [Credential Rotation](#credential-rotation) @@ -460,7 +460,12 @@ fom.register_orchestrator( ) ``` -## Optional: Telemetry Sidecar +## Telemetry Sidecar + +> [!NOTE] +> The telemetry sidecar can be disabled, but doing so disables the +> FiftyOne UI's log viewer for delegated-operator runs — it depends +> on the sidecar to capture per-operation logs. If your deployment runs telemetry (the helm chart and docker compose files include it by default), you can attach a per-Job telemetry sidecar @@ -484,14 +489,20 @@ spec: shareProcessNamespace: true initContainers: - name: telemetry-sidecar - image: voxel51/telemetry-sidecar:latest + image: voxel51/telemetry-sidecar:v2.19.0 restartPolicy: Always securityContext: + # The sidecar image runs as root (SYS_PTRACE + + # /proc//fd/1 access require it). Set explicitly so it + # works even when the pod's podSecurityContext sets + # runAsNonRoot: true. + runAsNonRoot: false + runAsUser: 0 capabilities: add: [SYS_PTRACE] env: - name: TARGET_NAME - value: delegated + value: "fiftyone delegated" - name: SERVICE_TYPE value: delegated-operator - name: EXECUTOR_SIDECAR @@ -507,7 +518,7 @@ spec: fieldRef: fieldPath: metadata.namespace - name: FIFTYONE_TELEMETRY_REDIS_URL - value: redis://telemetry-redis.fiftyone.svc.cluster.local:6379 + value: redis://-telemetry-redis.fiftyone-teams.svc.cluster.local:6379 - name: FIFTYONE_DATABASE_URI valueFrom: secretKeyRef: @@ -529,7 +540,7 @@ spec: - name: TELEMETRY_SOCKET value: /tmp/telemetry/agent.sock - name: FIFTYONE_TELEMETRY_REDIS_URL - value: redis://telemetry-redis.fiftyone.svc.cluster.local:6379 + value: redis://-telemetry-redis.fiftyone-teams.svc.cluster.local:6379 volumeMounts: # ... existing mounts, plus: - mountPath: /tmp/telemetry @@ -638,14 +649,20 @@ spec: shareProcessNamespace: true initContainers: - name: telemetry-sidecar - image: voxel51/telemetry-sidecar:latest + image: voxel51/telemetry-sidecar:v2.19.0 restartPolicy: Always securityContext: + # The sidecar image runs as root (SYS_PTRACE + + # /proc//fd/1 access require it). Set explicitly so it + # works even when the pod's podSecurityContext sets + # runAsNonRoot: true. + runAsNonRoot: false + runAsUser: 0 capabilities: add: [SYS_PTRACE] env: - name: TARGET_NAME - value: delegated + value: "fiftyone delegated" - name: SERVICE_TYPE value: delegated-operator - name: EXECUTOR_SIDECAR diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index c4f392412..e8ca1f567 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -1085,7 +1085,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | teamsAppSettings.updateStrategy | object | `{"type":"RollingUpdate"}` | Control how `teams-app` pods are redeployed during an upgrade. [Reference][upgrade-strategies] | | teamsAppSettings.volumeMounts | list | `[]` | Volume mounts for `teams-app` pods. [Reference][volumes]. | | teamsAppSettings.volumes | list | `[]` | Volumes for `teams-app` pods. [Reference][volumes]. | -| telemetry.enabled | bool | `true` | Master switch. When `true` (default), all telemetry resources and sidecars are rendered. When `false`, none are. | +| telemetry.enabled | bool | `true` | Master switch. When `true` (default), all telemetry resources and sidecars are rendered. When `false`, none are — note that the FiftyOne UI's delegated-operator log viewer depends on the sidecar and will be empty without it. | | telemetry.redis.external.url | string | `""` | URL of an external Redis to use instead of the bundled one (e.g. `redis://my-redis.example.com:6379`). | | telemetry.redis.image | string | `"redis:7-alpine"` | Container image for the telemetry Redis Deployment. | | telemetry.redis.maxmemory | string | `"400mb"` | `--maxmemory` flag passed to `redis-server`. | @@ -1099,6 +1099,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | telemetry.sidecar.image.repository | string | `"voxel51/telemetry-sidecar"` | Container image for `telemetry-sidecar`. | | telemetry.sidecar.image.tag | string | `""` | Image tag for `telemetry-sidecar`. Defaults to the chart version. | | telemetry.sidecar.resources | object | `{"limits":{"cpu":"100m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"512Mi"}}` | Resource requests/limits for each `telemetry-sidecar` container. | + diff --git a/helm/fiftyone-teams-app/values.schema.json b/helm/fiftyone-teams-app/values.schema.json index df33645e2..ad4ec7805 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -3930,11 +3930,11 @@ "type": "object" }, "telemetry": { - "description": "In-cluster telemetry. When enabled (the default), the chart renders:\n - a Redis Deployment + Service that collects per-service metric streams\n - a Role + RoleBinding granting the sidecar read access to `pods/log`\n - a `telemetry-sidecar` container injected into the `teams-api`,\n `fiftyone-app`, `teams-plugins`, and delegated-operator workloads,\n and as a native sidecar `initContainer` on delegated-operator Jobs\n - `FIFTYONE_TELEMETRY_REDIS_URL` on every workload that talks to the\n redis backend (api, app, plugins, teams-app, delegated operators)\n\nSet `enabled: false` to skip all telemetry resources and sidecar injection.", + "description": "In-cluster telemetry. When enabled (the default), the chart renders:\n - a Redis Deployment + Service that collects per-service metric streams\n - a Role + RoleBinding granting the sidecar read access to `pods/log`\n - a `telemetry-sidecar` container injected into the `teams-api`,\n `fiftyone-app`, `teams-plugins`, and delegated-operator workloads,\n and as a native sidecar `initContainer` on delegated-operator Jobs\n - `FIFTYONE_TELEMETRY_REDIS_URL` on every workload that talks to the\n redis backend (api, app, plugins, teams-app, delegated operators)\n\nSetting `enabled: false` skips all telemetry resources and sidecar\ninjection. Note that the FiftyOne UI's log viewer for delegated-\noperator runs depends on the sidecar to capture per-operation logs;\ndisabling telemetry will leave that log viewer empty.", "properties": { "enabled": { "default": true, - "description": "Master switch. When `true` (default), all telemetry resources and\nsidecars are rendered. When `false`, none are.", + "description": "Master switch. When `true` (default), all telemetry resources and\nsidecars are rendered. When `false`, none are — note that the\nFiftyOne UI's delegated-operator log viewer depends on the sidecar\nand will be empty without it.", "title": "enabled", "type": "boolean" }, diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index a51b1f73b..40042583b 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -1605,10 +1605,15 @@ teamsAppSettings: # - `FIFTYONE_TELEMETRY_REDIS_URL` on every workload that talks to the # redis backend (api, app, plugins, teams-app, delegated operators) # -# Set `enabled: false` to skip all telemetry resources and sidecar injection. +# Setting `enabled: false` skips all telemetry resources and sidecar +# injection. Note that the FiftyOne UI's log viewer for delegated- +# operator runs depends on the sidecar to capture per-operation logs; +# disabling telemetry will leave that log viewer empty. telemetry: # -- Master switch. When `true` (default), all telemetry resources and - # sidecars are rendered. When `false`, none are. + # sidecars are rendered. When `false`, none are — note that the + # FiftyOne UI's delegated-operator log viewer depends on the sidecar + # and will be empty without it. enabled: true redis: # -- Container image for the telemetry Redis Deployment. From 04fb4f61f6bd7f0590093f3d13baf294b0908603 Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Fri, 22 May 2026 10:26:52 -0400 Subject: [PATCH 38/49] fix(compose): bump GPU DO image tag to v2.19.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new compose.delegated-operators.gpu.yaml files for both auth modes pinned voxel51/fiftyone-teams-cv-full at v2.17.1 — a copy-paste from an older reference. The rest of the v2.19.0 branch (common-services.yaml, configuring-gpu-workloads.md) uses v2.19.0. Co-Authored-By: Claude Opus 4.7 (1M context) --- docker/internal-auth/compose.delegated-operators.gpu.yaml | 2 +- docker/legacy-auth/compose.delegated-operators.gpu.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/internal-auth/compose.delegated-operators.gpu.yaml b/docker/internal-auth/compose.delegated-operators.gpu.yaml index b7128de58..1f19f9c66 100644 --- a/docker/internal-auth/compose.delegated-operators.gpu.yaml +++ b/docker/internal-auth/compose.delegated-operators.gpu.yaml @@ -19,7 +19,7 @@ # https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies services: teams-do-gpu: - image: voxel51/fiftyone-teams-cv-full:v2.17.1 + image: voxel51/fiftyone-teams-cv-full:v2.19.0 profiles: ["gpu"] shm_size: "8g" command: > diff --git a/docker/legacy-auth/compose.delegated-operators.gpu.yaml b/docker/legacy-auth/compose.delegated-operators.gpu.yaml index b7128de58..1f19f9c66 100644 --- a/docker/legacy-auth/compose.delegated-operators.gpu.yaml +++ b/docker/legacy-auth/compose.delegated-operators.gpu.yaml @@ -19,7 +19,7 @@ # https://github.com/voxel51/fiftyone-teams-app-deploy/tree/main/docker#environment-proxies services: teams-do-gpu: - image: voxel51/fiftyone-teams-cv-full:v2.17.1 + image: voxel51/fiftyone-teams-cv-full:v2.19.0 profiles: ["gpu"] shm_size: "8g" command: > From 0862f09d7a6efb33e5f3f8e4b6de66f5438390b4 Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Fri, 22 May 2026 10:29:10 -0400 Subject: [PATCH 39/49] fix(helm): use FQDN for in-cluster telemetry Redis URL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The telemetry.redis.url helper returned redis://-telemetry-redis:6379 — a short name that only resolves within the chart's namespace. Delegated- operator Jobs (documented use-case in docs/orchestrators/configuring-kubernetes-orchestrator.md) can be scheduled into a different namespace and would silently fail to reach the bundled Redis. Switch to a fully-qualified ..svc.cluster.local hostname. Co-Authored-By: Claude Opus 4.7 (1M context) --- helm/fiftyone-teams-app/templates/_telemetry.tpl | 6 ++++-- tests/unit/helm/telemetry-redis_test.go | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/helm/fiftyone-teams-app/templates/_telemetry.tpl b/helm/fiftyone-teams-app/templates/_telemetry.tpl index 8b7b00c3c..fa243e826 100644 --- a/helm/fiftyone-teams-app/templates/_telemetry.tpl +++ b/helm/fiftyone-teams-app/templates/_telemetry.tpl @@ -12,7 +12,9 @@ If `telemetry.redis.external.url` is set, returns it (chart skips the bundled Redis Deployment/Service/PVC and consumer workloads + sidecars are wired at the external URL instead — e.g. for managed Redis like ElastiCache or MemoryStore). Otherwise returns the in-cluster Service -URL of the bundled Redis. +URL of the bundled Redis as a fully-qualified `..svc.cluster.local` +hostname so cross-namespace consumers (e.g. delegated-operator Jobs +scheduled into a different namespace) still resolve it. Always returns a non-empty URL when telemetry is enabled. */}} @@ -20,7 +22,7 @@ Always returns a non-empty URL when telemetry is enabled. {{- if .Values.telemetry.redis.external.url -}} {{- .Values.telemetry.redis.external.url -}} {{- else -}} -{{- printf "redis://%s:6379" (include "telemetry.redis.name" .) -}} +{{- printf "redis://%s.%s.svc.cluster.local:6379" (include "telemetry.redis.name" .) .Values.namespace.name -}} {{- end -}} {{- end }} diff --git a/tests/unit/helm/telemetry-redis_test.go b/tests/unit/helm/telemetry-redis_test.go index 0562bcde0..33c6dfff3 100644 --- a/tests/unit/helm/telemetry-redis_test.go +++ b/tests/unit/helm/telemetry-redis_test.go @@ -115,7 +115,8 @@ func (s *telemetryRedisTemplateTest) TestBundledUrlWiresApiDeployment() { var deployment appsv1.Deployment helm.UnmarshalK8SYaml(s.T(), output, &deployment) - expectedURL := fmt.Sprintf("redis://%s-telemetry-redis:6379", s.releaseName) + expectedURL := fmt.Sprintf("redis://%s-telemetry-redis.%s.svc.cluster.local:6379", + s.releaseName, "fiftyone-teams") for _, container := range deployment.Spec.Template.Spec.Containers { for _, ev := range container.Env { if ev.Name == "FIFTYONE_TELEMETRY_REDIS_URL" { @@ -181,7 +182,8 @@ func (s *telemetryRedisTemplateTest) TestExternalUrlWiresDelegatedOperatorJobCon // The ConfigMap embeds the Job spec as a multi-line YAML string in // .data.. The render output is plain text, so we just need to // ensure no occurrence of the in-cluster URL leaks through. - inClusterURL := fmt.Sprintf("redis://%s-telemetry-redis:6379", s.releaseName) + inClusterURL := fmt.Sprintf("redis://%s-telemetry-redis.%s.svc.cluster.local:6379", + s.releaseName, "fiftyone-teams") s.NotContains(output, inClusterURL, "DO Job ConfigMap must not embed the in-cluster Redis URL when external.url is set") s.Contains(output, "redis://my-managed-redis:6379", From 1a567a24bd76866f2239b39c7342f71e472af650 Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Fri, 22 May 2026 10:32:08 -0400 Subject: [PATCH 40/49] docs(helm): clarify telemetry sidecar tag default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit values.yaml said "chart version" — the helper actually falls back to .Chart.AppVersion, which is distinct from .Chart.Version in some charts. Make the comment unambiguous and regenerate the README + values.schema.json. Co-Authored-By: Claude Opus 4.7 (1M context) --- helm/fiftyone-teams-app/README.md | 2 +- helm/fiftyone-teams-app/values.schema.json | 2 +- helm/fiftyone-teams-app/values.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index e8ca1f567..7038bc296 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -1097,7 +1097,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | telemetry.serviceAccounts | list | `[]` | ServiceAccount names (in `namespace.name`) bound to the telemetry pod-logs Role. Each entry becomes a `ServiceAccount` subject on the generated RoleBinding. When empty, the RoleBinding subjects default to the chart's main app service account and the teams-api RBAC service account so the telemetry sidecars can read their target's logs. | | telemetry.sidecar.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | | telemetry.sidecar.image.repository | string | `"voxel51/telemetry-sidecar"` | Container image for `telemetry-sidecar`. | -| telemetry.sidecar.image.tag | string | `""` | Image tag for `telemetry-sidecar`. Defaults to the chart version. | +| telemetry.sidecar.image.tag | string | `""` | Image tag for `telemetry-sidecar`. Defaults to `Chart.AppVersion`. | | telemetry.sidecar.resources | object | `{"limits":{"cpu":"100m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"512Mi"}}` | Resource requests/limits for each `telemetry-sidecar` container. | diff --git a/helm/fiftyone-teams-app/values.schema.json b/helm/fiftyone-teams-app/values.schema.json index ad4ec7805..7e435864f 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -4084,7 +4084,7 @@ }, "tag": { "default": "", - "description": "Image tag for `telemetry-sidecar`. Defaults to the chart version.", + "description": "Image tag for `telemetry-sidecar`. Defaults to `Chart.AppVersion`.", "title": "tag", "type": "string" } diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index 40042583b..6d77b9104 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -1664,7 +1664,7 @@ telemetry: pullPolicy: Always # -- Container image for `telemetry-sidecar`. repository: voxel51/telemetry-sidecar - # -- Image tag for `telemetry-sidecar`. Defaults to the chart version. + # -- Image tag for `telemetry-sidecar`. Defaults to `Chart.AppVersion`. tag: "" # -- Resource requests/limits for each `telemetry-sidecar` container. resources: From 5065ebf8242b1c7aa6b9305f2056d108010f0930 Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Fri, 22 May 2026 10:37:39 -0400 Subject: [PATCH 41/49] feat(helm): add securityContext to telemetry-redis The upstream redis:7-alpine image runs as root with no explicit security posture, which clusters with PSA `restricted` or opinionated admission policies reject. Default to: - pod-level: runAsNonRoot, UID/GID 999 (the redis user in the upstream image), fsGroup 999 so the mounted /data PVC is group-writable - container-level: allowPrivilegeEscalation: false, readOnlyRootFilesystem: true (writes go to the /data volume), drop all capabilities Both blocks are exposed as values so operators can override on clusters that mandate specific UIDs or policies. Co-Authored-By: Claude Opus 4.7 (1M context) --- helm/fiftyone-teams-app/README.md | 2 + .../templates/telemetry-redis.yaml | 8 +++ helm/fiftyone-teams-app/values.schema.json | 65 +++++++++++++++++++ helm/fiftyone-teams-app/values.yaml | 17 +++++ 4 files changed, 92 insertions(+) diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 7038bc296..ea821c1c1 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -1086,6 +1086,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | teamsAppSettings.volumeMounts | list | `[]` | Volume mounts for `teams-app` pods. [Reference][volumes]. | | teamsAppSettings.volumes | list | `[]` | Volumes for `teams-app` pods. [Reference][volumes]. | | telemetry.enabled | bool | `true` | Master switch. When `true` (default), all telemetry resources and sidecars are rendered. When `false`, none are — note that the FiftyOne UI's delegated-operator log viewer depends on the sidecar and will be empty without it. | +| telemetry.redis.containerSecurityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true}` | Container-level security attributes for the telemetry Redis. `readOnlyRootFilesystem: true` is safe because writes go to the `/data` volume. [Reference][container-security-context]. | | telemetry.redis.external.url | string | `""` | URL of an external Redis to use instead of the bundled one (e.g. `redis://my-redis.example.com:6379`). | | telemetry.redis.image | string | `"redis:7-alpine"` | Container image for the telemetry Redis Deployment. | | telemetry.redis.maxmemory | string | `"400mb"` | `--maxmemory` flag passed to `redis-server`. | @@ -1093,6 +1094,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | telemetry.redis.persistence.enabled | bool | `true` | Controls whether a `PersistentVolumeClaim` is created for the bundled telemetry Redis. When `false`, Redis runs with an `emptyDir` volume and state is lost on pod restart — fine for dev clusters without a dynamic PV provisioner, not for production. | | telemetry.redis.persistence.size | string | `"1Gi"` | Storage size for the telemetry Redis `PersistentVolumeClaim`. | | telemetry.redis.persistence.storageClass | string | `""` | `StorageClass` name for the telemetry Redis `PersistentVolumeClaim`. Leave unset to use the cluster's default `StorageClass`. | +| telemetry.redis.podSecurityContext | object | `{"fsGroup":999,"runAsGroup":999,"runAsNonRoot":true,"runAsUser":999}` | Pod-level security attributes for the telemetry Redis. Defaults to a non-root UID/GID matching the `redis` user in the upstream `redis:7-alpine` image (999). `fsGroup` ensures the mounted `/data` volume is group-writable. [Reference][security-context]. | | telemetry.redis.resources | object | `{"limits":{"cpu":"250m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"256Mi"}}` | Resource requests/limits for the telemetry Redis container. [Reference][resources]. | | telemetry.serviceAccounts | list | `[]` | ServiceAccount names (in `namespace.name`) bound to the telemetry pod-logs Role. Each entry becomes a `ServiceAccount` subject on the generated RoleBinding. When empty, the RoleBinding subjects default to the chart's main app service account and the teams-api RBAC service account so the telemetry sidecars can read their target's logs. | | telemetry.sidecar.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | diff --git a/helm/fiftyone-teams-app/templates/telemetry-redis.yaml b/helm/fiftyone-teams-app/templates/telemetry-redis.yaml index 7771a047c..088d88f14 100644 --- a/helm/fiftyone-teams-app/templates/telemetry-redis.yaml +++ b/helm/fiftyone-teams-app/templates/telemetry-redis.yaml @@ -37,9 +37,17 @@ spec: labels: {{- include "telemetry.redis.labels" . | nindent 8 }} spec: + {{- with .Values.telemetry.redis.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} containers: - name: redis image: {{ .Values.telemetry.redis.image | default "redis:7-alpine" }} + {{- with .Values.telemetry.redis.containerSecurityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} args: - redis-server - --save diff --git a/helm/fiftyone-teams-app/values.schema.json b/helm/fiftyone-teams-app/values.schema.json index 7e435864f..7137da5db 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -3940,6 +3940,43 @@ }, "redis": { "properties": { + "containerSecurityContext": { + "description": "Container-level security attributes for the telemetry Redis.\n`readOnlyRootFilesystem: true` is safe because writes go to the\n`/data` volume. [Reference][container-security-context].", + "properties": { + "allowPrivilegeEscalation": { + "default": false, + "title": "allowPrivilegeEscalation", + "type": "boolean" + }, + "capabilities": { + "properties": { + "drop": { + "items": { + "anyOf": [ + { + "type": "string" + } + ], + "required": [] + }, + "title": "drop", + "type": "array" + } + }, + "required": [], + "title": "capabilities", + "type": "object" + }, + "readOnlyRootFilesystem": { + "default": true, + "title": "readOnlyRootFilesystem", + "type": "boolean" + } + }, + "required": [], + "title": "containerSecurityContext", + "type": "object" + }, "external": { "description": "When `external.url` is set, the chart does NOT render the bundled\nRedis `Deployment`/`Service`/`PersistentVolumeClaim`; consumer\nworkloads and telemetry sidecars are wired at the external URL\ninstead. Use this for managed Redis (ElastiCache, MemoryStore, etc.)\nor an existing shared Redis you already operate.", "properties": { @@ -4001,6 +4038,34 @@ "title": "persistence", "type": "object" }, + "podSecurityContext": { + "description": "Pod-level security attributes for the telemetry Redis. Defaults\nto a non-root UID/GID matching the `redis` user in the upstream\n`redis:7-alpine` image (999). `fsGroup` ensures the mounted `/data`\nvolume is group-writable. [Reference][security-context].", + "properties": { + "fsGroup": { + "default": 999, + "title": "fsGroup", + "type": "integer" + }, + "runAsGroup": { + "default": 999, + "title": "runAsGroup", + "type": "integer" + }, + "runAsNonRoot": { + "default": true, + "title": "runAsNonRoot", + "type": "boolean" + }, + "runAsUser": { + "default": 999, + "title": "runAsUser", + "type": "integer" + } + }, + "required": [], + "title": "podSecurityContext", + "type": "object" + }, "resources": { "description": "Resource requests/limits for the telemetry Redis container. [Reference][resources].", "properties": { diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index 6d77b9104..42bb36634 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -1644,6 +1644,23 @@ telemetry: limits: cpu: 250m memory: 512Mi + # -- Pod-level security attributes for the telemetry Redis. Defaults + # to a non-root UID/GID matching the `redis` user in the upstream + # `redis:7-alpine` image (999). `fsGroup` ensures the mounted `/data` + # volume is group-writable. [Reference][security-context]. + podSecurityContext: + runAsNonRoot: true + runAsUser: 999 + runAsGroup: 999 + fsGroup: 999 + # -- Container-level security attributes for the telemetry Redis. + # `readOnlyRootFilesystem: true` is safe because writes go to the + # `/data` volume. [Reference][container-security-context]. + containerSecurityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] # When `external.url` is set, the chart does NOT render the bundled # Redis `Deployment`/`Service`/`PersistentVolumeClaim`; consumer # workloads and telemetry sidecars are wired at the external URL From a05f11c10c8d945511372c13b514ee9c8a6b461c Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Fri, 22 May 2026 10:41:48 -0400 Subject: [PATCH 42/49] test(helm): cover shareProcessNamespace + sidecar securityContext MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three gaps in the telemetry test coverage: - shareProcessNamespace on api/app/plugins/DO deployments was never asserted. Without it the sidecar's /proc//fd/1 access breaks silently. Cover both on (telemetry enabled) and off (telemetry disabled) states. - The sidecar's securityContext (runAsUser: 0, runAsNonRoot: false, SYS_PTRACE) was never asserted. This is the posture that makes telemetry incompatible with PSA `restricted` — regressing it without an explicit posture change would silently break log capture on clusters that already accept it. - api-role.yaml's pods/log GET rule wasn't pinned across the telemetry on/off boundary. Lock it in so future api-SA consolidation work doesn't accidentally lift the grant. Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/unit/helm/api-role_test.go | 50 ++++-- tests/unit/helm/common_test.go | 4 +- tests/unit/helm/telemetry-sidecar_test.go | 193 ++++++++++++++++++++++ 3 files changed, 228 insertions(+), 19 deletions(-) create mode 100644 tests/unit/helm/telemetry-sidecar_test.go diff --git a/tests/unit/helm/api-role_test.go b/tests/unit/helm/api-role_test.go index 2b9fdc770..71e5ad3f3 100644 --- a/tests/unit/helm/api-role_test.go +++ b/tests/unit/helm/api-role_test.go @@ -286,6 +286,28 @@ func (s *apiRoleTemplateTest) TestMetadataLabels() { } func (s *apiRoleTemplateTest) TestRules() { + // The pods/log GET rule must remain present regardless of telemetry + // settings — the api uses it for its own DO log retrieval flows, and + // the telemetry RoleBinding piggybacks on this rule for the api SA + // (see _telemetry.tpl: telemetry.role.subjects). Render both with + // telemetry on and off to lock that in. + expectedRulesJson := `[ + { + "apiGroups": ["batch"], + "resources": ["jobs"], + "verbs": ["create", "get", "list", "watch", "update", "delete"] + }, + { + "apiGroups": [""], + "resources": ["pods"], + "verbs": ["get", "list", "watch", "delete"] + }, + { + "apiGroups": [""], + "resources": ["pods/log"], + "verbs": ["get"] + } + ]` testCases := []struct { name string values map[string]string @@ -295,29 +317,23 @@ func (s *apiRoleTemplateTest) TestRules() { "defaultValues", nil, func(rules []rbacv1.PolicyRule) { - expectedRulesJson := `[ - { - "apiGroups": ["batch"], - "resources": ["jobs"], - "verbs": ["create", "get", "list", "watch", "update", "delete"] - }, - { - "apiGroups": [""], - "resources": ["pods"], - "verbs": ["get", "list", "watch", "delete"] - }, - { - "apiGroups": [""], - "resources": ["pods/log"], - "verbs": ["get"] - } - ]` var expectedRules []rbacv1.PolicyRule err := json.Unmarshal([]byte(expectedRulesJson), &expectedRules) s.NoError(err) s.Equal(expectedRules, rules, "Rules should be equal") }, }, + { + "telemetryDisabled", + map[string]string{"telemetry.enabled": "false"}, + func(rules []rbacv1.PolicyRule) { + var expectedRules []rbacv1.PolicyRule + err := json.Unmarshal([]byte(expectedRulesJson), &expectedRules) + s.NoError(err) + s.Equal(expectedRules, rules, + "api-role rules must be unchanged when telemetry is disabled") + }, + }, } for _, testCase := range testCases { diff --git a/tests/unit/helm/common_test.go b/tests/unit/helm/common_test.go index f4bf76ef0..80b0d34de 100644 --- a/tests/unit/helm/common_test.go +++ b/tests/unit/helm/common_test.go @@ -1,5 +1,5 @@ -//go:build all || helm || unit || unitApiDeployment || unitApiService || unitAppDeployment || unitAppHpa || unitAppService || unitCasDeployment || unitCasService || unitIngress || unitNamespace || unitPluginsDeployment || unitHpaPlugins || unitPluginsService || unitSecrets || unitServiceAccount || unitTeamsAppDeployment || unitTeamsAppHpa || unitTeamsAppService || unitTelemetryRedis || unitTelemetryRoleBinding -// +build all helm unit unitApiDeployment unitApiService unitAppDeployment unitAppHpa unitAppService unitCasDeployment unitCasService unitIngress unitNamespace unitPluginsDeployment unitHpaPlugins unitPluginsService unitSecrets unitServiceAccount unitTeamsAppDeployment unitTeamsAppHpa unitTeamsAppService unitTelemetryRedis unitTelemetryRoleBinding +//go:build all || helm || unit || unitApiDeployment || unitApiService || unitAppDeployment || unitAppHpa || unitAppService || unitCasDeployment || unitCasService || unitIngress || unitNamespace || unitPluginsDeployment || unitHpaPlugins || unitPluginsService || unitSecrets || unitServiceAccount || unitTeamsAppDeployment || unitTeamsAppHpa || unitTeamsAppService || unitTelemetryRedis || unitTelemetryRoleBinding || unitTelemetrySidecar +// +build all helm unit unitApiDeployment unitApiService unitAppDeployment unitAppHpa unitAppService unitCasDeployment unitCasService unitIngress unitNamespace unitPluginsDeployment unitHpaPlugins unitPluginsService unitSecrets unitServiceAccount unitTeamsAppDeployment unitTeamsAppHpa unitTeamsAppService unitTelemetryRedis unitTelemetryRoleBinding unitTelemetrySidecar package unit diff --git a/tests/unit/helm/telemetry-sidecar_test.go b/tests/unit/helm/telemetry-sidecar_test.go new file mode 100644 index 000000000..e63ac472a --- /dev/null +++ b/tests/unit/helm/telemetry-sidecar_test.go @@ -0,0 +1,193 @@ +//go:build kubeall || helm || unit || unitTelemetrySidecar +// +build kubeall helm unit unitTelemetrySidecar + +package unit + +import ( + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" +) + +type telemetrySidecarTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string +} + +func TestTelemetrySidecarTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &telemetrySidecarTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + }) +} + +// findSidecar locates the telemetry-sidecar container in a slice of containers. +func findSidecar(containers []corev1.Container) *corev1.Container { + for i, c := range containers { + if c.Name == "telemetry-sidecar" { + return &containers[i] + } + } + return nil +} + +// TestShareProcessNamespaceEnabledByDefault asserts that the api, app, +// plugins, and delegated-operator deployments all opt into PID-namespace +// sharing when telemetry is enabled (the default). The sidecar relies on +// /proc//fd/1 access in the target container's PID namespace, so +// dropping this would silently break log capture. +func (s *telemetrySidecarTemplateTest) TestShareProcessNamespaceEnabledByDefault() { + cases := []struct { + template string + values map[string]string + }{ + {"templates/api-deployment.yaml", nil}, + {"templates/app-deployment.yaml", nil}, + { + "templates/plugins-deployment.yaml", + map[string]string{"pluginsSettings.enabled": "true"}, + }, + { + "templates/delegated-operator-instance-deployment.yaml", + map[string]string{ + "delegatedOperatorDeployments.deployments.teamsDoCpuDefault.enabled": "true", + }, + }, + } + + for _, tc := range cases { + tc := tc + s.Run(tc.template, func() { + options := &helm.Options{SetValues: tc.values} + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, + []string{tc.template}) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(s.T(), output, &deployment) + + s.Require().NotNil(deployment.Spec.Template.Spec.ShareProcessNamespace, + "shareProcessNamespace should be set on %s", tc.template) + s.True(*deployment.Spec.Template.Spec.ShareProcessNamespace, + "shareProcessNamespace should be true on %s", tc.template) + }) + } +} + +// TestShareProcessNamespaceDisabledWithTelemetryOff asserts that +// shareProcessNamespace is NOT set on workloads when telemetry is +// disabled — otherwise we'd be punching an unnecessary hole in pod +// isolation for users who opt out of telemetry. +func (s *telemetrySidecarTemplateTest) TestShareProcessNamespaceDisabledWithTelemetryOff() { + cases := []struct { + template string + values map[string]string + }{ + {"templates/api-deployment.yaml", nil}, + {"templates/app-deployment.yaml", nil}, + { + "templates/plugins-deployment.yaml", + map[string]string{"pluginsSettings.enabled": "true"}, + }, + { + "templates/delegated-operator-instance-deployment.yaml", + map[string]string{ + "delegatedOperatorDeployments.deployments.teamsDoCpuDefault.enabled": "true", + }, + }, + } + + for _, tc := range cases { + tc := tc + s.Run(tc.template, func() { + options := &helm.Options{SetValues: disableTelemetry(tc.values)} + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, + []string{tc.template}) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(s.T(), output, &deployment) + + if deployment.Spec.Template.Spec.ShareProcessNamespace != nil { + s.False(*deployment.Spec.Template.Spec.ShareProcessNamespace, + "shareProcessNamespace should not be true on %s when telemetry is disabled", + tc.template) + } + }) + } +} + +// TestSidecarSecurityContext asserts the telemetry-sidecar container's +// securityContext renders with the elevated privileges the image needs: +// root UID (py-spy + /proc//fd/1) and SYS_PTRACE. This is the +// posture that makes telemetry incompatible with PSA `restricted` — +// regressing this without an explicit posture-change PR would silently +// break log capture on the clusters that already accept it. +func (s *telemetrySidecarTemplateTest) TestSidecarSecurityContext() { + cases := []struct { + template string + values map[string]string + }{ + {"templates/api-deployment.yaml", nil}, + {"templates/app-deployment.yaml", nil}, + { + "templates/plugins-deployment.yaml", + map[string]string{"pluginsSettings.enabled": "true"}, + }, + { + "templates/delegated-operator-instance-deployment.yaml", + map[string]string{ + "delegatedOperatorDeployments.deployments.teamsDoCpuDefault.enabled": "true", + }, + }, + } + + for _, tc := range cases { + tc := tc + s.Run(tc.template, func() { + options := &helm.Options{SetValues: tc.values} + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, + []string{tc.template}) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(s.T(), output, &deployment) + + sidecar := findSidecar(deployment.Spec.Template.Spec.Containers) + s.Require().NotNil(sidecar, "telemetry-sidecar container should be injected into %s", tc.template) + + sc := sidecar.SecurityContext + s.Require().NotNil(sc, "telemetry-sidecar should have a securityContext") + + s.Require().NotNil(sc.RunAsNonRoot, "RunAsNonRoot should be set on sidecar") + s.False(*sc.RunAsNonRoot, "telemetry-sidecar must run as root") + + s.Require().NotNil(sc.RunAsUser, "RunAsUser should be set on sidecar") + s.EqualValues(0, *sc.RunAsUser, "telemetry-sidecar must run as UID 0") + + s.Require().NotNil(sc.Capabilities, "Capabilities should be set on sidecar") + var hasPtrace bool + for _, capability := range sc.Capabilities.Add { + if capability == "SYS_PTRACE" { + hasPtrace = true + break + } + } + s.True(hasPtrace, "telemetry-sidecar must add SYS_PTRACE") + }) + } +} From 488da4b8fefedfa6d83bef4cc2ef05f8c12768f8 Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Fri, 22 May 2026 10:59:35 -0400 Subject: [PATCH 43/49] docs: add v2.19+ telemetry upgrade callout Telemetry is on by default and adds non-trivial resource and cluster-policy requirements on upgrade. Document in both upgrade guides: - per-workload resource overhead (~+0.4 CPU, ~+2 GiB memory) plus bundled Redis - helm: Pod Security Admission compatibility (sidecar needs SYS_PTRACE + runAsUser:0 + shareProcessNamespace), opt-out via `telemetry.enabled: false`, external Redis via `telemetry.redis.external.url` - docker: Compose v2.17+ for `depends_on.restart: true`, PID-namespace + SYS_PTRACE host requirements, teams-do forced to replicas=1, opt-out via the override pattern documented in configuring-telemetry.md Co-Authored-By: Claude Opus 4.7 (1M context) --- docker/docs/upgrading.md | 45 +++++++++++++++++++++++++++++ helm/docs/upgrading.md | 61 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/docker/docs/upgrading.md b/docker/docs/upgrading.md index 61cf8037e..6d98efbe9 100644 --- a/docker/docs/upgrading.md +++ b/docker/docs/upgrading.md @@ -19,6 +19,7 @@ - [Upgrading From Previous Versions](#upgrading-from-previous-versions) - [A Note On Database Migrations](#a-note-on-database-migrations) - [From FiftyOne Enterprise Version 2.0.0 and Later](#from-fiftyone-enterprise-version-200-and-later) + - [FiftyOne Enterprise v2.19+ Telemetry Sidecars](#fiftyone-enterprise-v219-telemetry-sidecars) - [FiftyOne Enterprise v2.16+ Additional API Routes](#fiftyone-enterprise-v216-additional-api-routes) - [FiftyOne Enterprise v2.15+ Additional API Routes](#fiftyone-enterprise-v215-additional-api-routes) - [FiftyOne Enterprise v2.7+ Delegated Operator Changes](#fiftyone-enterprise-v27-delegated-operator-changes) @@ -98,6 +99,50 @@ quickstart 0.21.2 fiftyone migrate --info ``` +#### FiftyOne Enterprise v2.19+ Telemetry Sidecars + +FiftyOne Enterprise v2.19.0 adds a `telemetry-sidecar` service paired +with each `fiftyone-app`, `teams-api`, `teams-plugins`, and `teams-do*` +service, plus a `telemetry-redis` service that buffers the streamed +metrics/logs. Telemetry is enabled by default. + +> [!IMPORTANT] +> The sidecar powers the FiftyOne UI's delegated-operator log viewer. +> Disabling telemetry will leave that log viewer empty. + +**Resource impact.** Each sidecar reserves `0.10` CPUs and `512M` memory +(reservation == limit). A stock deploy adds four sidecars +(`fiftyone-app` + `teams-api` + `teams-plugins` + one `teams-do`), +so expect roughly **+0.4 CPU** and **+2 GiB memory** of container +overhead, plus the bundled `telemetry-redis` service +(`0.10` CPU / `256M` memory reservation, `0.25` / `512M` limits) and its +`telemetry-redis-data` named volume. + +**Host requirements.** + +1. **Docker Compose v2.17+** for `depends_on..restart: true` + semantics. Older versions will see stale PID namespaces after a + target container is recreated. +1. **Linux host with PID-namespace sharing** (`pid: "service:"`) + and **`SYS_PTRACE`** capability granted to each sidecar container. + Docker Desktop on macOS/Windows supports this, but some hardened + container runtimes (gVisor, Kata) do not — telemetry will fail to + attach to the target process there. +1. **`teams-do` is forced to `replicas: 1`** while telemetry is enabled, + because Compose's `pid: "service:"` only joins a single + replica. To run multiple delegated-operator workers, see + [`docker/docs/configuring-telemetry.md`](configuring-telemetry.md). + +**Opt-out.** Add a `compose.override.yaml` that scales the +`telemetry-redis` and `*-telemetry` services to `replicas: 0`. See +[`configuring-telemetry.md`](configuring-telemetry.md#opt-out) for the +full override snippet. + +**External Redis.** Point at a managed Redis instead of the bundled +one by setting `FIFTYONE_TELEMETRY_REDIS_URL` in your `.env` to a +fully-qualified URL (e.g. `redis://my-managed-redis.example.com:6379`) +and scaling `telemetry-redis` to `replicas: 0` as above. + #### FiftyOne Enterprise v2.16+ Additional API Routes FiftyOne Enterprise v2.16.0 adds the `/cloud_credentials` endpoint to the `teams-api`. diff --git a/helm/docs/upgrading.md b/helm/docs/upgrading.md index 873e5e93d..37b5f2422 100644 --- a/helm/docs/upgrading.md +++ b/helm/docs/upgrading.md @@ -19,6 +19,7 @@ - [Upgrading From Previous Versions](#upgrading-from-previous-versions) - [A Note On Database Migrations](#a-note-on-database-migrations) - [From FiftyOne Enterprise Version 2.0.0 or Higher](#from-fiftyone-enterprise-version-200-or-higher) + - [FiftyOne Enterprise v2.19+ Telemetry Sidecars](#fiftyone-enterprise-v219-telemetry-sidecars) - [FiftyOne Enterprise v2.16+ Additional API Routes](#fiftyone-enterprise-v216-additional-api-routes) - [FiftyOne Enterprise v2.15+ Additional API Routes](#fiftyone-enterprise-v215-additional-api-routes) - [FiftyOne Enterprise v2.9+ Startup Probe Changes](#fiftyone-enterprise-v29-startup-probe-changes) @@ -141,6 +142,65 @@ quickstart 0.21.2 fiftyone migrate --info ``` +#### FiftyOne Enterprise v2.19+ Telemetry Sidecars + +FiftyOne Enterprise v2.19.0 adds a `telemetry-sidecar` container to the +`teams-api`, `fiftyone-app`, `teams-plugins`, and delegated-operator +workloads (and as a native init-sidecar on delegated-operator `Job` pods), +plus an in-cluster Redis `Deployment` + `Service` + `PersistentVolumeClaim` +that buffers the streamed metrics/logs. Telemetry is enabled by default. + +> [!IMPORTANT] +> The sidecar powers the FiftyOne UI's delegated-operator log viewer. +> Disabling telemetry (`telemetry.enabled: false`) will leave that +> log viewer empty. + +**Resource impact.** Each sidecar requests `100m` CPU and `512Mi` memory +(request == limit). A stock deploy adds four sidecars +(`teams-api` + `fiftyone-app` + `teams-plugins` + one delegated-operator), +so expect roughly **+400m CPU** and **+2 GiB memory** of pod overhead, +plus the bundled Redis (`100m` / `256Mi` requests, `250m` / `512Mi` limits) +and its `1Gi` PVC. Tune via `telemetry.sidecar.resources` and +`telemetry.redis.resources` / `telemetry.redis.persistence.size`. + +**Cluster requirements.** + +1. **`shareProcessNamespace: true`** is set on all four workloads so + the sidecar can read `/proc//fd/1` of the target container. +1. **The sidecar runs as root with `SYS_PTRACE`** (required for + `py-spy` and `/proc` access). Clusters that enforce + [Pod Security Admission][psa] `restricted`, or admission + policies (OPA/Gatekeeper, Kyverno) that block + `runAsUser: 0` or capability adds, will reject these pods at + admission. On such clusters, either: + - allow the chart's `namespace.name` namespace at PSA `baseline` + (or relax the relevant admission policy), or + - disable telemetry with `telemetry.enabled: false`. + +**Opt-out.** Set the following in your `values.yaml`: + +```yaml +telemetry: + enabled: false +``` + +This skips every telemetry resource (Redis, sidecars, Role/RoleBinding) +and reverts the workloads to their pre-v2.19 shape. + +**External Redis.** To point at a managed Redis (ElastiCache, +MemoryStore, an existing cluster) instead of running the bundled one, +set: + +```yaml +telemetry: + redis: + external: + url: redis://my-managed-redis.example.com:6379 +``` + +The chart will skip the bundled `Deployment` / `Service` / `PVC` and +wire every consumer at this URL. + #### FiftyOne Enterprise v2.16+ Additional API Routes FiftyOne Enterprise v2.16.0 adds the `/cloud_credentials` endpoint to the `teams-api`. @@ -596,3 +656,4 @@ existing configuration to migrate to a new Auth0 Tenant. [init-containers]: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ +[psa]: https://kubernetes.io/docs/concepts/security/pod-security-admission/ From 2d170079424032798b4bf6f146a9e4975ff39ec0 Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Fri, 22 May 2026 14:18:32 -0400 Subject: [PATCH 44/49] refactor(helm): drop redundant teams-api SA from telemetry RoleBinding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The default telemetry.role.subjects list bound both the main app SA and the teams-api RBAC SA. But api-role.yaml already grants pods/log GET to the teams-api SA — binding it a second time via the telemetry Role is pure duplication. Drop the teams-api SA from the default subjects. The main app SA still covers sidecars on fiftyone-app, teams-plugins, and delegated-operator workloads. When apiSettings.rbac.create is false, api-deployment falls back to the main app SA anyway, which this binding still covers, so the api sidecar retains pods/log access across both rbac modes. Co-Authored-By: Claude Opus 4.7 (1M context) --- helm/fiftyone-teams-app/README.md | 2 +- .../templates/_telemetry.tpl | 12 ++++++---- helm/fiftyone-teams-app/values.schema.json | 2 +- helm/fiftyone-teams-app/values.yaml | 8 ++++--- tests/unit/helm/telemetry-rolebinding_test.go | 22 +++++++------------ 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index ea821c1c1..f96ba6eb5 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -1096,7 +1096,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | telemetry.redis.persistence.storageClass | string | `""` | `StorageClass` name for the telemetry Redis `PersistentVolumeClaim`. Leave unset to use the cluster's default `StorageClass`. | | telemetry.redis.podSecurityContext | object | `{"fsGroup":999,"runAsGroup":999,"runAsNonRoot":true,"runAsUser":999}` | Pod-level security attributes for the telemetry Redis. Defaults to a non-root UID/GID matching the `redis` user in the upstream `redis:7-alpine` image (999). `fsGroup` ensures the mounted `/data` volume is group-writable. [Reference][security-context]. | | telemetry.redis.resources | object | `{"limits":{"cpu":"250m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"256Mi"}}` | Resource requests/limits for the telemetry Redis container. [Reference][resources]. | -| telemetry.serviceAccounts | list | `[]` | ServiceAccount names (in `namespace.name`) bound to the telemetry pod-logs Role. Each entry becomes a `ServiceAccount` subject on the generated RoleBinding. When empty, the RoleBinding subjects default to the chart's main app service account and the teams-api RBAC service account so the telemetry sidecars can read their target's logs. | +| telemetry.serviceAccounts | list | `[]` | ServiceAccount names (in `namespace.name`) bound to the telemetry pod-logs Role. Each entry becomes a `ServiceAccount` subject on the generated RoleBinding. When empty, the RoleBinding binds the chart's main app service account (used by sidecars on `fiftyone-app`, `teams-plugins`, and the delegated-operator workloads). The teams-api sidecar uses the teams-api RBAC service account, which already has `pods/log` GET via `api-role.yaml` and does not need to be re-bound. | | telemetry.sidecar.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | | telemetry.sidecar.image.repository | string | `"voxel51/telemetry-sidecar"` | Container image for `telemetry-sidecar`. | | telemetry.sidecar.image.tag | string | `""` | Image tag for `telemetry-sidecar`. Defaults to `Chart.AppVersion`. | diff --git a/helm/fiftyone-teams-app/templates/_telemetry.tpl b/helm/fiftyone-teams-app/templates/_telemetry.tpl index fa243e826..92237072f 100644 --- a/helm/fiftyone-teams-app/templates/_telemetry.tpl +++ b/helm/fiftyone-teams-app/templates/_telemetry.tpl @@ -62,13 +62,17 @@ app.voxel51.com/component: telemetry {{/* Default subjects for the telemetry RoleBinding. When .Values.telemetry.serviceAccounts -is empty, bind to both the main app service account and the teams-api RBAC service -account (covering the SAs used by the auto-injected sidecars). +is empty, bind to the main app service account — the SA used by the auto-injected +sidecars on app/plugins/delegated-operator pods. + +The teams-api sidecar uses the teams-api RBAC service account, which is already +granted `pods/log` GET by api-role.yaml; binding it here would be a redundant +duplicate. When `apiSettings.rbac.create` is false, api-deployment falls back to +the main app SA anyway, which this RoleBinding still covers. */}} {{- define "telemetry.role.subjects" -}} {{- $appSA := include "fiftyone-teams-app.serviceAccountName" . | trim -}} -{{- $apiSA := include "teams-api-rbac.service-account-name" . | trim -}} -{{- $defaultSubjects := list $appSA $apiSA | uniq -}} +{{- $defaultSubjects := list $appSA -}} {{- $subjects := .Values.telemetry.serviceAccounts | default $defaultSubjects -}} {{- range $subjects }} - kind: ServiceAccount diff --git a/helm/fiftyone-teams-app/values.schema.json b/helm/fiftyone-teams-app/values.schema.json index 7137da5db..d72483c33 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -4114,7 +4114,7 @@ "type": "object" }, "serviceAccounts": { - "description": "ServiceAccount names (in `namespace.name`) bound to the telemetry\npod-logs Role. Each entry becomes a `ServiceAccount` subject on the\ngenerated RoleBinding. When empty, the RoleBinding subjects default to\nthe chart's main app service account and the teams-api RBAC service\naccount so the telemetry sidecars can read their target's logs.", + "description": "ServiceAccount names (in `namespace.name`) bound to the telemetry\npod-logs Role. Each entry becomes a `ServiceAccount` subject on the\ngenerated RoleBinding. When empty, the RoleBinding binds the chart's\nmain app service account (used by sidecars on `fiftyone-app`,\n`teams-plugins`, and the delegated-operator workloads). The teams-api\nsidecar uses the teams-api RBAC service account, which already has\n`pods/log` GET via `api-role.yaml` and does not need to be re-bound.", "items": { "required": [] }, diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index 42bb36634..f3dbd8631 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -1693,7 +1693,9 @@ telemetry: memory: 512Mi # -- ServiceAccount names (in `namespace.name`) bound to the telemetry # pod-logs Role. Each entry becomes a `ServiceAccount` subject on the - # generated RoleBinding. When empty, the RoleBinding subjects default to - # the chart's main app service account and the teams-api RBAC service - # account so the telemetry sidecars can read their target's logs. + # generated RoleBinding. When empty, the RoleBinding binds the chart's + # main app service account (used by sidecars on `fiftyone-app`, + # `teams-plugins`, and the delegated-operator workloads). The teams-api + # sidecar uses the teams-api RBAC service account, which already has + # `pods/log` GET via `api-role.yaml` and does not need to be re-bound. serviceAccounts: [] diff --git a/tests/unit/helm/telemetry-rolebinding_test.go b/tests/unit/helm/telemetry-rolebinding_test.go index 024bb4757..30db01ee2 100644 --- a/tests/unit/helm/telemetry-rolebinding_test.go +++ b/tests/unit/helm/telemetry-rolebinding_test.go @@ -131,8 +131,10 @@ func (s *telemetryRoleBindingTemplateTest) TestRoleRules() { func (s *telemetryRoleBindingTemplateTest) TestRoleBindingDefaultSubject() { // With an unset serviceAccounts list, the binding falls back to the - // chart's main app service account and the teams-api RBAC service - // account (the SAs used by auto-injected sidecars). + // chart's main app service account — the SA used by sidecars on + // fiftyone-app, teams-plugins, and delegated-operator workloads. The + // teams-api SA is intentionally NOT bound here; api-role.yaml already + // grants it pods/log GET. options := &helm.Options{SetValues: map[string]string{ "telemetry.enabled": "true", }} @@ -141,19 +143,11 @@ func (s *telemetryRoleBindingTemplateTest) TestRoleBindingDefaultSubject() { rb, ok := s.extractRoleBinding(output) s.Require().True(ok, "RoleBinding document not found in rendered output") - s.Require().Len(rb.Subjects, 2, "Default RoleBinding should bind to app + api-rbac SAs") - subjectNames := map[string]bool{} - for _, sub := range rb.Subjects { - s.Equal("ServiceAccount", sub.Kind) - s.Equal("fiftyone-teams", sub.Namespace) - subjectNames[sub.Name] = true - } + s.Require().Len(rb.Subjects, 1, "Default RoleBinding should bind only to the main app SA") + s.Equal("ServiceAccount", rb.Subjects[0].Kind) + s.Equal("fiftyone-teams", rb.Subjects[0].Namespace) // Main app SA: chart default serviceAccount.name = "fiftyone-teams" - mainAppSA := "fiftyone-teams" - // teams-api SA: -teams-api - apiSA := fmt.Sprintf("%s-fiftyone-teams-app-teams-api", s.releaseName) - s.True(subjectNames[mainAppSA], "should bind to main app SA %q", mainAppSA) - s.True(subjectNames[apiSA], "should bind to teams-api SA %q", apiSA) + s.Equal("fiftyone-teams", rb.Subjects[0].Name) } func (s *telemetryRoleBindingTemplateTest) TestRoleBindingMultipleSubjects() { From e6850fbce0465d83eee7c64e67c845b8fb33ba3f Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Fri, 22 May 2026 15:06:24 -0400 Subject: [PATCH 45/49] feat(helm): support pre-provisioned PVC for telemetry Redis Adds telemetry.redis.persistence.existingClaim so operators on clusters without a dynamic PV provisioner (or wanting to reuse a volume across releases) can hand the chart a pre-created PVC instead of relying on chart-managed provisioning. When set, the chart skips its PVC render and the Deployment mounts the named claim; persistence.enabled=false still wins and yields an emptyDir. Co-Authored-By: Claude Opus 4.7 (1M context) --- helm/fiftyone-teams-app/README.md | 1 + .../templates/telemetry-redis.yaml | 4 +- helm/fiftyone-teams-app/values.schema.json | 6 ++ helm/fiftyone-teams-app/values.yaml | 8 +++ tests/unit/helm/telemetry-redis_test.go | 70 +++++++++++++++++++ 5 files changed, 87 insertions(+), 2 deletions(-) diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index f96ba6eb5..150c879fc 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -1092,6 +1092,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | telemetry.redis.maxmemory | string | `"400mb"` | `--maxmemory` flag passed to `redis-server`. | | telemetry.redis.maxmemoryPolicy | string | `"allkeys-lru"` | `--maxmemory-policy` flag passed to `redis-server`. | | telemetry.redis.persistence.enabled | bool | `true` | Controls whether a `PersistentVolumeClaim` is created for the bundled telemetry Redis. When `false`, Redis runs with an `emptyDir` volume and state is lost on pod restart — fine for dev clusters without a dynamic PV provisioner, not for production. | +| telemetry.redis.persistence.existingClaim | string | `""` | Name of an existing `PersistentVolumeClaim` (in `namespace.name`) to bind the telemetry Redis to. When set, the chart will NOT create a `PersistentVolumeClaim` and the `size` / `storageClass` fields above are ignored — you are responsible for provisioning the claim out of band. Use this on clusters without a dynamic PV provisioner, or to reuse an existing volume across releases. Has no effect when `persistence.enabled` is `false`. | | telemetry.redis.persistence.size | string | `"1Gi"` | Storage size for the telemetry Redis `PersistentVolumeClaim`. | | telemetry.redis.persistence.storageClass | string | `""` | `StorageClass` name for the telemetry Redis `PersistentVolumeClaim`. Leave unset to use the cluster's default `StorageClass`. | | telemetry.redis.podSecurityContext | object | `{"fsGroup":999,"runAsGroup":999,"runAsNonRoot":true,"runAsUser":999}` | Pod-level security attributes for the telemetry Redis. Defaults to a non-root UID/GID matching the `redis` user in the upstream `redis:7-alpine` image (999). `fsGroup` ensures the mounted `/data` volume is group-writable. [Reference][security-context]. | diff --git a/helm/fiftyone-teams-app/templates/telemetry-redis.yaml b/helm/fiftyone-teams-app/templates/telemetry-redis.yaml index 088d88f14..d503cafce 100644 --- a/helm/fiftyone-teams-app/templates/telemetry-redis.yaml +++ b/helm/fiftyone-teams-app/templates/telemetry-redis.yaml @@ -1,5 +1,5 @@ {{- if and .Values.telemetry.enabled (not .Values.telemetry.redis.external.url) }} -{{- if .Values.telemetry.redis.persistence.enabled }} +{{- if and .Values.telemetry.redis.persistence.enabled (not .Values.telemetry.redis.persistence.existingClaim) }} apiVersion: v1 kind: PersistentVolumeClaim metadata: @@ -72,7 +72,7 @@ spec: - name: redis-data {{- if .Values.telemetry.redis.persistence.enabled }} persistentVolumeClaim: - claimName: {{ include "telemetry.redis.name" . }}-data + claimName: {{ .Values.telemetry.redis.persistence.existingClaim | default (printf "%s-data" (include "telemetry.redis.name" .)) }} {{- else }} emptyDir: {} {{- end }} diff --git a/helm/fiftyone-teams-app/values.schema.json b/helm/fiftyone-teams-app/values.schema.json index d72483c33..160b93c90 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -4017,6 +4017,12 @@ "title": "enabled", "type": "boolean" }, + "existingClaim": { + "default": "", + "description": "Name of an existing `PersistentVolumeClaim` (in `namespace.name`) to\nbind the telemetry Redis to. When set, the chart will NOT create a\n`PersistentVolumeClaim` and the `size` / `storageClass` fields above\nare ignored — you are responsible for provisioning the claim out of\nband. Use this on clusters without a dynamic PV provisioner, or to\nreuse an existing volume across releases. Has no effect when\n`persistence.enabled` is `false`.", + "title": "existingClaim", + "type": "string" + }, "size": { "default": "1Gi", "description": "Storage size for the telemetry Redis `PersistentVolumeClaim`.", diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index f3dbd8631..a383d7992 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -1632,6 +1632,14 @@ telemetry: # -- `StorageClass` name for the telemetry Redis `PersistentVolumeClaim`. # Leave unset to use the cluster's default `StorageClass`. storageClass: "" + # -- Name of an existing `PersistentVolumeClaim` (in `namespace.name`) to + # bind the telemetry Redis to. When set, the chart will NOT create a + # `PersistentVolumeClaim` and the `size` / `storageClass` fields above + # are ignored — you are responsible for provisioning the claim out of + # band. Use this on clusters without a dynamic PV provisioner, or to + # reuse an existing volume across releases. Has no effect when + # `persistence.enabled` is `false`. + existingClaim: "" # -- `--maxmemory` flag passed to `redis-server`. maxmemory: 400mb # -- `--maxmemory-policy` flag passed to `redis-server`. diff --git a/tests/unit/helm/telemetry-redis_test.go b/tests/unit/helm/telemetry-redis_test.go index 33c6dfff3..c8a67e245 100644 --- a/tests/unit/helm/telemetry-redis_test.go +++ b/tests/unit/helm/telemetry-redis_test.go @@ -344,3 +344,73 @@ func (s *telemetryRedisTemplateTest) TestPersistenceDisabledSkipsPVCAndUsesEmpty s.Nil(volumes[0].PersistentVolumeClaim, "redis-data volume should NOT reference a PVC when persistence is disabled") } + +// TestExistingClaimSkipsPVCAndMountsNamedClaim ensures that +// persistence.existingClaim points the Deployment at the named PVC and +// suppresses chart-managed PVC creation — the path for clusters without +// a dynamic PV provisioner where the operator pre-creates the claim. +func (s *telemetryRedisTemplateTest) TestExistingClaimSkipsPVCAndMountsNamedClaim() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.persistence.existingClaim": "my-prebuilt-redis-pvc", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + + s.NotContains(output, "kind: PersistentVolumeClaim", + "PVC should not render when persistence.existingClaim is set") + + deployment := s.extractDeployment(output) + volumes := deployment.Spec.Template.Spec.Volumes + s.Require().Len(volumes, 1, "Deployment should have exactly one volume") + s.Equal("redis-data", volumes[0].Name) + s.Require().NotNil(volumes[0].PersistentVolumeClaim, + "redis-data volume should reference a PVC when existingClaim is set") + s.Equal("my-prebuilt-redis-pvc", volumes[0].PersistentVolumeClaim.ClaimName, + "claimName should match the user-supplied existingClaim, not the chart-generated name") + s.Nil(volumes[0].EmptyDir, + "redis-data volume should NOT be emptyDir when existingClaim is set") +} + +// TestExistingClaimIgnoresStorageClassAndSize ensures that size/storageClass +// have no effect once the user has taken over claim provisioning via +// existingClaim — the chart has no PVC to apply them to. +func (s *telemetryRedisTemplateTest) TestExistingClaimIgnoresStorageClassAndSize() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.persistence.existingClaim": "my-prebuilt-redis-pvc", + "telemetry.redis.persistence.size": "100Gi", + "telemetry.redis.persistence.storageClass": "gp3", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + + s.NotContains(output, "kind: PersistentVolumeClaim", + "PVC should not render when existingClaim is set, even with size/storageClass") + s.NotContains(output, "100Gi", + "size should not leak into the rendered output when existingClaim is set") + s.NotContains(output, "storageClassName", + "storageClassName should not appear in any rendered object when existingClaim is set") +} + +// TestPersistenceDisabledBeatsExistingClaim ensures persistence.enabled=false +// remains the kill-switch — even with existingClaim set, the user opting out +// of persistence should yield an emptyDir volume rather than a stale claim +// mount. +func (s *telemetryRedisTemplateTest) TestPersistenceDisabledBeatsExistingClaim() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.persistence.enabled": "false", + "telemetry.redis.persistence.existingClaim": "my-prebuilt-redis-pvc", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + + s.NotContains(output, "kind: PersistentVolumeClaim", + "PVC should not render when persistence.enabled=false") + + deployment := s.extractDeployment(output) + volumes := deployment.Spec.Template.Spec.Volumes + s.Require().Len(volumes, 1) + s.NotNil(volumes[0].EmptyDir, + "persistence.enabled=false should take precedence and yield an emptyDir volume") + s.Nil(volumes[0].PersistentVolumeClaim, + "redis-data volume should NOT reference the existingClaim when persistence is disabled") +} From 43d140cb98cbf64f165b0237f3becf74f5360ca7 Mon Sep 17 00:00:00 2001 From: Alan Smith <209585+mo-getter@users.noreply.github.com> Date: Fri, 22 May 2026 15:52:52 -0400 Subject: [PATCH 46/49] docs: clarify upgrading docs --- docker/docs/upgrading.md | 53 ++++++++++++++++------------ helm/docs/upgrading.md | 76 +++++++++++++++++++++++----------------- 2 files changed, 73 insertions(+), 56 deletions(-) diff --git a/docker/docs/upgrading.md b/docker/docs/upgrading.md index 6d98efbe9..1ec68f928 100644 --- a/docker/docs/upgrading.md +++ b/docker/docs/upgrading.md @@ -20,6 +20,8 @@ - [A Note On Database Migrations](#a-note-on-database-migrations) - [From FiftyOne Enterprise Version 2.0.0 and Later](#from-fiftyone-enterprise-version-200-and-later) - [FiftyOne Enterprise v2.19+ Telemetry Sidecars](#fiftyone-enterprise-v219-telemetry-sidecars) + - [Host Requirements](#host-requirements) + - [Opting out of Telemetry](#opting-out-of-telemetry) - [FiftyOne Enterprise v2.16+ Additional API Routes](#fiftyone-enterprise-v216-additional-api-routes) - [FiftyOne Enterprise v2.15+ Additional API Routes](#fiftyone-enterprise-v215-additional-api-routes) - [FiftyOne Enterprise v2.7+ Delegated Operator Changes](#fiftyone-enterprise-v27-delegated-operator-changes) @@ -101,24 +103,21 @@ quickstart 0.21.2 #### FiftyOne Enterprise v2.19+ Telemetry Sidecars -FiftyOne Enterprise v2.19.0 adds a `telemetry-sidecar` service paired -with each `fiftyone-app`, `teams-api`, `teams-plugins`, and `teams-do*` -service, plus a `telemetry-redis` service that buffers the streamed -metrics/logs. Telemetry is enabled by default. +FiftyOne Enterprise v2.19.0 adds observability features that are viewable +by admins directly within the FiftyOne UI. +This is enabled by a `telemetry-sidecar` service paired with each +`fiftyone-app`, `teams-api`, `teams-plugins`, and `teams-do*` service, +plus a `telemetry-redis` service that buffers the streamed metrics/logs. -> [!IMPORTANT] -> The sidecar powers the FiftyOne UI's delegated-operator log viewer. -> Disabling telemetry will leave that log viewer empty. +**Resource impact:** Each sidecar reserves `0.10` CPUs and `512M` memory +(reservation == limit). +A stock deploy adds four sidecars (`fiftyone-app` + `teams-api` + +`teams-plugins` + one `teams-do`), so expect roughly **+0.4 CPU** and **+2 GiB +memory** of additional resource usage, plus the bundled `telemetry-redis` +service (`0.10` CPU / `256M` memory reservation, `0.25` / `512M` limits) and +its `telemetry-redis-data` named volume. -**Resource impact.** Each sidecar reserves `0.10` CPUs and `512M` memory -(reservation == limit). A stock deploy adds four sidecars -(`fiftyone-app` + `teams-api` + `teams-plugins` + one `teams-do`), -so expect roughly **+0.4 CPU** and **+2 GiB memory** of container -overhead, plus the bundled `telemetry-redis` service -(`0.10` CPU / `256M` memory reservation, `0.25` / `512M` limits) and its -`telemetry-redis-data` named volume. - -**Host requirements.** +##### Host Requirements 1. **Docker Compose v2.17+** for `depends_on..restart: true` semantics. Older versions will see stale PID namespaces after a @@ -133,15 +132,23 @@ overhead, plus the bundled `telemetry-redis` service replica. To run multiple delegated-operator workers, see [`docker/docs/configuring-telemetry.md`](configuring-telemetry.md). -**Opt-out.** Add a `compose.override.yaml` that scales the -`telemetry-redis` and `*-telemetry` services to `replicas: 0`. See -[`configuring-telemetry.md`](configuring-telemetry.md#opt-out) for the -full override snippet. - -**External Redis.** Point at a managed Redis instead of the bundled +**External Redis:** Point at a managed Redis instead of the bundled one by setting `FIFTYONE_TELEMETRY_REDIS_URL` in your `.env` to a fully-qualified URL (e.g. `redis://my-managed-redis.example.com:6379`) -and scaling `telemetry-redis` to `replicas: 0` as above. +and scaling `telemetry-redis` to `replicas: 0` as below. + +##### Opting out of Telemetry + +The new Telemetry features are enabled by default, but can be disabled by +adding a `compose.override.yaml` that scales the `telemetry-redis` and +`*-telemetry` services to `replicas: 0`. +See +[`configuring-telemetry.md`](configuring-telemetry.md#opt-out) +for the full override snippet. + +> [!IMPORTANT] +> The sidecar powers the FiftyOne UI's delegated-operator log viewer. +> Disabling telemetry will leave that log viewer empty. #### FiftyOne Enterprise v2.16+ Additional API Routes diff --git a/helm/docs/upgrading.md b/helm/docs/upgrading.md index 37b5f2422..7ccfd1f48 100644 --- a/helm/docs/upgrading.md +++ b/helm/docs/upgrading.md @@ -20,6 +20,8 @@ - [A Note On Database Migrations](#a-note-on-database-migrations) - [From FiftyOne Enterprise Version 2.0.0 or Higher](#from-fiftyone-enterprise-version-200-or-higher) - [FiftyOne Enterprise v2.19+ Telemetry Sidecars](#fiftyone-enterprise-v219-telemetry-sidecars) + - [Cluster Requirements](#cluster-requirements) + - [Opting out of Telemetry](#opting-out-of-telemetry) - [FiftyOne Enterprise v2.16+ Additional API Routes](#fiftyone-enterprise-v216-additional-api-routes) - [FiftyOne Enterprise v2.15+ Additional API Routes](#fiftyone-enterprise-v215-additional-api-routes) - [FiftyOne Enterprise v2.9+ Startup Probe Changes](#fiftyone-enterprise-v29-startup-probe-changes) @@ -144,50 +146,40 @@ quickstart 0.21.2 #### FiftyOne Enterprise v2.19+ Telemetry Sidecars -FiftyOne Enterprise v2.19.0 adds a `telemetry-sidecar` container to the +FiftyOne Enterprise v2.19.0 adds observability features that are viewable +by admins directly within the FiftyOne UI. +This is enabled by a `telemetry-sidecar` container, which is added to the `teams-api`, `fiftyone-app`, `teams-plugins`, and delegated-operator -workloads (and as a native init-sidecar on delegated-operator `Job` pods), -plus an in-cluster Redis `Deployment` + `Service` + `PersistentVolumeClaim` -that buffers the streamed metrics/logs. Telemetry is enabled by default. +workloads (and as a native init-sidecar on on-demand delegated-operator `Job` +pods), plus an in-cluster Redis `Deployment` + `Service` + +`PersistentVolumeClaim` that buffers the streamed metrics/logs. -> [!IMPORTANT] -> The sidecar powers the FiftyOne UI's delegated-operator log viewer. -> Disabling telemetry (`telemetry.enabled: false`) will leave that -> log viewer empty. +**Resource impact:** Each sidecar requests `100m` CPU and `512Mi` memory +(request == limit). +A stock deploy adds four sidecars (`teams-api` + `fiftyone-app` + +`teams-plugins` + one delegated-operator), so expect roughly **+400m CPU** and +**+2 GiB memory** of additional resource usage, plus the bundled Redis (`100m` +/ `256Mi` requests, `250m` / `512Mi` limits) and its `1Gi` PVC. +Tune via `telemetry.sidecar.resources` and `telemetry.redis.resources` / +`telemetry.redis.persistence.size`. -**Resource impact.** Each sidecar requests `100m` CPU and `512Mi` memory -(request == limit). A stock deploy adds four sidecars -(`teams-api` + `fiftyone-app` + `teams-plugins` + one delegated-operator), -so expect roughly **+400m CPU** and **+2 GiB memory** of pod overhead, -plus the bundled Redis (`100m` / `256Mi` requests, `250m` / `512Mi` limits) -and its `1Gi` PVC. Tune via `telemetry.sidecar.resources` and -`telemetry.redis.resources` / `telemetry.redis.persistence.size`. - -**Cluster requirements.** +##### Cluster Requirements 1. **`shareProcessNamespace: true`** is set on all four workloads so the sidecar can read `/proc//fd/1` of the target container. 1. **The sidecar runs as root with `SYS_PTRACE`** (required for - `py-spy` and `/proc` access). Clusters that enforce - [Pod Security Admission][psa] `restricted`, or admission - policies (OPA/Gatekeeper, Kyverno) that block + `py-spy` and `/proc` access). + Clusters that enforce + [Pod Security Admission][psa] + `restricted`, or admission policies (OPA/Gatekeeper, Kyverno) that block `runAsUser: 0` or capability adds, will reject these pods at - admission. On such clusters, either: + admission. + On such clusters, either: - allow the chart's `namespace.name` namespace at PSA `baseline` (or relax the relevant admission policy), or - disable telemetry with `telemetry.enabled: false`. -**Opt-out.** Set the following in your `values.yaml`: - -```yaml -telemetry: - enabled: false -``` - -This skips every telemetry resource (Redis, sidecars, Role/RoleBinding) -and reverts the workloads to their pre-v2.19 shape. - -**External Redis.** To point at a managed Redis (ElastiCache, +**External Redis:** To point at a managed Redis (ElastiCache, MemoryStore, an existing cluster) instead of running the bundled one, set: @@ -198,9 +190,27 @@ telemetry: url: redis://my-managed-redis.example.com:6379 ``` -The chart will skip the bundled `Deployment` / `Service` / `PVC` and +The chart will skip the bundled Redis' `Deployment` / `Service` / `PVC` and wire every consumer at this URL. +##### Opting out of Telemetry + +The new Telemetry features are enabled by default, but can be disabled by +setting the following in your `values.yaml` file: + +```yaml +telemetry: + enabled: false +``` + +> [!IMPORTANT] +> The sidecar powers the FiftyOne UI's delegated-operator log viewer. +> Disabling telemetry (`telemetry.enabled: false`) will leave that +> log viewer empty. + +This skips every telemetry resource (Redis, sidecars, Role/RoleBinding) +and causes workloads to match their pre-v2.19 shape. + #### FiftyOne Enterprise v2.16+ Additional API Routes FiftyOne Enterprise v2.16.0 adds the `/cloud_credentials` endpoint to the `teams-api`. From 63bc17ac4dd463e596386d993c64fb234f9c296e Mon Sep 17 00:00:00 2001 From: kacey Date: Sat, 23 May 2026 10:33:00 -0700 Subject: [PATCH 47/49] fix: rm sys_ptrace from services, explicit user rather than root, set persistence to false for redis --- docker/common-services.yaml | 22 +++- helm/fiftyone-teams-app/README.md | 16 +-- helm/fiftyone-teams-app/templates/NOTES.txt | 9 +- .../templates/_telemetry.tpl | 26 +++- .../templates/api-deployment.yaml | 2 +- .../templates/app-deployment.yaml | 2 +- ...elegated-operator-instance-deployment.yaml | 2 +- .../delegated-operator-job-configmap.yaml | 2 +- .../templates/plugins-deployment.yaml | 2 +- helm/fiftyone-teams-app/values.schema.json | 124 +++++++++++++++++- helm/fiftyone-teams-app/values.yaml | 52 ++++++-- tests/unit/helm/api-deployment_test.go | 8 +- tests/unit/helm/app-deployment_test.go | 8 +- ...gated-operator-instance-deployment_test.go | 16 +-- tests/unit/helm/plugins-deployment_test.go | 8 +- tests/unit/helm/telemetry-redis_test.go | 9 +- tests/unit/helm/telemetry-sidecar_test.go | 98 +++++++++++--- ...-cpu-default-override-template-values.yaml | 3 + .../expected-cpu-default.yaml | 5 + ...d-override-example-cascading-template.yaml | 3 + .../expected-override-example-template.yaml | 3 + 21 files changed, 342 insertions(+), 78 deletions(-) diff --git a/docker/common-services.yaml b/docker/common-services.yaml index 7b122d119..494850059 100644 --- a/docker/common-services.yaml +++ b/docker/common-services.yaml @@ -227,8 +227,10 @@ services: fiftyone-app-telemetry-common: image: voxel51/telemetry-sidecar:v2.19.0 pid: "service:fiftyone-app" - cap_add: - - SYS_PTRACE + cap_drop: + - ALL + security_opt: + - no-new-privileges:true environment: POD_NAME: fiftyone-app POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} @@ -248,8 +250,10 @@ services: teams-api-telemetry-common: image: voxel51/telemetry-sidecar:v2.19.0 pid: "service:teams-api" - cap_add: - - SYS_PTRACE + cap_drop: + - ALL + security_opt: + - no-new-privileges:true environment: POD_NAME: teams-api POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} @@ -269,8 +273,10 @@ services: teams-plugins-telemetry-common: image: voxel51/telemetry-sidecar:v2.19.0 pid: "service:teams-plugins" - cap_add: - - SYS_PTRACE + cap_drop: + - ALL + security_opt: + - no-new-privileges:true environment: POD_NAME: teams-plugins POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} @@ -290,8 +296,12 @@ services: teams-do-telemetry-common: image: voxel51/telemetry-sidecar:v2.19.0 pid: "service:teams-do" + cap_drop: + - ALL cap_add: - SYS_PTRACE + security_opt: + - no-new-privileges:true environment: POD_NAME: teams-do POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 150c879fc..1ed8cfe29 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -745,7 +745,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | apiSettings.podDisruptionBudget | object | `{"enabled":false,"minAvailable":null}` | Pod Disruption Budget for pods for `teams-api`. [Reference][pod-disruption-budget]. | | apiSettings.podDisruptionBudget.enabled | bool | `false` | Whether a pod disruption budget is enabled for `teams-api`. | | apiSettings.podDisruptionBudget.minAvailable | string | `nil` | Sets the minimum available or maximum unavailable replicas for the deployment object. Either integers or percentages supported. `maxUnavailable` is also supported, however, only one setting can be used at a time. If both are set, `minAvailable` will be preferred. | -| apiSettings.podSecurityContext | object | `{}` | Pod-level security attributes and common container settings for `teams-api`. [Reference][security-context]. | +| apiSettings.podSecurityContext | object | `{"fsGroup":1000,"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000}` | Pod-level security attributes and common container settings for `teams-api`. [Reference][security-context]. Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. | | apiSettings.rbac | object | `{"create":true,"role":{"annotations":{},"labels":{},"name":""},"roleBinding":{"annotations":{},"create":true,"labels":{},"name":""},"serviceAccount":{"annotations":{},"create":true,"labels":{},"name":""}}` | RBAC roles, bindings, and service accounts which will be used to submit on-demand delegated operators to the kubernetes API. If `apiSettings.rbac.create=true`, these will be used by the `teams-api` pods. | | apiSettings.rbac.create | bool | `true` | Controls whether to create the `Role`, `RoleBinding`, and `ServiceAccount` for on-demand delegated-operator submission. | | apiSettings.rbac.role.annotations | object | `{}` | `Role` annotations. [Reference][annotations]. | @@ -812,7 +812,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | appSettings.podDisruptionBudget | object | `{"enabled":false,"minAvailable":null}` | Pod Disruption Budget for pods for `fiftyone-app`. [Reference][pod-disruption-budget]. | | appSettings.podDisruptionBudget.enabled | bool | `false` | Whether a pod disruption budget is enabled for `fiftyone-app`. | | appSettings.podDisruptionBudget.minAvailable | string | `nil` | Sets the minimum available or maximum unavailable replicas for the deployment object. Either integers or percentages supported. `maxUnavailable` is also supported, however, only one setting can be used at a time. If both are set, `minAvailable` will be preferred. | -| appSettings.podSecurityContext | object | `{}` | Pod-level security attributes and common container settings for `fiftyone-app`. [Reference][security-context]. | +| appSettings.podSecurityContext | object | `{"fsGroup":1000,"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000}` | Pod-level security attributes and common container settings for `fiftyone-app`. [Reference][security-context]. Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. | | appSettings.readiness.failureThreshold | int | `5` | Number of times to retry the readiness probe for the `fiftyone-app`. [Reference][probes]. | | appSettings.readiness.periodSeconds | int | `15` | How often (in seconds) to perform the readiness probe for `fiftyone-app`. [Reference][probes]. | | appSettings.readiness.timeoutSeconds | int | `5` | Timeout for the readiness probe for the `fiftyone-app`. [Reference][probes]. | @@ -885,7 +885,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | casSettings.volumes | list | `[]` | Volumes for `teams-cas`. [Reference][volumes]. | | delegatedOperatorDeployments.deployments | object | `{"teamsDoCpuDefault":{"enabled":false,"env":{"FIFTYONE_MEDIA_CACHE_DIR":"/opt/media_cache","FIFTYONE_MEDIA_CACHE_SIZE_BYTES":"2147483648"},"replicaCount":1,"resources":{"limits":{"cpu":4,"ephemeral-storage":"1Gi","memory":"16Gi"},"requests":{"cpu":4,"ephemeral-storage":"1Gi","memory":"16Gi"}},"volumeMounts":[{"mountPath":"/dev/shm","name":"shm-vol"},{"mountPath":"/opt/media_cache","name":"memory-media-cache-vol"}],"volumes":[{"emptyDir":{"medium":"Memory","sizeLimit":"2Gi"},"name":"shm-vol"},{"emptyDir":{"medium":"Memory","sizeLimit":"2.5Gi"},"name":"memory-media-cache-vol"}]}}` | Additional deployments to configure. Each template will use .Values.delegatedOperatorDeployments.template as a base. Each template value may be overridden. Maps/dictionaries will be merged key-wise, with the deployment instance taking precedence. List values will not be merged, but be overridden completely by the deployment instance. | | delegatedOperatorDeployments.deployments.teamsDoCpuDefault | object | `{"enabled":false,"env":{"FIFTYONE_MEDIA_CACHE_DIR":"/opt/media_cache","FIFTYONE_MEDIA_CACHE_SIZE_BYTES":"2147483648"},"replicaCount":1,"resources":{"limits":{"cpu":4,"ephemeral-storage":"1Gi","memory":"16Gi"},"requests":{"cpu":4,"ephemeral-storage":"1Gi","memory":"16Gi"}},"volumeMounts":[{"mountPath":"/dev/shm","name":"shm-vol"},{"mountPath":"/opt/media_cache","name":"memory-media-cache-vol"}],"volumes":[{"emptyDir":{"medium":"Memory","sizeLimit":"2Gi"},"name":"shm-vol"},{"emptyDir":{"medium":"Memory","sizeLimit":"2.5Gi"},"name":"memory-media-cache-vol"}]}` | Default (CPU-only) delegated operator runner. Defaults to an 4vCPU, 16Gi RAM runner. The deployment is backed with a 2.5Gi in-memory volume for caching media and an extra 2Gi of shared memory. | -| delegatedOperatorDeployments.template | object | `{"affinity":{},"deploymentAnnotations":{},"description":"","env":{"FIFTYONE_DELEGATED_OPERATION_LOG_PATH":"","FIFTYONE_INTERNAL_SERVICE":true,"FIFTYONE_MEDIA_CACHE_SIZE_BYTES":-1},"image":{"pullPolicy":"Always","repository":"voxel51/fiftyone-teams-cv-full","tag":""},"labels":{},"liveness":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"nodeSelector":{},"podAnnotations":{},"podDisruptionBudget":{"enabled":false,"minAvailable":null},"podSecurityContext":{},"readiness":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"replicaCount":3,"resources":{"limits":{},"requests":{}},"secretEnv":{},"securityContext":{},"startup":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"tolerations":[],"topologySpreadConstraints":[],"updateStrategy":{"type":"RollingUpdate"},"volumeMounts":[],"volumes":[]}` | A common template applied to all deployments. Each deployment can then override individual fields as needed by the operator. | +| delegatedOperatorDeployments.template | object | `{"affinity":{},"deploymentAnnotations":{},"description":"","env":{"FIFTYONE_DELEGATED_OPERATION_LOG_PATH":"","FIFTYONE_INTERNAL_SERVICE":true,"FIFTYONE_MEDIA_CACHE_SIZE_BYTES":-1},"image":{"pullPolicy":"Always","repository":"voxel51/fiftyone-teams-cv-full","tag":""},"labels":{},"liveness":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"nodeSelector":{},"podAnnotations":{},"podDisruptionBudget":{"enabled":false,"minAvailable":null},"podSecurityContext":{"fsGroup":1000,"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000},"readiness":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"replicaCount":3,"resources":{"limits":{},"requests":{}},"secretEnv":{},"securityContext":{},"startup":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"tolerations":[],"topologySpreadConstraints":[],"updateStrategy":{"type":"RollingUpdate"},"volumeMounts":[],"volumes":[]}` | A common template applied to all deployments. Each deployment can then override individual fields as needed by the operator. | | delegatedOperatorDeployments.template.affinity | object | `{}` | Affinity and anti-affinity for `delegated-operator-executor`. [Reference][affinity]. | | delegatedOperatorDeployments.template.deploymentAnnotations | object | `{}` | Annotations for the `teams-do` deployment. [Reference][annotations]. | | delegatedOperatorDeployments.template.description | string | `""` | A description for the delegated operator instance. This is unused in the template context. Each operator should either set their own description or, optionally, use the default. If unset at the operator context, it will be defaulted to `Long running operations delegated to $name` where `$name` is the name of the Deployment object. | @@ -903,7 +903,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | delegatedOperatorDeployments.template.podAnnotations | object | `{}` | Annotations for delegated-operator-executor pods. [Reference][annotations]. | | delegatedOperatorDeployments.template.podDisruptionBudget.enabled | bool | `false` | Whether a pod disruption budget is enabled for `teams-plugins`. | | delegatedOperatorDeployments.template.podDisruptionBudget.minAvailable | string | `nil` | Sets the minimum available or maximum unavailable replicas for the deployment object. Either integers or percentages supported. `maxUnavailable` is also supported, however, only one setting can be used at a time. If both are set, `minAvailable` will be preferred. | -| delegatedOperatorDeployments.template.podSecurityContext | object | `{}` | Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context]. | +| delegatedOperatorDeployments.template.podSecurityContext | object | `{"fsGroup":1000,"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000}` | Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context]. Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. | | delegatedOperatorDeployments.template.readiness.failureThreshold | int | `5` | Number of times to retry the readiness probe for the `teams-do`. [Reference][probes]. | | delegatedOperatorDeployments.template.readiness.periodSeconds | int | `30` | How often (in seconds) to perform the readiness probe for `teams-do`. [Reference][probes]. | | delegatedOperatorDeployments.template.readiness.timeoutSeconds | int | `30` | Timeout for the readiness probe for the `teams-do`. [Reference][probes]. | @@ -924,7 +924,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | delegatedOperatorJobTemplates.configMap.labels | object | `{}` | Additional labels for the generated `ConfigMap`. [Reference][labels-and-selectors]. | | delegatedOperatorJobTemplates.configMap.name | string | `""` | Name of the `ConfigMap` (existing or to be created) in the namespace `namespace.name` used for DO templates. Defaults to `release-name-fiftyone-teams-app-do-templates`. | | delegatedOperatorJobTemplates.jobs | object | `{}` | On-Demand Delegated Operator Jobs. | -| delegatedOperatorJobTemplates.template | object | `{"activeDeadlineSeconds":null,"affinity":{},"backoffLimit":null,"completions":null,"containerSecurityContext":{},"env":{"FIFTYONE_DELEGATED_OPERATION_LOG_PATH":"","FIFTYONE_MEDIA_CACHE_SIZE_BYTES":-1},"image":{"pullPolicy":"Always","repository":"voxel51/fiftyone-teams-cv-full","tag":""},"jobAnnotations":{},"labels":{},"nodeSelector":{},"podAnnotations":{},"podSecurityContext":{},"resources":{"limits":{},"requests":{}},"secretEnv":{},"tolerations":[],"ttlSecondsAfterFinished":null,"volumeMounts":[],"volumes":[]}` | A common template applied to all deployments. Each deployment can then override individual fields as needed by the operator. | +| delegatedOperatorJobTemplates.template | object | `{"activeDeadlineSeconds":null,"affinity":{},"backoffLimit":null,"completions":null,"containerSecurityContext":{},"env":{"FIFTYONE_DELEGATED_OPERATION_LOG_PATH":"","FIFTYONE_MEDIA_CACHE_SIZE_BYTES":-1},"image":{"pullPolicy":"Always","repository":"voxel51/fiftyone-teams-cv-full","tag":""},"jobAnnotations":{},"labels":{},"nodeSelector":{},"podAnnotations":{},"podSecurityContext":{"fsGroup":1000,"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000},"resources":{"limits":{},"requests":{}},"secretEnv":{},"tolerations":[],"ttlSecondsAfterFinished":null,"volumeMounts":[],"volumes":[]}` | A common template applied to all deployments. Each deployment can then override individual fields as needed by the operator. | | delegatedOperatorJobTemplates.template.activeDeadlineSeconds | optional | `nil` | Maximum of seconds a job should be able to run. [Reference][job-termination-and-cleanup]. | | delegatedOperatorJobTemplates.template.affinity | object | `{}` | Affinity and anti-affinity for `delegated-operator-executor`. [Reference][affinity]. | | delegatedOperatorJobTemplates.template.backoffLimit | optional | `nil` | Amount of retries for the job to try before being marked as "Failed". [Reference][job-backoff-failure-policy]. | @@ -939,7 +939,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | delegatedOperatorJobTemplates.template.labels | object | `{}` | Additional labels for the `delegated-operator-executor` related objects. [Reference][labels-and-selectors]. | | delegatedOperatorJobTemplates.template.nodeSelector | object | `{}` | nodeSelector for `delegated-operator-executor`. [Reference][node-selector]. | | delegatedOperatorJobTemplates.template.podAnnotations | object | `{}` | Annotations for delegated-operator-executor pods. [Reference][annotations]. | -| delegatedOperatorJobTemplates.template.podSecurityContext | object | `{}` | Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context]. | +| delegatedOperatorJobTemplates.template.podSecurityContext | object | `{"fsGroup":1000,"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000}` | Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context]. Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. | | delegatedOperatorJobTemplates.template.resources | object | `{"limits":{},"requests":{}}` | Container resource requests and limits for `delegated-operator-executor`. [Reference][resources]. | | delegatedOperatorJobTemplates.template.secretEnv | object | `{}` | Secret variables to be passed to the delegated-operator-executor containers. | | delegatedOperatorJobTemplates.template.tolerations | list | `[]` | Allow the k8s scheduler to schedule delegated-operator-executor pods with matching taints. [Reference][taints-and-tolerations]. | @@ -994,7 +994,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | pluginsSettings.podDisruptionBudget | object | `{"enabled":false,"minAvailable":null}` | Pod Disruption Budget for pods for `teams-plugins`. [Reference][pod-disruption-budget]. | | pluginsSettings.podDisruptionBudget.enabled | bool | `false` | Whether a pod disruption budget is enabled for `teams-plugins`. | | pluginsSettings.podDisruptionBudget.minAvailable | string | `nil` | Sets the minimum available or maximum unavailable replicas for the deployment object. Either integers or percentages supported. `maxUnavailable` is also supported, however, only one setting can be used at a time. If both are set, `minAvailable` will be preferred. | -| pluginsSettings.podSecurityContext | object | `{}` | Pod-level security attributes and common container settings for `teams-plugins`. [Reference][security-context]. | +| pluginsSettings.podSecurityContext | object | `{"fsGroup":1000,"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000}` | Pod-level security attributes and common container settings for `teams-plugins`. [Reference][security-context]. Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. | | pluginsSettings.readiness.failureThreshold | int | `5` | Number of times to retry the readiness probe for the `teams-plugins`. [Reference][probes]. | | pluginsSettings.readiness.periodSeconds | int | `15` | How often (in seconds) to perform the readiness probe for `teams-plugins`. [Reference][probes]. | | pluginsSettings.readiness.timeoutSeconds | int | `5` | Timeout for the readiness probe for the `teams-plugins`. [Reference][probes]. | @@ -1091,7 +1091,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | telemetry.redis.image | string | `"redis:7-alpine"` | Container image for the telemetry Redis Deployment. | | telemetry.redis.maxmemory | string | `"400mb"` | `--maxmemory` flag passed to `redis-server`. | | telemetry.redis.maxmemoryPolicy | string | `"allkeys-lru"` | `--maxmemory-policy` flag passed to `redis-server`. | -| telemetry.redis.persistence.enabled | bool | `true` | Controls whether a `PersistentVolumeClaim` is created for the bundled telemetry Redis. When `false`, Redis runs with an `emptyDir` volume and state is lost on pod restart — fine for dev clusters without a dynamic PV provisioner, not for production. | +| telemetry.redis.persistence.enabled | bool | `false` | Controls whether a `PersistentVolumeClaim` is created for the bundled telemetry Redis. Defaults to `false` (emptyDir) so the chart installs cleanly on clusters without a dynamic PV provisioner or a default `StorageClass`. Telemetry archives flush to MongoDB every ~10 minutes (`metrics_history`, `service_logs`, completed DO runs), so what you lose on a Redis pod restart is only the in-window dashboard backscroll (~10 minutes of un-archived data, plus any live op buffers). Other workload crashes do NOT affect Redis state — only a Redis pod restart drops the in-memory window. Set to `true` to provision a PVC and have Redis recover its in-window data across pod restarts (requires a working `StorageClass`, or set `existingClaim` below to point at a pre-provisioned PVC). | | telemetry.redis.persistence.existingClaim | string | `""` | Name of an existing `PersistentVolumeClaim` (in `namespace.name`) to bind the telemetry Redis to. When set, the chart will NOT create a `PersistentVolumeClaim` and the `size` / `storageClass` fields above are ignored — you are responsible for provisioning the claim out of band. Use this on clusters without a dynamic PV provisioner, or to reuse an existing volume across releases. Has no effect when `persistence.enabled` is `false`. | | telemetry.redis.persistence.size | string | `"1Gi"` | Storage size for the telemetry Redis `PersistentVolumeClaim`. | | telemetry.redis.persistence.storageClass | string | `""` | `StorageClass` name for the telemetry Redis `PersistentVolumeClaim`. Leave unset to use the cluster's default `StorageClass`. | diff --git a/helm/fiftyone-teams-app/templates/NOTES.txt b/helm/fiftyone-teams-app/templates/NOTES.txt index c85b16b50..694f3cdf3 100644 --- a/helm/fiftyone-teams-app/templates/NOTES.txt +++ b/helm/fiftyone-teams-app/templates/NOTES.txt @@ -4,6 +4,13 @@ apiSettings.replicaCount will be set to 1 for this deployment. Please see https://helm.fiftyone.ai for details. {{ end }} - +{{- if and .Values.telemetry.enabled (not .Values.telemetry.redis.external.url) (not .Values.telemetry.redis.persistence.enabled) }} +[INFO] Telemetry Redis is running on an emptyDir volume (no PersistentVolumeClaim). + Long-term telemetry archives in MongoDB are unaffected, but the in-window + dashboard backscroll (~10 minutes of un-archived data) is lost if the + Redis pod restarts. To make telemetry survive Redis restarts, set + `telemetry.redis.persistence.enabled: true` (needs a working StorageClass + or `telemetry.redis.persistence.existingClaim` pointing at a pre-created PVC). +{{ end }} Visit the following URL to access your FiftyOne Enterprise application: http{{ if $.Values.ingress.tlsEnabled }}s{{ end }}://{{ .Values.teamsAppSettings.dnsName }} diff --git a/helm/fiftyone-teams-app/templates/_telemetry.tpl b/helm/fiftyone-teams-app/templates/_telemetry.tpl index 92237072f..2daccc79e 100644 --- a/helm/fiftyone-teams-app/templates/_telemetry.tpl +++ b/helm/fiftyone-teams-app/templates/_telemetry.tpl @@ -144,14 +144,21 @@ Inputs: same dict as telemetry.sidecar-env. {{- toYaml . | nindent 4 }} {{- end }} securityContext: - # The sidecar image runs as root (SYS_PTRACE + /proc//fd/1 access - # require it). Explicit container-level override so it works even when - # the pod's podSecurityContext sets runAsNonRoot: true. + # Match the paired workload's UID so same-UID /proc reads work without elevated caps. + {{- if .targetUid }} + runAsUser: {{ .targetUid }} + runAsNonRoot: {{ ne (int .targetUid) 0 }} + {{- else }} runAsNonRoot: false runAsUser: 0 + {{- end }} + allowPrivilegeEscalation: false capabilities: + drop: ["ALL"] + {{- if .executor }} add: - SYS_PTRACE + {{- end }} {{- if .executor }} volumeMounts: - name: telemetry-socket @@ -184,14 +191,21 @@ would block Job completion. failureThreshold: 30 periodSeconds: 1 securityContext: - # The sidecar image runs as root (SYS_PTRACE + /proc//fd/1 access - # require it). Explicit container-level override so it works even when - # the pod's podSecurityContext sets runAsNonRoot: true. + # Match the paired workload's UID so same-UID /proc reads work without elevated caps. + {{- if .targetUid }} + runAsUser: {{ .targetUid }} + runAsNonRoot: {{ ne (int .targetUid) 0 }} + {{- else }} runAsNonRoot: false runAsUser: 0 + {{- end }} + allowPrivilegeEscalation: false capabilities: + drop: ["ALL"] + {{- if .executor }} add: - SYS_PTRACE + {{- end }} volumeMounts: - name: telemetry-socket mountPath: /tmp/telemetry diff --git a/helm/fiftyone-teams-app/templates/api-deployment.yaml b/helm/fiftyone-teams-app/templates/api-deployment.yaml index 04ce551c6..c03eb54c2 100644 --- a/helm/fiftyone-teams-app/templates/api-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/api-deployment.yaml @@ -93,7 +93,7 @@ spec: {{- toYaml . | nindent 12 }} {{- end }} {{- if .Values.telemetry.enabled }} - {{- include "telemetry.sidecar" (dict "ctx" . "serviceType" "teams-api" "targetName" "fiftyone-teams-api") | nindent 8 }} + {{- include "telemetry.sidecar" (dict "ctx" . "serviceType" "teams-api" "targetName" "fiftyone-teams-api" "targetUid" ((.Values.apiSettings.podSecurityContext | default dict).runAsUser)) | nindent 8 }} {{- end }} {{- if .Values.apiSettings.initContainers.enabled }} initContainers: diff --git a/helm/fiftyone-teams-app/templates/app-deployment.yaml b/helm/fiftyone-teams-app/templates/app-deployment.yaml index 03ca186f8..0e3e1e9bb 100644 --- a/helm/fiftyone-teams-app/templates/app-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/app-deployment.yaml @@ -77,7 +77,7 @@ spec: {{- toYaml . | nindent 12 }} {{- end }} {{- if .Values.telemetry.enabled }} - {{- include "telemetry.sidecar" (dict "ctx" . "serviceType" "fiftyone-app" "targetName" "hypercorn") | nindent 8 }} + {{- include "telemetry.sidecar" (dict "ctx" . "serviceType" "fiftyone-app" "targetName" "hypercorn" "targetUid" ((.Values.appSettings.podSecurityContext | default dict).runAsUser)) | nindent 8 }} {{- end }} {{- if .Values.appSettings.initContainers.enabled }} initContainers: diff --git a/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml b/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml index ed264a941..501227b13 100644 --- a/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml @@ -121,7 +121,7 @@ spec: periodSeconds: {{ ($v.startup).periodSeconds | default $baseTpl.startup.periodSeconds }} timeoutSeconds: {{ ($v.startup).timeoutSeconds | default $baseTpl.startup.timeoutSeconds }} {{- if $.Values.telemetry.enabled }} - {{- include "telemetry.sidecar" (dict "ctx" $ "serviceType" "delegated-operator" "targetName" "fiftyone delegated" "executor" true) | nindent 8 }} + {{- include "telemetry.sidecar" (dict "ctx" $ "serviceType" "delegated-operator" "targetName" "fiftyone delegated" "executor" true "targetUid" (coalesce (($v.podSecurityContext | default dict).runAsUser) (($baseTpl.podSecurityContext | default dict).runAsUser))) | nindent 8 }} {{- end }} {{- with (merge (dict) ($v.nodeSelector | default dict) ($baseTpl.nodeSelector)) }} nodeSelector: diff --git a/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml b/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml index 22d2d9ef2..d19d8ac8a 100644 --- a/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml +++ b/helm/fiftyone-teams-app/templates/delegated-operator-job-configmap.yaml @@ -143,7 +143,7 @@ data: {{- end }} {{- if $.Values.telemetry.enabled }} initContainers: - {{- include "telemetry.native-sidecar" (dict "ctx" $ "serviceType" "delegated-operator" "targetName" "fiftyone delegated" "targetContainer" "executor" "executor" true) | nindent 12 }} + {{- include "telemetry.native-sidecar" (dict "ctx" $ "serviceType" "delegated-operator" "targetName" "fiftyone delegated" "targetContainer" "executor" "executor" true "targetUid" ($mergedPodSecurityContext.runAsUser)) | nindent 12 }} {{- end }} {{- with $mergedNodeSelector }} nodeSelector: diff --git a/helm/fiftyone-teams-app/templates/plugins-deployment.yaml b/helm/fiftyone-teams-app/templates/plugins-deployment.yaml index b564f9d67..44f2baf01 100644 --- a/helm/fiftyone-teams-app/templates/plugins-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/plugins-deployment.yaml @@ -78,7 +78,7 @@ spec: {{- toYaml . | nindent 12 }} {{- end }} {{- if .Values.telemetry.enabled }} - {{- include "telemetry.sidecar" (dict "ctx" . "serviceType" "teams-plugins" "targetName" "hypercorn") | nindent 8 }} + {{- include "telemetry.sidecar" (dict "ctx" . "serviceType" "teams-plugins" "targetName" "hypercorn" "targetUid" ((.Values.pluginsSettings.podSecurityContext | default dict).runAsUser)) | nindent 8 }} {{- end }} {{- if .Values.pluginsSettings.initContainers.enabled }} initContainers: diff --git a/helm/fiftyone-teams-app/values.schema.json b/helm/fiftyone-teams-app/values.schema.json index 160b93c90..da1d9780f 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -367,7 +367,29 @@ "type": "object" }, "podSecurityContext": { - "description": "Pod-level security attributes and common container settings for `teams-api`. [Reference][security-context].", + "description": "Pod-level security attributes and common container settings for `teams-api`. [Reference][security-context].\nImage runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload.", + "properties": { + "fsGroup": { + "default": 1000, + "title": "fsGroup", + "type": "integer" + }, + "runAsGroup": { + "default": 1000, + "title": "runAsGroup", + "type": "integer" + }, + "runAsNonRoot": { + "default": true, + "title": "runAsNonRoot", + "type": "boolean" + }, + "runAsUser": { + "default": 1000, + "title": "runAsUser", + "type": "integer" + } + }, "required": [], "title": "podSecurityContext", "type": "object" @@ -1055,7 +1077,29 @@ "type": "object" }, "podSecurityContext": { - "description": "Pod-level security attributes and common container settings for `fiftyone-app`. [Reference][security-context].", + "description": "Pod-level security attributes and common container settings for `fiftyone-app`. [Reference][security-context].\nImage runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload.", + "properties": { + "fsGroup": { + "default": 1000, + "title": "fsGroup", + "type": "integer" + }, + "runAsGroup": { + "default": 1000, + "title": "runAsGroup", + "type": "integer" + }, + "runAsNonRoot": { + "default": true, + "title": "runAsNonRoot", + "type": "boolean" + }, + "runAsUser": { + "default": 1000, + "title": "runAsUser", + "type": "integer" + } + }, "required": [], "title": "podSecurityContext", "type": "object" @@ -2129,7 +2173,29 @@ "type": "object" }, "podSecurityContext": { - "description": "Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context].", + "description": "Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context].\nImage runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload.", + "properties": { + "fsGroup": { + "default": 1000, + "title": "fsGroup", + "type": "integer" + }, + "runAsGroup": { + "default": 1000, + "title": "runAsGroup", + "type": "integer" + }, + "runAsNonRoot": { + "default": true, + "title": "runAsNonRoot", + "type": "boolean" + }, + "runAsUser": { + "default": 1000, + "title": "runAsUser", + "type": "integer" + } + }, "required": [], "title": "podSecurityContext", "type": "object" @@ -2445,7 +2511,29 @@ "type": "object" }, "podSecurityContext": { - "description": "Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context].", + "description": "Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context].\nImage runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload.", + "properties": { + "fsGroup": { + "default": 1000, + "title": "fsGroup", + "type": "integer" + }, + "runAsGroup": { + "default": 1000, + "title": "runAsGroup", + "type": "integer" + }, + "runAsNonRoot": { + "default": true, + "title": "runAsNonRoot", + "type": "boolean" + }, + "runAsUser": { + "default": 1000, + "title": "runAsUser", + "type": "integer" + } + }, "required": [], "title": "podSecurityContext", "type": "object" @@ -3052,7 +3140,29 @@ "type": "object" }, "podSecurityContext": { - "description": "Pod-level security attributes and common container settings for `teams-plugins`. [Reference][security-context].", + "description": "Pod-level security attributes and common container settings for `teams-plugins`. [Reference][security-context].\nImage runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload.", + "properties": { + "fsGroup": { + "default": 1000, + "title": "fsGroup", + "type": "integer" + }, + "runAsGroup": { + "default": 1000, + "title": "runAsGroup", + "type": "integer" + }, + "runAsNonRoot": { + "default": true, + "title": "runAsNonRoot", + "type": "boolean" + }, + "runAsUser": { + "default": 1000, + "title": "runAsUser", + "type": "integer" + } + }, "required": [], "title": "podSecurityContext", "type": "object" @@ -4012,8 +4122,8 @@ "persistence": { "properties": { "enabled": { - "default": true, - "description": "Controls whether a `PersistentVolumeClaim` is created for the\nbundled telemetry Redis. When `false`, Redis runs with an `emptyDir`\nvolume and state is lost on pod restart — fine for dev clusters\nwithout a dynamic PV provisioner, not for production.", + "default": false, + "description": "Controls whether a `PersistentVolumeClaim` is created for the\nbundled telemetry Redis. Defaults to `false` (emptyDir) so the chart\ninstalls cleanly on clusters without a dynamic PV provisioner or a\ndefault `StorageClass`. Telemetry archives flush to MongoDB every\n~10 minutes (`metrics_history`, `service_logs`, completed DO runs),\nso what you lose on a Redis pod restart is only the in-window\ndashboard backscroll (~10 minutes of un-archived data, plus any\nlive op buffers). Other workload crashes do NOT affect Redis state\n— only a Redis pod restart drops the in-memory window.\n\nSet to `true` to provision a PVC and have Redis recover its in-window\ndata across pod restarts (requires a working `StorageClass`, or set\n`existingClaim` below to point at a pre-provisioned PVC).", "title": "enabled", "type": "boolean" }, diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index a383d7992..a2c92687e 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -191,7 +191,12 @@ apiSettings: minAvailable: # maxUnavailable: "" # -- Pod-level security attributes and common container settings for `teams-api`. [Reference][security-context]. - podSecurityContext: {} + # Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. + podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 # -- RBAC roles, bindings, and service accounts which will be used to # submit on-demand delegated operators to the kubernetes API. # If `apiSettings.rbac.create=true`, these will be used by the `teams-api` pods. @@ -458,7 +463,12 @@ appSettings: minAvailable: # maxUnavailable: "" # -- Pod-level security attributes and common container settings for `fiftyone-app`. [Reference][security-context]. - podSecurityContext: {} + # Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. + podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 readiness: # -- Number of times to retry the readiness probe for the `fiftyone-app`. [Reference][probes]. failureThreshold: 5 @@ -788,7 +798,12 @@ delegatedOperatorDeployments: minAvailable: # maxUnavailable: "" # -- Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context]. - podSecurityContext: {} + # Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. + podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 readiness: # -- Number of times to retry the readiness probe for the `teams-do`. [Reference][probes]. failureThreshold: 5 @@ -972,7 +987,12 @@ delegatedOperatorJobTemplates: # -- Annotations for delegated-operator-executor pods. [Reference][annotations]. podAnnotations: {} # -- Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context]. - podSecurityContext: {} + # Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. + podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 # Instead of setting default resources, we require the user define them # This enables running on resource constrained environments # (like Minikube). To set resources, uncomment the following @@ -1295,7 +1315,12 @@ pluginsSettings: minAvailable: # maxUnavailable: "" # -- Pod-level security attributes and common container settings for `teams-plugins`. [Reference][security-context]. - podSecurityContext: {} + # Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. + podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 readiness: # -- Number of times to retry the readiness probe for the `teams-plugins`. [Reference][probes]. failureThreshold: 5 @@ -1620,10 +1645,19 @@ telemetry: image: redis:7-alpine persistence: # -- Controls whether a `PersistentVolumeClaim` is created for the - # bundled telemetry Redis. When `false`, Redis runs with an `emptyDir` - # volume and state is lost on pod restart — fine for dev clusters - # without a dynamic PV provisioner, not for production. - enabled: true + # bundled telemetry Redis. Defaults to `false` (emptyDir) so the chart + # installs cleanly on clusters without a dynamic PV provisioner or a + # default `StorageClass`. Telemetry archives flush to MongoDB every + # ~10 minutes (`metrics_history`, `service_logs`, completed DO runs), + # so what you lose on a Redis pod restart is only the in-window + # dashboard backscroll (~10 minutes of un-archived data, plus any + # live op buffers). Other workload crashes do NOT affect Redis state + # — only a Redis pod restart drops the in-memory window. + # + # Set to `true` to provision a PVC and have Redis recover its in-window + # data across pod restarts (requires a working `StorageClass`, or set + # `existingClaim` below to point at a pre-provisioned PVC). + enabled: false # -- Storage size for the telemetry Redis `PersistentVolumeClaim`. size: 1Gi # @schema diff --git a/tests/unit/helm/api-deployment_test.go b/tests/unit/helm/api-deployment_test.go index bf7b66861..b4231b993 100644 --- a/tests/unit/helm/api-deployment_test.go +++ b/tests/unit/helm/api-deployment_test.go @@ -2400,11 +2400,11 @@ func (s *deploymentApiTemplateTest) TestPodSecurityContext() { "defaultValues", nil, func(podSecurityContext *corev1.PodSecurityContext) { - s.Nil(podSecurityContext.FSGroup, "should be nil") + s.Equal(int64(1000), *podSecurityContext.FSGroup, "fsGroup should be 1000 (image UID)") + s.Equal(int64(1000), *podSecurityContext.RunAsGroup, "runAsGroup should be 1000") + s.True(*podSecurityContext.RunAsNonRoot, "runAsNonRoot should be true") + s.Equal(int64(1000), *podSecurityContext.RunAsUser, "runAsUser should be 1000") s.Nil(podSecurityContext.FSGroupChangePolicy, "should be nil") - s.Nil(podSecurityContext.RunAsGroup, "should be nil") - s.Nil(podSecurityContext.RunAsNonRoot, "should be nil") - s.Nil(podSecurityContext.RunAsUser, "should be nil") s.Nil(podSecurityContext.SeccompProfile, "should be nil") s.Nil(podSecurityContext.SELinuxOptions, "should be nil") s.Nil(podSecurityContext.SupplementalGroups, "should be nil") diff --git a/tests/unit/helm/app-deployment_test.go b/tests/unit/helm/app-deployment_test.go index 3f3cfba20..5637978c2 100644 --- a/tests/unit/helm/app-deployment_test.go +++ b/tests/unit/helm/app-deployment_test.go @@ -2091,11 +2091,11 @@ func (s *deploymentAppTemplateTest) TestPodSecurityContext() { "defaultValues", nil, func(podSecurityContext *corev1.PodSecurityContext) { - s.Nil(podSecurityContext.FSGroup, "should be nil") + s.Equal(int64(1000), *podSecurityContext.FSGroup, "fsGroup should be 1000 (image UID)") + s.Equal(int64(1000), *podSecurityContext.RunAsGroup, "runAsGroup should be 1000") + s.True(*podSecurityContext.RunAsNonRoot, "runAsNonRoot should be true") + s.Equal(int64(1000), *podSecurityContext.RunAsUser, "runAsUser should be 1000") s.Nil(podSecurityContext.FSGroupChangePolicy, "should be nil") - s.Nil(podSecurityContext.RunAsGroup, "should be nil") - s.Nil(podSecurityContext.RunAsNonRoot, "should be nil") - s.Nil(podSecurityContext.RunAsUser, "should be nil") s.Nil(podSecurityContext.SeccompProfile, "should be nil") s.Nil(podSecurityContext.SELinuxOptions, "should be nil") s.Nil(podSecurityContext.SupplementalGroups, "should be nil") diff --git a/tests/unit/helm/delegated-operator-instance-deployment_test.go b/tests/unit/helm/delegated-operator-instance-deployment_test.go index 118a4dbdf..f190c57f2 100644 --- a/tests/unit/helm/delegated-operator-instance-deployment_test.go +++ b/tests/unit/helm/delegated-operator-instance-deployment_test.go @@ -3652,11 +3652,11 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestPodSecurityContext []func(podSecurityContext *corev1.PodSecurityContext){ func(podSecurityContext *corev1.PodSecurityContext) { - s.Nil(podSecurityContext.FSGroup, "should be nil") + s.Equal(int64(1000), *podSecurityContext.FSGroup, "fsGroup should be 1000 (image UID)") + s.Equal(int64(1000), *podSecurityContext.RunAsGroup, "runAsGroup should be 1000") + s.True(*podSecurityContext.RunAsNonRoot, "runAsNonRoot should be true") + s.Equal(int64(1000), *podSecurityContext.RunAsUser, "runAsUser should be 1000") s.Nil(podSecurityContext.FSGroupChangePolicy, "should be nil") - s.Nil(podSecurityContext.RunAsGroup, "should be nil") - s.Nil(podSecurityContext.RunAsNonRoot, "should be nil") - s.Nil(podSecurityContext.RunAsUser, "should be nil") s.Nil(podSecurityContext.SeccompProfile, "should be nil") s.Nil(podSecurityContext.SELinuxOptions, "should be nil") s.Nil(podSecurityContext.SupplementalGroups, "should be nil") @@ -3672,11 +3672,11 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestPodSecurityContext }, []func(podSecurityContext *corev1.PodSecurityContext){ func(podSecurityContext *corev1.PodSecurityContext) { - s.Nil(podSecurityContext.FSGroup, "should be nil") + s.Equal(int64(1000), *podSecurityContext.FSGroup, "fsGroup should be 1000 (image UID)") + s.Equal(int64(1000), *podSecurityContext.RunAsGroup, "runAsGroup should be 1000") + s.True(*podSecurityContext.RunAsNonRoot, "runAsNonRoot should be true") + s.Equal(int64(1000), *podSecurityContext.RunAsUser, "runAsUser should be 1000") s.Nil(podSecurityContext.FSGroupChangePolicy, "should be nil") - s.Nil(podSecurityContext.RunAsGroup, "should be nil") - s.Nil(podSecurityContext.RunAsNonRoot, "should be nil") - s.Nil(podSecurityContext.RunAsUser, "should be nil") s.Nil(podSecurityContext.SeccompProfile, "should be nil") s.Nil(podSecurityContext.SELinuxOptions, "should be nil") s.Nil(podSecurityContext.SupplementalGroups, "should be nil") diff --git a/tests/unit/helm/plugins-deployment_test.go b/tests/unit/helm/plugins-deployment_test.go index 459679ce8..f0009eebb 100644 --- a/tests/unit/helm/plugins-deployment_test.go +++ b/tests/unit/helm/plugins-deployment_test.go @@ -2522,11 +2522,11 @@ func (s *deploymentPluginsTemplateTest) TestPodSecurityContext() { "pluginsSettings.enabled": "true", }, func(podSecurityContext *corev1.PodSecurityContext) { - s.Nil(podSecurityContext.FSGroup, "should be nil") + s.Equal(int64(1000), *podSecurityContext.FSGroup, "fsGroup should be 1000 (image UID)") + s.Equal(int64(1000), *podSecurityContext.RunAsGroup, "runAsGroup should be 1000") + s.True(*podSecurityContext.RunAsNonRoot, "runAsNonRoot should be true") + s.Equal(int64(1000), *podSecurityContext.RunAsUser, "runAsUser should be 1000") s.Nil(podSecurityContext.FSGroupChangePolicy, "should be nil") - s.Nil(podSecurityContext.RunAsGroup, "should be nil") - s.Nil(podSecurityContext.RunAsNonRoot, "should be nil") - s.Nil(podSecurityContext.RunAsUser, "should be nil") s.Nil(podSecurityContext.SeccompProfile, "should be nil") s.Nil(podSecurityContext.SELinuxOptions, "should be nil") s.Nil(podSecurityContext.SupplementalGroups, "should be nil") diff --git a/tests/unit/helm/telemetry-redis_test.go b/tests/unit/helm/telemetry-redis_test.go index c8a67e245..6d95d94e1 100644 --- a/tests/unit/helm/telemetry-redis_test.go +++ b/tests/unit/helm/telemetry-redis_test.go @@ -48,7 +48,8 @@ func (s *telemetryRedisTemplateTest) TestEnabledByDefault() { s.Require().NoError(err) s.Contains(output, "kind: Deployment", "Redis Deployment should be rendered by default") s.Contains(output, "kind: Service", "Redis Service should be rendered by default") - s.Contains(output, "kind: PersistentVolumeClaim", "Redis PVC should be rendered by default") + s.NotContains(output, "kind: PersistentVolumeClaim", + "Redis PVC should NOT be rendered by default — persistence.enabled defaults to false so the chart installs cleanly on clusters without a default StorageClass") } func (s *telemetryRedisTemplateTest) TestExplicitlyDisabled() { @@ -297,8 +298,10 @@ func (s *telemetryRedisTemplateTest) TestServiceMetadata() { } func (s *telemetryRedisTemplateTest) TestPVCMetadata() { + // persistence.enabled defaults to false; re-enable to exercise the PVC-rendering path. options := &helm.Options{SetValues: map[string]string{ "telemetry.enabled": "true", + "telemetry.redis.persistence.enabled": "true", "telemetry.redis.persistence.size": "5Gi", "telemetry.redis.persistence.storageClass": "gp3", }} @@ -350,7 +353,9 @@ func (s *telemetryRedisTemplateTest) TestPersistenceDisabledSkipsPVCAndUsesEmpty // suppresses chart-managed PVC creation — the path for clusters without // a dynamic PV provisioner where the operator pre-creates the claim. func (s *telemetryRedisTemplateTest) TestExistingClaimSkipsPVCAndMountsNamedClaim() { + // persistence.enabled defaults to false; re-enable to exercise the existingClaim mount path. options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.persistence.enabled": "true", "telemetry.redis.persistence.existingClaim": "my-prebuilt-redis-pvc", }} @@ -375,7 +380,9 @@ func (s *telemetryRedisTemplateTest) TestExistingClaimSkipsPVCAndMountsNamedClai // have no effect once the user has taken over claim provisioning via // existingClaim — the chart has no PVC to apply them to. func (s *telemetryRedisTemplateTest) TestExistingClaimIgnoresStorageClassAndSize() { + // persistence.enabled defaults to false; re-enable so the test exercises the existingClaim path with size/storageClass set. options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.persistence.enabled": "true", "telemetry.redis.persistence.existingClaim": "my-prebuilt-redis-pvc", "telemetry.redis.persistence.size": "100Gi", "telemetry.redis.persistence.storageClass": "gp3", diff --git a/tests/unit/helm/telemetry-sidecar_test.go b/tests/unit/helm/telemetry-sidecar_test.go index e63ac472a..61ae13263 100644 --- a/tests/unit/helm/telemetry-sidecar_test.go +++ b/tests/unit/helm/telemetry-sidecar_test.go @@ -132,28 +132,32 @@ func (s *telemetrySidecarTemplateTest) TestShareProcessNamespaceDisabledWithTele } } -// TestSidecarSecurityContext asserts the telemetry-sidecar container's -// securityContext renders with the elevated privileges the image needs: -// root UID (py-spy + /proc//fd/1) and SYS_PTRACE. This is the -// posture that makes telemetry incompatible with PSA `restricted` — -// regressing this without an explicit posture-change PR would silently -// break log capture on the clusters that already accept it. +// TestSidecarSecurityContext asserts the sidecar's securityContext follows +// the paired workload's runAsUser, drops all default caps, and only adds +// SYS_PTRACE on executor (DO) sidecars where py-spy crash-stack archives are +// load-bearing. Service-mode sidecars (api/app/plugins) get no elevated caps. func (s *telemetrySidecarTemplateTest) TestSidecarSecurityContext() { cases := []struct { - template string - values map[string]string + template string + values map[string]string + executor bool + expectRunAsUid int64 }{ - {"templates/api-deployment.yaml", nil}, - {"templates/app-deployment.yaml", nil}, + {"templates/api-deployment.yaml", nil, false, 1000}, + {"templates/app-deployment.yaml", nil, false, 1000}, { "templates/plugins-deployment.yaml", map[string]string{"pluginsSettings.enabled": "true"}, + false, + 1000, }, { "templates/delegated-operator-instance-deployment.yaml", map[string]string{ "delegatedOperatorDeployments.deployments.teamsDoCpuDefault.enabled": "true", }, + true, + 1000, }, } @@ -173,13 +177,16 @@ func (s *telemetrySidecarTemplateTest) TestSidecarSecurityContext() { sc := sidecar.SecurityContext s.Require().NotNil(sc, "telemetry-sidecar should have a securityContext") - s.Require().NotNil(sc.RunAsNonRoot, "RunAsNonRoot should be set on sidecar") - s.False(*sc.RunAsNonRoot, "telemetry-sidecar must run as root") - s.Require().NotNil(sc.RunAsUser, "RunAsUser should be set on sidecar") - s.EqualValues(0, *sc.RunAsUser, "telemetry-sidecar must run as UID 0") + s.EqualValues(tc.expectRunAsUid, *sc.RunAsUser, "sidecar UID should match paired workload") + + s.Require().NotNil(sc.AllowPrivilegeEscalation, "allowPrivilegeEscalation should be set") + s.False(*sc.AllowPrivilegeEscalation, "sidecar must not allow privilege escalation") s.Require().NotNil(sc.Capabilities, "Capabilities should be set on sidecar") + s.Contains(sc.Capabilities.Drop, corev1.Capability("ALL"), + "sidecar must drop all default capabilities") + var hasPtrace bool for _, capability := range sc.Capabilities.Add { if capability == "SYS_PTRACE" { @@ -187,7 +194,68 @@ func (s *telemetrySidecarTemplateTest) TestSidecarSecurityContext() { break } } - s.True(hasPtrace, "telemetry-sidecar must add SYS_PTRACE") + if tc.executor { + s.True(hasPtrace, "executor sidecar must add SYS_PTRACE for py-spy crash archives") + } else { + s.False(hasPtrace, "service-mode sidecar must not add SYS_PTRACE") + } + }) + } +} + +// TestSidecarMatchesWorkloadUid asserts the sidecar's runAsUser follows +// the paired workload's podSecurityContext.runAsUser override, so same-UID +// /proc reads work without root or SYS_PTRACE. +func (s *telemetrySidecarTemplateTest) TestSidecarMatchesWorkloadUid() { + const overrideUid = "1500" + + cases := []struct { + template string + values map[string]string + }{ + { + "templates/api-deployment.yaml", + map[string]string{"apiSettings.podSecurityContext.runAsUser": overrideUid}, + }, + { + "templates/app-deployment.yaml", + map[string]string{"appSettings.podSecurityContext.runAsUser": overrideUid}, + }, + { + "templates/plugins-deployment.yaml", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.podSecurityContext.runAsUser": overrideUid, + }, + }, + { + "templates/delegated-operator-instance-deployment.yaml", + map[string]string{ + "delegatedOperatorDeployments.deployments.teamsDoCpuDefault.enabled": "true", + "delegatedOperatorDeployments.template.podSecurityContext.runAsUser": overrideUid, + }, + }, + } + + for _, tc := range cases { + tc := tc + s.Run(tc.template, func() { + options := &helm.Options{SetValues: tc.values} + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, + []string{tc.template}) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(s.T(), output, &deployment) + + sidecar := findSidecar(deployment.Spec.Template.Spec.Containers) + s.Require().NotNil(sidecar, "telemetry-sidecar should be injected into %s", tc.template) + + sc := sidecar.SecurityContext + s.Require().NotNil(sc.RunAsUser, "RunAsUser should follow workload override") + s.EqualValues(1500, *sc.RunAsUser, "sidecar UID should match workload override") + + s.Require().NotNil(sc.RunAsNonRoot, "RunAsNonRoot should be set") + s.True(*sc.RunAsNonRoot, "non-zero UID implies runAsNonRoot: true") }) } } diff --git a/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-cpu-default-override-template-values.yaml b/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-cpu-default-override-template-values.yaml index b5f52a368..c64fababb 100644 --- a/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-cpu-default-override-template-values.yaml +++ b/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-cpu-default-override-template-values.yaml @@ -33,6 +33,9 @@ spec: restartPolicy: Never serviceAccountName: fiftyone-teams securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true runAsUser: 1000 containers: - name: executor diff --git a/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-cpu-default.yaml b/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-cpu-default.yaml index 260f66378..5f0afd093 100644 --- a/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-cpu-default.yaml +++ b/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-cpu-default.yaml @@ -23,6 +23,11 @@ spec: spec: restartPolicy: Never serviceAccountName: fiftyone-teams + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 containers: - name: executor image: "voxel51/fiftyone-teams-cv-full:v{{CHART_VERSION}}" diff --git a/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-override-example-cascading-template.yaml b/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-override-example-cascading-template.yaml index 3af3c3d21..e1354b1cd 100644 --- a/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-override-example-cascading-template.yaml +++ b/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-override-example-cascading-template.yaml @@ -33,6 +33,9 @@ spec: restartPolicy: Never serviceAccountName: fiftyone-teams securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true runAsUser: 1000 containers: - name: executor diff --git a/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-override-example-template.yaml b/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-override-example-template.yaml index 5dd8e0d4e..0f5fcca41 100644 --- a/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-override-example-template.yaml +++ b/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-override-example-template.yaml @@ -33,6 +33,9 @@ spec: restartPolicy: Never serviceAccountName: fiftyone-teams securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true runAsUser: 3000 containers: - name: executor From 23793fade608ee45ae923c94820227747172ef4a Mon Sep 17 00:00:00 2001 From: kacey Date: Sat, 23 May 2026 10:40:21 -0700 Subject: [PATCH 48/49] chore: trigger build From 60ec93046186ea909c8aa06ceca16f1f632219f5 Mon Sep 17 00:00:00 2001 From: kacey Date: Sat, 23 May 2026 13:53:14 -0700 Subject: [PATCH 49/49] chore: trigger build