diff --git a/docker/README.md b/docker/README.md index e97cd60e2..88272dbf8 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 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) +for full details. + ### Snapshot Archival Since version v1.5, FiftyOne Enterprise supports diff --git a/docker/common-services.yaml b/docker/common-services.yaml index 7050831db..75a0b81f1 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.18.1 + # 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 new file mode 100644 index 000000000..99f7d0bb1 --- /dev/null +++ b/docker/docs/configuring-telemetry.md @@ -0,0 +1,184 @@ + + +
+

+ +Voxel51 Logo   +Voxel51 FiftyOne + +

+
+ + +--- + +# Configuring FiftyOne Enterprise Telemetry + +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. + +**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. + +## Default deployment + +```shell +docker compose -f compose.yaml up -d +``` + +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 \ + 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: + +```shell +docker compose --profile gpu \ + -f compose.yaml \ + -f compose.delegated-operators.yaml \ + -f compose.delegated-operators.gpu.yaml \ + up -d +``` + +## 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 + 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. Sidecars run + with the `SYS_PTRACE` capability so py-spy can attach to the target. +- `teams-plugins-telemetry` (only with `compose.dedicated-plugins.yaml`) — + sidecar for the dedicated `teams-plugins` service. +- `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 + `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`, `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, `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: + +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. + +### 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: + +| 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 | + +## 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.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 new file mode 100644 index 000000000..a6de978d8 --- /dev/null +++ b/docker/internal-auth/compose.delegated-operators.gpu.yaml @@ -0,0 +1,88 @@ +--- +# 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 \ +# 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: + 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.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.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/internal-auth/env.template b/docker/internal-auth/env.template index 3933cdaf1..270780446 100644 --- a/docker/internal-auth/env.template +++ b/docker/internal-auth/env.template @@ -98,3 +98,27 @@ 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_PLUGINS_TARGET_NAME=hypercorn +# TEAMS_DO_TARGET_NAME=fiftyone delegated 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 new file mode 100644 index 000000000..a6de978d8 --- /dev/null +++ b/docker/legacy-auth/compose.delegated-operators.gpu.yaml @@ -0,0 +1,88 @@ +--- +# 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 \ +# 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: + 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.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.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/docker/legacy-auth/env.template b/docker/legacy-auth/env.template index 42c5cc963..ef62237a1 100644 --- a/docker/legacy-auth/env.template +++ b/docker/legacy-auth/env.template @@ -103,3 +103,27 @@ 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_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..8e614d5bb 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 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/) +(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 f85817c83..dfe3250ac 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -727,6 +727,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. | @@ -739,6 +740,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]. | @@ -794,6 +796,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. | @@ -806,6 +809,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]. | @@ -884,13 +888,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. | @@ -898,6 +903,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`. | @@ -923,7 +929,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]. | @@ -931,11 +937,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. 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]. | @@ -976,6 +984,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. | @@ -988,6 +997,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]. | @@ -1084,6 +1094,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` | 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 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.resources | object | `{"limits":{"cpu":"100m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"512Mi"}}` | Resource requests/limits for each auto-injected sidecar container. | @@ -1117,6 +1137,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 ae6994480..6d7291de3 100644 --- a/helm/fiftyone-teams-app/README.md.gotmpl +++ b/helm/fiftyone-teams-app/README.md.gotmpl @@ -752,6 +752,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/_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 new file mode 100644 index 000000000..690d1e65c --- /dev/null +++ b/helm/fiftyone-teams-app/templates/_telemetry.tpl @@ -0,0 +1,204 @@ +{{/* +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 }} + +{{/* +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 "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 }} + {{- with .ctx.Values.telemetry.sidecar.resources }} + resources: + {{- 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 + {{- 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 "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: + {{- 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: + # 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 + 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 a0623130d..37635c293 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 or .Values.apiSettings.extraContainers .Values.apiSettings.nativeSidecarContainers .Values.telemetry.enabled }} + shareProcessNamespace: true + {{- end }} securityContext: {{- toYaml .Values.apiSettings.podSecurityContext | nindent 8 }} containers: @@ -89,8 +92,15 @@ spec: {{- with .Values.apiSettings.volumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} - {{- if .Values.apiSettings.initContainers.enabled }} + {{- 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 }} {{- include "fiftyone-teams-app.commonInitContainers" ( @@ -101,6 +111,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/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/app-deployment.yaml b/helm/fiftyone-teams-app/templates/app-deployment.yaml index 54d3309fe..b071e0a13 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 or .Values.appSettings.extraContainers .Values.appSettings.nativeSidecarContainers .Values.telemetry.enabled }} + shareProcessNamespace: true + {{- end }} securityContext: {{- toYaml .Values.appSettings.podSecurityContext | nindent 8 }} containers: @@ -73,8 +76,15 @@ spec: volumeMounts: {{- toYaml . | nindent 12 }} {{- end }} - {{- if .Values.appSettings.initContainers.enabled }} + {{- 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 }} {{- include "fiftyone-teams-app.commonInitContainers" ( @@ -85,6 +95,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 ca93b52ce..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,6 +51,9 @@ 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 }} + shareProcessNamespace: true + {{- end }} securityContext: {{- toYaml (merge (dict) ($v.podSecurityContext | default dict) ($baseTpl.podSecurityContext)) | nindent 8 }} containers: @@ -75,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 }} @@ -106,6 +120,16 @@ 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 }} @@ -129,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 3ce459bdc..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. */ -}} @@ -42,6 +43,46 @@ 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 }} + {{- 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 }} @@ -87,6 +128,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 +163,38 @@ data: volumeMounts: {{- toYaml . | nindent 16 }} {{- end }} + {{- with $mergedExtraContainers }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with $mergedNativeSidecarContainers }} + 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 }} + {{- end }} {{- with $mergedNodeSelector }} nodeSelector: {{- toYaml . | nindent 12 }} diff --git a/helm/fiftyone-teams-app/templates/plugins-deployment.yaml b/helm/fiftyone-teams-app/templates/plugins-deployment.yaml index 85ef25c92..baa9762e9 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 or .Values.pluginsSettings.extraContainers .Values.pluginsSettings.nativeSidecarContainers .Values.telemetry.enabled }} + shareProcessNamespace: true + {{- end }} securityContext: {{- toYaml .Values.pluginsSettings.podSecurityContext | nindent 8 }} containers: @@ -74,8 +77,15 @@ spec: volumeMounts: {{- toYaml . | nindent 12 }} {{- end }} - {{- if .Values.pluginsSettings.initContainers.enabled }} + {{- 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 }} {{- include "fiftyone-teams-app.commonInitContainers" ( @@ -86,6 +96,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/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..8075f0d42 --- /dev/null +++ b/helm/fiftyone-teams-app/templates/telemetry-rolebinding.yaml @@ -0,0 +1,30 @@ +{{- 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: + {{- 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 fbd55d461..7d68c3443 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -102,6 +102,14 @@ "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": { @@ -328,6 +336,14 @@ "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": [], @@ -794,6 +810,14 @@ "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": { @@ -1016,6 +1040,14 @@ "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": [], @@ -2025,6 +2057,14 @@ "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": { @@ -2091,6 +2131,14 @@ "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": [], @@ -2385,6 +2433,14 @@ "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": { @@ -2432,6 +2488,14 @@ "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": [], @@ -2791,6 +2855,14 @@ "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": { @@ -3013,6 +3085,14 @@ "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": [], @@ -3928,6 +4008,162 @@ "required": [], "title": "teamsAppSettings", "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.", + "properties": { + "enabled": { + "default": true, + "description": "Master switch. When `true` (default), all telemetry resources and\nauto-injected sidecars are rendered. When `false`, none are.", + "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 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": "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" + }, + "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": [], + "title": "telemetry", + "type": "object" } }, "required": [], diff --git a/helm/fiftyone-teams-app/values.yaml b/helm/fiftyone-teams-app/values.yaml index 404b37d0d..08c6bc01d 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,20 @@ 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 @@ -1227,6 +1266,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: @@ -1595,3 +1642,58 @@ teamsAppSettings: volumeMounts: [] # -- Volumes for `teams-app` pods. [Reference][volumes]. volumes: [] + +# 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: + # -- 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. + 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 + 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 + # -- 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 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/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 diff --git a/tests/fixtures/docker/integration_internal_auth.env b/tests/fixtures/docker/integration_internal_auth.env index 677024ad6..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.18.1 -FIFTYONE_APP_VERSION=v2.18.1 -FIFTYONE_TEAMS_API_VERSION=v2.18.1 -FIFTYONE_TEAMS_APP_VERSION=v2.18.1 -FIFTYONE_TEAMS_CAS_VERSION=v2.18.1 +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 1d77f397d..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.18.1 -FIFTYONE_APP_VERSION=v2.18.1 -FIFTYONE_TEAMS_API_VERSION=v2.18.1 -FIFTYONE_TEAMS_APP_VERSION=v2.18.1 -FIFTYONE_TEAMS_CAS_VERSION=v2.18.1 +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 f66afa156..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.18.1 + 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.18.1 + 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.18.1 + 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.18.1 + 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.18.1 + 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.18.1 + 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.18.1 + tag: v2.19.0-rc.6 diff --git a/tests/unit/compose/docker-compose-internal-auth_test.go b/tests/unit/compose/docker-compose-internal-auth_test.go index 62908d416..84f19d2cc 100644 --- a/tests/unit/compose/docker-compose-internal-auth_test.go +++ b/tests/unit/compose/docker-compose-internal-auth_test.go @@ -28,6 +28,7 @@ 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 internalAuthEnvTemplateFilePath = filepath.Join(dockerInternalAuthDir, "env.template") type commonServicesInternalAuthDockerComposeTest struct { @@ -59,48 +60,94 @@ 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", + "fiftyone-app-telemetry", "teams-api", + "teams-api-telemetry", "teams-app", "teams-cas", + "telemetry-redis", }, }, { "composePlugins", []string{internalAuthComposePluginsFile}, s.dotEnvFiles, + nil, []string{ "fiftyone-app", + "fiftyone-app-telemetry", "teams-api", + "teams-api-telemetry", "teams-app", "teams-cas", + "telemetry-redis", }, }, { "composeDedicatedPlugins", []string{internalAuthComposeDedicatedPluginsFile}, 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", }, }, { "composeDelegatedOperations", - []string{internalAuthComposeDelegatedOperationsFile}, + []string{internalAuthComposeFile, internalAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, + nil, []string{ + "fiftyone-app", + "fiftyone-app-telemetry", + "teams-api", + "teams-api-telemetry", + "teams-app", + "teams-cas", "teams-do", + "teams-do-telemetry", + "telemetry-redis", + }, + }, + { + "composeDelegatedOperationsGpu", + []string{ + internalAuthComposeFile, + internalAuthComposeDelegatedOperationsFile, + internalAuthComposeDelegatedOperationsGpuFile, + }, + 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", + "teams-do-telemetry", + "telemetry-redis", }, }, } @@ -118,6 +165,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServicesNames() { cli.WithName(s.projectName), cli.WithEnvFiles(testCase.envFiles...), cli.WithDotEnv, + cli.WithProfiles(testCase.profiles), ) s.NoError(err) @@ -183,10 +231,48 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceImage() { { "delegatedOperationsTeamsDo", "teams-do", - []string{internalAuthComposeDelegatedOperationsFile}, + []string{internalAuthComposeFile, internalAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, "voxel51/fiftyone-teams-cv-full:v2.18.1", }, + { + "telemetryRedis", + "telemetry-redis", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + "redis:7-alpine", + }, + { + "telemetrySidecarFiftyoneApp", + "fiftyone-app-telemetry", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:latest", + }, + { + "telemetrySidecarTeamsApi", + "teams-api-telemetry", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:latest", + }, + { + "telemetrySidecarTeamsDo", + "teams-do-telemetry", + []string{ + internalAuthComposeFile, + internalAuthComposeDelegatedOperationsFile, + }, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:latest", + }, + { + "telemetrySidecarTeamsPlugins", + "teams-plugins-telemetry", + []string{internalAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:latest", + }, } for _, testCase := range testCases { @@ -250,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", }, }, { @@ -267,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", @@ -287,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", @@ -334,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", }, }, { @@ -351,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", @@ -372,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", @@ -418,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", }, }, { @@ -435,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", @@ -456,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", @@ -501,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", @@ -517,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", }, }, } @@ -707,7 +805,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceRestart() { { "delegatedOperationsTeamsDo", "teams-do", - []string{internalAuthComposeDelegatedOperationsFile}, + []string{internalAuthComposeFile, internalAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, types.RestartPolicyAlways, }, @@ -905,7 +1003,7 @@ func (s *commonServicesInternalAuthDockerComposeTest) TestServiceVolumes() { { "delegatedOperationsTeamsDo", "teams-do", - []string{internalAuthComposeDelegatedOperationsFile}, + []string{internalAuthComposeFile, internalAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, []types.ServiceVolumeConfig{ { @@ -915,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{}, + }, }, }, } @@ -962,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", @@ -972,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", + }, }, }, { @@ -982,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 2e3712ae0..fcbb3d72c 100644 --- a/tests/unit/compose/docker-compose-legacy-auth_test.go +++ b/tests/unit/compose/docker-compose-legacy-auth_test.go @@ -28,6 +28,7 @@ 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 legacyAuthEnvTemplateFilePath = filepath.Join(dockerLegacyAuthDir, "env.template") type commonServicesLegacyAuthDockerComposeTest struct { @@ -59,48 +60,94 @@ 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", + "fiftyone-app-telemetry", "teams-api", + "teams-api-telemetry", "teams-app", "teams-cas", + "telemetry-redis", }, }, { "composePlugins", []string{legacyAuthComposePluginsFile}, s.dotEnvFiles, + nil, []string{ "fiftyone-app", + "fiftyone-app-telemetry", "teams-api", + "teams-api-telemetry", "teams-app", "teams-cas", + "telemetry-redis", }, }, { "composeDedicatedPlugins", []string{legacyAuthComposeDedicatedPluginsFile}, 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", }, }, { "composeDelegatedOperations", - []string{legacyAuthComposeDelegatedOperationsFile}, + []string{legacyAuthComposeFile, legacyAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, + nil, []string{ + "fiftyone-app", + "fiftyone-app-telemetry", + "teams-api", + "teams-api-telemetry", + "teams-app", + "teams-cas", "teams-do", + "teams-do-telemetry", + "telemetry-redis", + }, + }, + { + "composeDelegatedOperationsGpu", + []string{ + legacyAuthComposeFile, + legacyAuthComposeDelegatedOperationsFile, + legacyAuthComposeDelegatedOperationsGpuFile, + }, + 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", + "teams-do-telemetry", + "telemetry-redis", }, }, } @@ -118,6 +165,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServicesNames() { cli.WithName(s.projectName), cli.WithEnvFiles(testCase.envFiles...), cli.WithDotEnv, + cli.WithProfiles(testCase.profiles), ) s.NoError(err) @@ -183,10 +231,48 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceImage() { { "delegatedOperationsTeamsDo", "teams-do", - []string{legacyAuthComposeDelegatedOperationsFile}, + []string{legacyAuthComposeFile, legacyAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, "voxel51/fiftyone-teams-cv-full:v2.18.1", }, + { + "telemetryRedis", + "telemetry-redis", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + "redis:7-alpine", + }, + { + "telemetrySidecarFiftyoneApp", + "fiftyone-app-telemetry", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:latest", + }, + { + "telemetrySidecarTeamsApi", + "teams-api-telemetry", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:latest", + }, + { + "telemetrySidecarTeamsDo", + "teams-do-telemetry", + []string{ + legacyAuthComposeFile, + legacyAuthComposeDelegatedOperationsFile, + }, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:latest", + }, + { + "telemetrySidecarTeamsPlugins", + "teams-plugins-telemetry", + []string{legacyAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:latest", + }, } for _, testCase := range testCases { @@ -250,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", }, }, { @@ -267,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", @@ -287,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", @@ -332,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", }, }, { @@ -349,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", @@ -369,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", @@ -415,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", }, }, { @@ -433,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", @@ -453,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", @@ -498,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", }, }, { @@ -516,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", @@ -536,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", @@ -580,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", @@ -596,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", }, }, } @@ -791,7 +892,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceRestart() { { "delegatedOperationsTeamsDo", "teams-do", - []string{legacyAuthComposeDelegatedOperationsFile}, + []string{legacyAuthComposeFile, legacyAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, types.RestartPolicyAlways, }, @@ -989,7 +1090,7 @@ func (s *commonServicesLegacyAuthDockerComposeTest) TestServiceVolumes() { { "delegatedOperationsTeamsDo", "teams-do", - []string{legacyAuthComposeDelegatedOperationsFile}, + []string{legacyAuthComposeFile, legacyAuthComposeDelegatedOperationsFile}, s.dotEnvFiles, []types.ServiceVolumeConfig{ { @@ -999,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{}, + }, }, }, } @@ -1046,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", @@ -1056,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", + }, }, }, { @@ -1066,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/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/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 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/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/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 9ab313a4a..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) @@ -657,3 +657,147 @@ 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 +} 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-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..024bb4757 --- /dev/null +++ b/tests/unit/helm/telemetry-rolebinding_test.go @@ -0,0 +1,191 @@ +//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 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", + }} + + 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, "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() { + 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..de1a1c8df --- /dev/null +++ b/tests/unit/helm/yaml_helpers.go @@ -0,0 +1,31 @@ +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 +} + +// 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 +} diff --git a/utils/validate-docker-pulls.sh b/utils/validate-docker-pulls.sh index 2af6b79c2..40e7d8e20 100755 --- a/utils/validate-docker-pulls.sh +++ b/utils/validate-docker-pulls.sh @@ -14,8 +14,11 @@ 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) "voxel51/fiftyone-app" "voxel51/fiftyone-app-gpt" "voxel51/fiftyone-app-torch" @@ -25,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.62" +) + . "$(dirname "$0")/common.sh" VALUES_YAML="${GIT_ROOT}/helm/fiftyone-teams-app/values.yaml" @@ -101,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}" @@ -122,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