diff --git a/docker/README.md b/docker/README.md index 7f231589b..ba9ea9feb 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 bc71259a8..494850059 100644 --- a/docker/common-services.yaml +++ b/docker/common-services.yaml @@ -17,6 +17,7 @@ services: FIFTYONE_MEDIA_CACHE_APP_IMAGES: false FIFTYONE_MEDIA_CACHE_SIZE_BYTES: -1 FIFTYONE_SIGNED_URL_EXPIRATION: ${FIFTYONE_SIGNED_URL_EXPIRATION:-24} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} # If you are routing through a proxy server you will want to set # HTTP_PROXY_URL, HTTPS_PROXY_URL, and NO_PROXY_LIST in your .env # then add the following environment variables to your @@ -43,6 +44,7 @@ services: FIFTYONE_ENV: ${FIFTYONE_ENV} FIFTYONE_INTERNAL_SERVICE: true FIFTYONE_LOGGING_FORMAT: ${FIFTYONE_LOGGING_FORMAT:-text} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} GRAPHQL_DEFAULT_LIMIT: ${GRAPHQL_DEFAULT_LIMIT} LOGGING_LEVEL: ${API_LOGGING_LEVEL:-INFO} MONGO_DEFAULT_DB: ${FIFTYONE_DATABASE_NAME} @@ -72,6 +74,7 @@ services: FIFTYONE_SERVER_ADDRESS: "" FIFTYONE_SERVER_PATH_PREFIX: /api/proxy/fiftyone-teams FIFTYONE_TEAMS_PROXY_URL: ${FIFTYONE_TEAMS_PROXY_URL} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} NODE_ENV: production RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED: false FIFTYONE_APP_ANONYMOUS_ANALYTICS_ENABLED: ${FIFTYONE_APP_ANONYMOUS_ANALYTICS_ENABLED:-true} @@ -144,6 +147,7 @@ services: FIFTYONE_MEDIA_CACHE_APP_IMAGES: false FIFTYONE_MEDIA_CACHE_SIZE_BYTES: -1 FIFTYONE_PLUGINS_DIR: /opt/plugins + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} # If you are routing through a proxy server you will want to set # HTTP_PROXY_URL, HTTPS_PROXY_URL, and NO_PROXY_LIST in your .env # then add the following environment variables to your @@ -158,8 +162,12 @@ services: teams-do-common: image: voxel51/fiftyone-teams-cv-full:v2.19.0 + # Telemetry default-on requires teams-do to share its PID namespace + # with a single sidecar (compose's `pid: "service:"` only joins + # one replica). Force replicas=1 to keep the 1:1 pairing deterministic. + # See docker/docs/configuring-telemetry.md for multi-worker patterns. deploy: - replicas: ${FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS:-3} + replicas: 1 command: > /bin/sh -c "fiftyone delegated launch -t remote -m" environment: @@ -171,6 +179,147 @@ 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 + + # ─── Telemetry common services ───────────────────────────────────────── + # Each base compose file (compose.yaml, compose.plugins.yaml, + # compose.dedicated-plugins.yaml) is a standalone alternative to the + # others — they can't be layered. Defining the telemetry services once + # here lets all three pull them in via `extends:` without duplicating + # image, env, and resource blocks. + # + # `extends` does NOT propagate `depends_on`; each extending service + # redeclares it locally. + + telemetry-redis-common: + image: ${TELEMETRY_REDIS_IMAGE:-redis:7-alpine} + command: + - redis-server + - --save + - "60" + - "1" + - --dir + - /data + - --maxmemory + - ${TELEMETRY_REDIS_MAXMEMORY:-400mb} + - --maxmemory-policy + - ${TELEMETRY_REDIS_MAXMEMORY_POLICY:-allkeys-lru} + restart: always + deploy: + resources: + limits: + cpus: "0.25" + memory: 512M + reservations: + cpus: "0.10" + memory: 256M + volumes: + - telemetry-redis-data:/data + + # One sidecar per observed service. `pid: "service:"` joins the + # target's PID namespace so the sidecar can read /proc//fd/1 and + # psutil can see the target's process. + fiftyone-app-telemetry-common: + image: voxel51/telemetry-sidecar:v2.19.0 + pid: "service:fiftyone-app" + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + environment: + POD_NAME: fiftyone-app + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: fiftyone-app + TARGET_NAME: ${FIFTYONE_APP_TARGET_NAME:-hypercorn} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M + restart: always + + teams-api-telemetry-common: + image: voxel51/telemetry-sidecar:v2.19.0 + pid: "service:teams-api" + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + environment: + POD_NAME: teams-api + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: teams-api + TARGET_NAME: ${TEAMS_API_TARGET_NAME:-fiftyone-teams-api} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M + restart: always + + teams-plugins-telemetry-common: + image: voxel51/telemetry-sidecar:v2.19.0 + pid: "service:teams-plugins" + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + environment: + POD_NAME: teams-plugins + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: teams-plugins + TARGET_NAME: ${TEAMS_PLUGINS_TARGET_NAME:-hypercorn} + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M + restart: always + + teams-do-telemetry-common: + image: voxel51/telemetry-sidecar:v2.19.0 + pid: "service:teams-do" + cap_drop: + - ALL + cap_add: + - SYS_PTRACE + security_opt: + - no-new-privileges:true + environment: + POD_NAME: teams-do + POD_NAMESPACE: ${TELEMETRY_NAMESPACE:-docker} + SERVICE_TYPE: delegated-operator + TARGET_NAME: ${TEAMS_DO_TARGET_NAME:-fiftyone delegated} + EXECUTOR_SIDECAR: "true" + TELEMETRY_SOCKET: /tmp/telemetry/agent.sock + FIFTYONE_TELEMETRY_REDIS_URL: ${FIFTYONE_TELEMETRY_REDIS_URL:-redis://telemetry-redis:6379} + FIFTYONE_DATABASE_URI: ${FIFTYONE_DATABASE_URI} + FIFTYONE_DATABASE_NAME: ${FIFTYONE_DATABASE_NAME} + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M + volumes: + - telemetry-socket:/tmp/telemetry + restart: always diff --git a/docker/docs/configuring-telemetry.md b/docker/docs/configuring-telemetry.md new file mode 100644 index 000000000..3b97063ef --- /dev/null +++ b/docker/docs/configuring-telemetry.md @@ -0,0 +1,205 @@ + + +
+

+ +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 + +> [!NOTE] +> The telemetry sidecar can be disabled, but doing so disables the +> FiftyOne UI's log viewer for delegated-operator runs — it depends +> on the sidecar to capture per-operation logs. + +To run without telemetry, add a `compose.override.yaml` that scales the +telemetry services to zero replicas: + +```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_REDIS_IMAGE` | `redis:7-alpine` | Alternate redis image | +| `TELEMETRY_REDIS_MAXMEMORY` | `400mb` | Redis maxmemory budget | +| `TELEMETRY_REDIS_MAXMEMORY_POLICY` | `allkeys-lru` | Redis eviction policy (mirrors helm `telemetry.redis.maxmemoryPolicy`) | +| `TELEMETRY_NAMESPACE` | `docker` | Namespace label attached to each registered pod | +| `FIFTYONE_APP_TARGET_NAME` | `hypercorn` | Substring used to locate the fiftyone-app process | +| `TEAMS_API_TARGET_NAME` | `fiftyone-teams-api` | Substring used to locate the teams-api process | +| `TEAMS_PLUGINS_TARGET_NAME` | `hypercorn` | Substring used to locate the teams-plugins process | +| `TEAMS_DO_TARGET_NAME` | `fiftyone delegated` | Substring used to locate the teams-do process | +| `NVIDIA_GPU_COUNT` | `1` | GPU reservation for the GPU DO worker + sidecar | +| `NVIDIA_VISIBLE_DEVICES` | `all` | Pass-through to teams-do-gpu / sidecar | +| `NVIDIA_DRIVER_CAPABILITIES` | `compute,utility` | Must include `utility` so NVML is available | + +## Resource limits + +Telemetry containers ship with conservative CPU and memory limits that +mirror the helm chart's defaults — sized so the sidecars do not starve +the workloads they observe. The values are declared under each +service's `deploy.resources` block in the compose files; compose v2 +honors `cpus` and `memory` limits/reservations outside swarm mode. + +| Service | CPU limit | Memory limit | Notes | +| ----------------------------- | --------- | ------------ | ----------------------------------- | +| `telemetry-redis` | `0.25` | `512M` | Reserves `0.10` CPU / `256M` memory | +| `*-telemetry` (any sidecar) | `0.10` | `512M` | Reserved == limit | + +To tune these for your hardware, override `deploy.resources` in a +`compose.override.yaml` (the override merges with the base entry). + +## Access control + +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_targets +docker compose exec telemetry-redis redis-cli XLEN metrics:fiftyone-app +``` + +`XLEN` should increase over time. If it does not, check the sidecar logs: + +```shell +docker compose logs fiftyone-app-telemetry teams-api-telemetry --tail 20 +``` diff --git a/docker/docs/upgrading.md b/docker/docs/upgrading.md index 61cf8037e..1ec68f928 100644 --- a/docker/docs/upgrading.md +++ b/docker/docs/upgrading.md @@ -19,6 +19,9 @@ - [Upgrading From Previous Versions](#upgrading-from-previous-versions) - [A Note On Database Migrations](#a-note-on-database-migrations) - [From FiftyOne Enterprise Version 2.0.0 and Later](#from-fiftyone-enterprise-version-200-and-later) + - [FiftyOne Enterprise v2.19+ Telemetry Sidecars](#fiftyone-enterprise-v219-telemetry-sidecars) + - [Host Requirements](#host-requirements) + - [Opting out of Telemetry](#opting-out-of-telemetry) - [FiftyOne Enterprise v2.16+ Additional API Routes](#fiftyone-enterprise-v216-additional-api-routes) - [FiftyOne Enterprise v2.15+ Additional API Routes](#fiftyone-enterprise-v215-additional-api-routes) - [FiftyOne Enterprise v2.7+ Delegated Operator Changes](#fiftyone-enterprise-v27-delegated-operator-changes) @@ -98,6 +101,55 @@ quickstart 0.21.2 fiftyone migrate --info ``` +#### FiftyOne Enterprise v2.19+ Telemetry Sidecars + +FiftyOne Enterprise v2.19.0 adds observability features that are viewable +by admins directly within the FiftyOne UI. +This is enabled by a `telemetry-sidecar` service paired with each +`fiftyone-app`, `teams-api`, `teams-plugins`, and `teams-do*` service, +plus a `telemetry-redis` service that buffers the streamed metrics/logs. + +**Resource impact:** Each sidecar reserves `0.10` CPUs and `512M` memory +(reservation == limit). +A stock deploy adds four sidecars (`fiftyone-app` + `teams-api` + +`teams-plugins` + one `teams-do`), so expect roughly **+0.4 CPU** and **+2 GiB +memory** of additional resource usage, plus the bundled `telemetry-redis` +service (`0.10` CPU / `256M` memory reservation, `0.25` / `512M` limits) and +its `telemetry-redis-data` named volume. + +##### Host Requirements + +1. **Docker Compose v2.17+** for `depends_on..restart: true` + semantics. Older versions will see stale PID namespaces after a + target container is recreated. +1. **Linux host with PID-namespace sharing** (`pid: "service:"`) + and **`SYS_PTRACE`** capability granted to each sidecar container. + Docker Desktop on macOS/Windows supports this, but some hardened + container runtimes (gVisor, Kata) do not — telemetry will fail to + attach to the target process there. +1. **`teams-do` is forced to `replicas: 1`** while telemetry is enabled, + because Compose's `pid: "service:"` only joins a single + replica. To run multiple delegated-operator workers, see + [`docker/docs/configuring-telemetry.md`](configuring-telemetry.md). + +**External Redis:** Point at a managed Redis instead of the bundled +one by setting `FIFTYONE_TELEMETRY_REDIS_URL` in your `.env` to a +fully-qualified URL (e.g. `redis://my-managed-redis.example.com:6379`) +and scaling `telemetry-redis` to `replicas: 0` as below. + +##### Opting out of Telemetry + +The new Telemetry features are enabled by default, but can be disabled by +adding a `compose.override.yaml` that scales the `telemetry-redis` and +`*-telemetry` services to `replicas: 0`. +See +[`configuring-telemetry.md`](configuring-telemetry.md#opt-out) +for the full override snippet. + +> [!IMPORTANT] +> The sidecar powers the FiftyOne UI's delegated-operator log viewer. +> Disabling telemetry will leave that log viewer empty. + #### FiftyOne Enterprise v2.16+ Additional API Routes FiftyOne Enterprise v2.16.0 adds the `/cloud_credentials` endpoint to the `teams-api`. diff --git a/docker/internal-auth/compose.dedicated-plugins.yaml b/docker/internal-auth/compose.dedicated-plugins.yaml index 1cf90e790..503ff802e 100644 --- a/docker/internal-auth/compose.dedicated-plugins.yaml +++ b/docker/internal-auth/compose.dedicated-plugins.yaml @@ -39,5 +39,44 @@ services: volumes: - plugins-vol:/opt/plugins:ro + telemetry-redis: + extends: + file: ../common-services.yaml + service: telemetry-redis-common + + fiftyone-app-telemetry: + extends: + file: ../common-services.yaml + service: fiftyone-app-telemetry-common + depends_on: + fiftyone-app: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + + teams-api-telemetry: + extends: + file: ../common-services.yaml + service: teams-api-telemetry-common + depends_on: + teams-api: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + + teams-plugins-telemetry: + extends: + file: ../common-services.yaml + service: teams-plugins-telemetry-common + depends_on: + teams-plugins: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + 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..1f19f9c66 --- /dev/null +++ b/docker/internal-auth/compose.delegated-operators.gpu.yaml @@ -0,0 +1,93 @@ +--- +# 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.19.0 + 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: voxel51/telemetry-sidecar:v2.19.0 + pid: "service:teams-do-gpu" + profiles: ["gpu"] + cap_add: + - SYS_PTRACE + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M + devices: + - driver: nvidia + count: ${NVIDIA_GPU_COUNT:-1} + 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..5846577e9 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,17 @@ services: file: ../common-services.yaml service: teams-do-common + teams-do-telemetry: + extends: + file: ../common-services.yaml + service: teams-do-telemetry-common + depends_on: + teams-do: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + volumes: plugins-vol: + telemetry-socket: diff --git a/docker/internal-auth/compose.plugins.yaml b/docker/internal-auth/compose.plugins.yaml index 1eefc9b95..7e8d116e6 100644 --- a/docker/internal-auth/compose.plugins.yaml +++ b/docker/internal-auth/compose.plugins.yaml @@ -34,5 +34,33 @@ services: file: ../common-services.yaml service: teams-cas-common + telemetry-redis: + extends: + file: ../common-services.yaml + service: telemetry-redis-common + + fiftyone-app-telemetry: + extends: + file: ../common-services.yaml + service: fiftyone-app-telemetry-common + depends_on: + fiftyone-app: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + + teams-api-telemetry: + extends: + file: ../common-services.yaml + service: teams-api-telemetry-common + depends_on: + teams-api: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + volumes: plugins-vol: + telemetry-redis-data: diff --git a/docker/internal-auth/compose.yaml b/docker/internal-auth/compose.yaml index 896585019..5ad3967a9 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,33 @@ services: extends: file: ../common-services.yaml service: teams-cas-common + + telemetry-redis: + extends: + file: ../common-services.yaml + service: telemetry-redis-common + + fiftyone-app-telemetry: + extends: + file: ../common-services.yaml + service: fiftyone-app-telemetry-common + depends_on: + fiftyone-app: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + + teams-api-telemetry: + extends: + file: ../common-services.yaml + service: teams-api-telemetry-common + depends_on: + teams-api: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + +volumes: + telemetry-redis-data: diff --git a/docker/internal-auth/env.template b/docker/internal-auth/env.template index 3933cdaf1..5760196a9 100644 --- a/docker/internal-auth/env.template +++ b/docker/internal-auth/env.template @@ -98,3 +98,32 @@ 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 (bundled by default; overrides below are optional) ────── +# Telemetry (`telemetry-redis` + per-service sidecars) is included in +# every compose file by default. The variables below only need to be set +# when overriding the bundled defaults. See +# docker/docs/configuring-telemetry.md to opt out or swap in an external +# Redis. + +# Redis URL used by the telemetry sidecars and the main fiftyone-app / +# teams-api / teams-app / teams-plugins / teams-do containers. Leave +# blank to use the bundled in-stack Redis (redis://telemetry-redis:6379). +# Override to point at a managed Redis (e.g. ElastiCache, MemoryStore). +# FIFTYONE_TELEMETRY_REDIS_URL= + +# Redis image, max memory, and eviction policy for the bundled telemetry +# backend. The defaults mirror the helm `telemetry.redis.*` settings. +# TELEMETRY_REDIS_IMAGE=redis:7-alpine +# TELEMETRY_REDIS_MAXMEMORY=400mb +# TELEMETRY_REDIS_MAXMEMORY_POLICY=allkeys-lru + +# Namespace label applied to all telemetry pods registered by sidecars. +# TELEMETRY_NAMESPACE=docker + +# 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..7e679a4d5 100644 --- a/docker/legacy-auth/compose.dedicated-plugins.yaml +++ b/docker/legacy-auth/compose.dedicated-plugins.yaml @@ -38,5 +38,44 @@ services: volumes: - plugins-vol:/opt/plugins:ro + telemetry-redis: + extends: + file: ../common-services.yaml + service: telemetry-redis-common + + fiftyone-app-telemetry: + extends: + file: ../common-services.yaml + service: fiftyone-app-telemetry-common + depends_on: + fiftyone-app: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + + teams-api-telemetry: + extends: + file: ../common-services.yaml + service: teams-api-telemetry-common + depends_on: + teams-api: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + + teams-plugins-telemetry: + extends: + file: ../common-services.yaml + service: teams-plugins-telemetry-common + depends_on: + teams-plugins: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + 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..1f19f9c66 --- /dev/null +++ b/docker/legacy-auth/compose.delegated-operators.gpu.yaml @@ -0,0 +1,93 @@ +--- +# 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.19.0 + 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: voxel51/telemetry-sidecar:v2.19.0 + pid: "service:teams-do-gpu" + profiles: ["gpu"] + cap_add: + - SYS_PTRACE + deploy: + resources: + limits: + cpus: "0.10" + memory: 512M + reservations: + cpus: "0.10" + memory: 512M + devices: + - driver: nvidia + count: ${NVIDIA_GPU_COUNT:-1} + 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..5846577e9 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,17 @@ services: file: ../common-services.yaml service: teams-do-common + teams-do-telemetry: + extends: + file: ../common-services.yaml + service: teams-do-telemetry-common + depends_on: + teams-do: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + volumes: plugins-vol: + telemetry-socket: diff --git a/docker/legacy-auth/compose.plugins.yaml b/docker/legacy-auth/compose.plugins.yaml index c71f27224..89794d4c7 100644 --- a/docker/legacy-auth/compose.plugins.yaml +++ b/docker/legacy-auth/compose.plugins.yaml @@ -33,5 +33,33 @@ services: CAS_URL: ${AUTH0_BASE_URL} NEXTAUTH_URL: ${AUTH0_BASE_URL}/cas/api/auth + telemetry-redis: + extends: + file: ../common-services.yaml + service: telemetry-redis-common + + fiftyone-app-telemetry: + extends: + file: ../common-services.yaml + service: fiftyone-app-telemetry-common + depends_on: + fiftyone-app: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + + teams-api-telemetry: + extends: + file: ../common-services.yaml + service: teams-api-telemetry-common + depends_on: + teams-api: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + volumes: plugins-vol: + telemetry-redis-data: diff --git a/docker/legacy-auth/compose.yaml b/docker/legacy-auth/compose.yaml index c463cf25e..ed70bbda4 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,33 @@ services: extends: file: ../common-services.yaml service: teams-cas-common + + telemetry-redis: + extends: + file: ../common-services.yaml + service: telemetry-redis-common + + fiftyone-app-telemetry: + extends: + file: ../common-services.yaml + service: fiftyone-app-telemetry-common + depends_on: + fiftyone-app: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + + teams-api-telemetry: + extends: + file: ../common-services.yaml + service: teams-api-telemetry-common + depends_on: + teams-api: + condition: service_started + restart: true + telemetry-redis: + condition: service_started + +volumes: + telemetry-redis-data: diff --git a/docker/legacy-auth/env.template b/docker/legacy-auth/env.template index 42c5cc963..49af6ec57 100644 --- a/docker/legacy-auth/env.template +++ b/docker/legacy-auth/env.template @@ -103,3 +103,32 @@ 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 (bundled by default; overrides below are optional) ────── +# Telemetry (`telemetry-redis` + per-service sidecars) is included in +# every compose file by default. The variables below only need to be set +# when overriding the bundled defaults. See +# docker/docs/configuring-telemetry.md to opt out or swap in an external +# Redis. + +# Redis URL used by the telemetry sidecars and the main fiftyone-app / +# teams-api / teams-app / teams-plugins / teams-do containers. Leave +# blank to use the bundled in-stack Redis (redis://telemetry-redis:6379). +# Override to point at a managed Redis (e.g. ElastiCache, MemoryStore). +# FIFTYONE_TELEMETRY_REDIS_URL= + +# Redis image, max memory, and eviction policy for the bundled telemetry +# backend. The defaults mirror the helm `telemetry.redis.*` settings. +# TELEMETRY_REDIS_IMAGE=redis:7-alpine +# TELEMETRY_REDIS_MAXMEMORY=400mb +# TELEMETRY_REDIS_MAXMEMORY_POLICY=allkeys-lru + +# Namespace label applied to all telemetry pods registered by sidecars. +# TELEMETRY_NAMESPACE=docker + +# 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..96abc7ae6 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) +- [Telemetry Sidecar](#telemetry-sidecar) - [Refresh Orchestrator Operators](#refresh-orchestrator-operators) - [Additional Considerations](#additional-considerations) - [Credential Rotation](#credential-rotation) @@ -459,6 +460,109 @@ fom.register_orchestrator( ) ``` +## Telemetry Sidecar + +> [!NOTE] +> The telemetry sidecar can be disabled, but doing so disables the +> FiftyOne UI's log viewer for delegated-operator runs — it depends +> on the sidecar to capture per-operation logs. + +If your deployment runs telemetry (the helm chart and docker compose +files include it by default), you can attach a per-Job telemetry sidecar +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:v2.19.0 + restartPolicy: Always + securityContext: + # The sidecar image runs as root (SYS_PTRACE + + # /proc//fd/1 access require it). Set explicitly so it + # works even when the pod's podSecurityContext sets + # runAsNonRoot: true. + runAsNonRoot: false + runAsUser: 0 + capabilities: + add: [SYS_PTRACE] + env: + - name: TARGET_NAME + value: "fiftyone 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-teams.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-teams.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 +646,59 @@ spec: serviceAccountName: your-org-fiftyone-teams podSecurityContext: runAsNonRoot: false + shareProcessNamespace: true + initContainers: + - name: telemetry-sidecar + image: voxel51/telemetry-sidecar:v2.19.0 + restartPolicy: Always + securityContext: + # The sidecar image runs as root (SYS_PTRACE + + # /proc//fd/1 access require it). Set explicitly so it + # works even when the pod's podSecurityContext sets + # runAsNonRoot: true. + runAsNonRoot: false + runAsUser: 0 + capabilities: + add: [SYS_PTRACE] + env: + - name: TARGET_NAME + value: "fiftyone 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 +742,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 +779,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 +815,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/docs/upgrading.md b/helm/docs/upgrading.md index 873e5e93d..7ccfd1f48 100644 --- a/helm/docs/upgrading.md +++ b/helm/docs/upgrading.md @@ -19,6 +19,9 @@ - [Upgrading From Previous Versions](#upgrading-from-previous-versions) - [A Note On Database Migrations](#a-note-on-database-migrations) - [From FiftyOne Enterprise Version 2.0.0 or Higher](#from-fiftyone-enterprise-version-200-or-higher) + - [FiftyOne Enterprise v2.19+ Telemetry Sidecars](#fiftyone-enterprise-v219-telemetry-sidecars) + - [Cluster Requirements](#cluster-requirements) + - [Opting out of Telemetry](#opting-out-of-telemetry) - [FiftyOne Enterprise v2.16+ Additional API Routes](#fiftyone-enterprise-v216-additional-api-routes) - [FiftyOne Enterprise v2.15+ Additional API Routes](#fiftyone-enterprise-v215-additional-api-routes) - [FiftyOne Enterprise v2.9+ Startup Probe Changes](#fiftyone-enterprise-v29-startup-probe-changes) @@ -141,6 +144,73 @@ quickstart 0.21.2 fiftyone migrate --info ``` +#### FiftyOne Enterprise v2.19+ Telemetry Sidecars + +FiftyOne Enterprise v2.19.0 adds observability features that are viewable +by admins directly within the FiftyOne UI. +This is enabled by a `telemetry-sidecar` container, which is added to the +`teams-api`, `fiftyone-app`, `teams-plugins`, and delegated-operator +workloads (and as a native init-sidecar on on-demand delegated-operator `Job` +pods), plus an in-cluster Redis `Deployment` + `Service` + +`PersistentVolumeClaim` that buffers the streamed metrics/logs. + +**Resource impact:** Each sidecar requests `100m` CPU and `512Mi` memory +(request == limit). +A stock deploy adds four sidecars (`teams-api` + `fiftyone-app` + +`teams-plugins` + one delegated-operator), so expect roughly **+400m CPU** and +**+2 GiB memory** of additional resource usage, plus the bundled Redis (`100m` +/ `256Mi` requests, `250m` / `512Mi` limits) and its `1Gi` PVC. +Tune via `telemetry.sidecar.resources` and `telemetry.redis.resources` / +`telemetry.redis.persistence.size`. + +##### Cluster Requirements + +1. **`shareProcessNamespace: true`** is set on all four workloads so + the sidecar can read `/proc//fd/1` of the target container. +1. **The sidecar runs as root with `SYS_PTRACE`** (required for + `py-spy` and `/proc` access). + Clusters that enforce + [Pod Security Admission][psa] + `restricted`, or admission policies (OPA/Gatekeeper, Kyverno) that block + `runAsUser: 0` or capability adds, will reject these pods at + admission. + On such clusters, either: + - allow the chart's `namespace.name` namespace at PSA `baseline` + (or relax the relevant admission policy), or + - disable telemetry with `telemetry.enabled: false`. + +**External Redis:** To point at a managed Redis (ElastiCache, +MemoryStore, an existing cluster) instead of running the bundled one, +set: + +```yaml +telemetry: + redis: + external: + url: redis://my-managed-redis.example.com:6379 +``` + +The chart will skip the bundled Redis' `Deployment` / `Service` / `PVC` and +wire every consumer at this URL. + +##### Opting out of Telemetry + +The new Telemetry features are enabled by default, but can be disabled by +setting the following in your `values.yaml` file: + +```yaml +telemetry: + enabled: false +``` + +> [!IMPORTANT] +> The sidecar powers the FiftyOne UI's delegated-operator log viewer. +> Disabling telemetry (`telemetry.enabled: false`) will leave that +> log viewer empty. + +This skips every telemetry resource (Redis, sidecars, Role/RoleBinding) +and causes workloads to match their pre-v2.19 shape. + #### FiftyOne Enterprise v2.16+ Additional API Routes FiftyOne Enterprise v2.16.0 adds the `/cloud_credentials` endpoint to the `teams-api`. @@ -596,3 +666,4 @@ existing configuration to migrate to a new Auth0 Tenant. [init-containers]: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ +[psa]: https://kubernetes.io/docs/concepts/security/pod-security-admission/ diff --git a/helm/fiftyone-teams-app/README.md b/helm/fiftyone-teams-app/README.md index 8b066e31c..1ed8cfe29 100644 --- a/helm/fiftyone-teams-app/README.md +++ b/helm/fiftyone-teams-app/README.md @@ -745,7 +745,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | apiSettings.podDisruptionBudget | object | `{"enabled":false,"minAvailable":null}` | Pod Disruption Budget for pods for `teams-api`. [Reference][pod-disruption-budget]. | | apiSettings.podDisruptionBudget.enabled | bool | `false` | Whether a pod disruption budget is enabled for `teams-api`. | | apiSettings.podDisruptionBudget.minAvailable | string | `nil` | Sets the minimum available or maximum unavailable replicas for the deployment object. Either integers or percentages supported. `maxUnavailable` is also supported, however, only one setting can be used at a time. If both are set, `minAvailable` will be preferred. | -| apiSettings.podSecurityContext | object | `{}` | Pod-level security attributes and common container settings for `teams-api`. [Reference][security-context]. | +| apiSettings.podSecurityContext | object | `{"fsGroup":1000,"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000}` | Pod-level security attributes and common container settings for `teams-api`. [Reference][security-context]. Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. | | apiSettings.rbac | object | `{"create":true,"role":{"annotations":{},"labels":{},"name":""},"roleBinding":{"annotations":{},"create":true,"labels":{},"name":""},"serviceAccount":{"annotations":{},"create":true,"labels":{},"name":""}}` | RBAC roles, bindings, and service accounts which will be used to submit on-demand delegated operators to the kubernetes API. If `apiSettings.rbac.create=true`, these will be used by the `teams-api` pods. | | apiSettings.rbac.create | bool | `true` | Controls whether to create the `Role`, `RoleBinding`, and `ServiceAccount` for on-demand delegated-operator submission. | | apiSettings.rbac.role.annotations | object | `{}` | `Role` annotations. [Reference][annotations]. | @@ -812,7 +812,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | appSettings.podDisruptionBudget | object | `{"enabled":false,"minAvailable":null}` | Pod Disruption Budget for pods for `fiftyone-app`. [Reference][pod-disruption-budget]. | | appSettings.podDisruptionBudget.enabled | bool | `false` | Whether a pod disruption budget is enabled for `fiftyone-app`. | | appSettings.podDisruptionBudget.minAvailable | string | `nil` | Sets the minimum available or maximum unavailable replicas for the deployment object. Either integers or percentages supported. `maxUnavailable` is also supported, however, only one setting can be used at a time. If both are set, `minAvailable` will be preferred. | -| appSettings.podSecurityContext | object | `{}` | Pod-level security attributes and common container settings for `fiftyone-app`. [Reference][security-context]. | +| appSettings.podSecurityContext | object | `{"fsGroup":1000,"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000}` | Pod-level security attributes and common container settings for `fiftyone-app`. [Reference][security-context]. Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. | | appSettings.readiness.failureThreshold | int | `5` | Number of times to retry the readiness probe for the `fiftyone-app`. [Reference][probes]. | | appSettings.readiness.periodSeconds | int | `15` | How often (in seconds) to perform the readiness probe for `fiftyone-app`. [Reference][probes]. | | appSettings.readiness.timeoutSeconds | int | `5` | Timeout for the readiness probe for the `fiftyone-app`. [Reference][probes]. | @@ -885,7 +885,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | casSettings.volumes | list | `[]` | Volumes for `teams-cas`. [Reference][volumes]. | | delegatedOperatorDeployments.deployments | object | `{"teamsDoCpuDefault":{"enabled":false,"env":{"FIFTYONE_MEDIA_CACHE_DIR":"/opt/media_cache","FIFTYONE_MEDIA_CACHE_SIZE_BYTES":"2147483648"},"replicaCount":1,"resources":{"limits":{"cpu":4,"ephemeral-storage":"1Gi","memory":"16Gi"},"requests":{"cpu":4,"ephemeral-storage":"1Gi","memory":"16Gi"}},"volumeMounts":[{"mountPath":"/dev/shm","name":"shm-vol"},{"mountPath":"/opt/media_cache","name":"memory-media-cache-vol"}],"volumes":[{"emptyDir":{"medium":"Memory","sizeLimit":"2Gi"},"name":"shm-vol"},{"emptyDir":{"medium":"Memory","sizeLimit":"2.5Gi"},"name":"memory-media-cache-vol"}]}}` | Additional deployments to configure. Each template will use .Values.delegatedOperatorDeployments.template as a base. Each template value may be overridden. Maps/dictionaries will be merged key-wise, with the deployment instance taking precedence. List values will not be merged, but be overridden completely by the deployment instance. | | delegatedOperatorDeployments.deployments.teamsDoCpuDefault | object | `{"enabled":false,"env":{"FIFTYONE_MEDIA_CACHE_DIR":"/opt/media_cache","FIFTYONE_MEDIA_CACHE_SIZE_BYTES":"2147483648"},"replicaCount":1,"resources":{"limits":{"cpu":4,"ephemeral-storage":"1Gi","memory":"16Gi"},"requests":{"cpu":4,"ephemeral-storage":"1Gi","memory":"16Gi"}},"volumeMounts":[{"mountPath":"/dev/shm","name":"shm-vol"},{"mountPath":"/opt/media_cache","name":"memory-media-cache-vol"}],"volumes":[{"emptyDir":{"medium":"Memory","sizeLimit":"2Gi"},"name":"shm-vol"},{"emptyDir":{"medium":"Memory","sizeLimit":"2.5Gi"},"name":"memory-media-cache-vol"}]}` | Default (CPU-only) delegated operator runner. Defaults to an 4vCPU, 16Gi RAM runner. The deployment is backed with a 2.5Gi in-memory volume for caching media and an extra 2Gi of shared memory. | -| delegatedOperatorDeployments.template | object | `{"affinity":{},"deploymentAnnotations":{},"description":"","env":{"FIFTYONE_DELEGATED_OPERATION_LOG_PATH":"","FIFTYONE_INTERNAL_SERVICE":true,"FIFTYONE_MEDIA_CACHE_SIZE_BYTES":-1},"image":{"pullPolicy":"Always","repository":"voxel51/fiftyone-teams-cv-full","tag":""},"labels":{},"liveness":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"nodeSelector":{},"podAnnotations":{},"podDisruptionBudget":{"enabled":false,"minAvailable":null},"podSecurityContext":{},"readiness":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"replicaCount":3,"resources":{"limits":{},"requests":{}},"secretEnv":{},"securityContext":{},"startup":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"tolerations":[],"topologySpreadConstraints":[],"updateStrategy":{"type":"RollingUpdate"},"volumeMounts":[],"volumes":[]}` | A common template applied to all deployments. Each deployment can then override individual fields as needed by the operator. | +| delegatedOperatorDeployments.template | object | `{"affinity":{},"deploymentAnnotations":{},"description":"","env":{"FIFTYONE_DELEGATED_OPERATION_LOG_PATH":"","FIFTYONE_INTERNAL_SERVICE":true,"FIFTYONE_MEDIA_CACHE_SIZE_BYTES":-1},"image":{"pullPolicy":"Always","repository":"voxel51/fiftyone-teams-cv-full","tag":""},"labels":{},"liveness":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"nodeSelector":{},"podAnnotations":{},"podDisruptionBudget":{"enabled":false,"minAvailable":null},"podSecurityContext":{"fsGroup":1000,"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000},"readiness":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"replicaCount":3,"resources":{"limits":{},"requests":{}},"secretEnv":{},"securityContext":{},"startup":{"failureThreshold":5,"periodSeconds":30,"timeoutSeconds":30},"tolerations":[],"topologySpreadConstraints":[],"updateStrategy":{"type":"RollingUpdate"},"volumeMounts":[],"volumes":[]}` | A common template applied to all deployments. Each deployment can then override individual fields as needed by the operator. | | delegatedOperatorDeployments.template.affinity | object | `{}` | Affinity and anti-affinity for `delegated-operator-executor`. [Reference][affinity]. | | delegatedOperatorDeployments.template.deploymentAnnotations | object | `{}` | Annotations for the `teams-do` deployment. [Reference][annotations]. | | delegatedOperatorDeployments.template.description | string | `""` | A description for the delegated operator instance. This is unused in the template context. Each operator should either set their own description or, optionally, use the default. If unset at the operator context, it will be defaulted to `Long running operations delegated to $name` where `$name` is the name of the Deployment object. | @@ -903,7 +903,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | delegatedOperatorDeployments.template.podAnnotations | object | `{}` | Annotations for delegated-operator-executor pods. [Reference][annotations]. | | delegatedOperatorDeployments.template.podDisruptionBudget.enabled | bool | `false` | Whether a pod disruption budget is enabled for `teams-plugins`. | | delegatedOperatorDeployments.template.podDisruptionBudget.minAvailable | string | `nil` | Sets the minimum available or maximum unavailable replicas for the deployment object. Either integers or percentages supported. `maxUnavailable` is also supported, however, only one setting can be used at a time. If both are set, `minAvailable` will be preferred. | -| delegatedOperatorDeployments.template.podSecurityContext | object | `{}` | Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context]. | +| delegatedOperatorDeployments.template.podSecurityContext | object | `{"fsGroup":1000,"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000}` | Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context]. Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. | | delegatedOperatorDeployments.template.readiness.failureThreshold | int | `5` | Number of times to retry the readiness probe for the `teams-do`. [Reference][probes]. | | delegatedOperatorDeployments.template.readiness.periodSeconds | int | `30` | How often (in seconds) to perform the readiness probe for `teams-do`. [Reference][probes]. | | delegatedOperatorDeployments.template.readiness.timeoutSeconds | int | `30` | Timeout for the readiness probe for the `teams-do`. [Reference][probes]. | @@ -924,7 +924,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | delegatedOperatorJobTemplates.configMap.labels | object | `{}` | Additional labels for the generated `ConfigMap`. [Reference][labels-and-selectors]. | | delegatedOperatorJobTemplates.configMap.name | string | `""` | Name of the `ConfigMap` (existing or to be created) in the namespace `namespace.name` used for DO templates. Defaults to `release-name-fiftyone-teams-app-do-templates`. | | delegatedOperatorJobTemplates.jobs | object | `{}` | On-Demand Delegated Operator Jobs. | -| delegatedOperatorJobTemplates.template | object | `{"activeDeadlineSeconds":null,"affinity":{},"backoffLimit":null,"completions":null,"containerSecurityContext":{},"env":{"FIFTYONE_DELEGATED_OPERATION_LOG_PATH":"","FIFTYONE_MEDIA_CACHE_SIZE_BYTES":-1},"image":{"pullPolicy":"Always","repository":"voxel51/fiftyone-teams-cv-full","tag":""},"jobAnnotations":{},"labels":{},"nodeSelector":{},"podAnnotations":{},"podSecurityContext":{},"resources":{"limits":{},"requests":{}},"secretEnv":{},"tolerations":[],"ttlSecondsAfterFinished":null,"volumeMounts":[],"volumes":[]}` | A common template applied to all deployments. Each deployment can then override individual fields as needed by the operator. | +| delegatedOperatorJobTemplates.template | object | `{"activeDeadlineSeconds":null,"affinity":{},"backoffLimit":null,"completions":null,"containerSecurityContext":{},"env":{"FIFTYONE_DELEGATED_OPERATION_LOG_PATH":"","FIFTYONE_MEDIA_CACHE_SIZE_BYTES":-1},"image":{"pullPolicy":"Always","repository":"voxel51/fiftyone-teams-cv-full","tag":""},"jobAnnotations":{},"labels":{},"nodeSelector":{},"podAnnotations":{},"podSecurityContext":{"fsGroup":1000,"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000},"resources":{"limits":{},"requests":{}},"secretEnv":{},"tolerations":[],"ttlSecondsAfterFinished":null,"volumeMounts":[],"volumes":[]}` | A common template applied to all deployments. Each deployment can then override individual fields as needed by the operator. | | delegatedOperatorJobTemplates.template.activeDeadlineSeconds | optional | `nil` | Maximum of seconds a job should be able to run. [Reference][job-termination-and-cleanup]. | | delegatedOperatorJobTemplates.template.affinity | object | `{}` | Affinity and anti-affinity for `delegated-operator-executor`. [Reference][affinity]. | | delegatedOperatorJobTemplates.template.backoffLimit | optional | `nil` | Amount of retries for the job to try before being marked as "Failed". [Reference][job-backoff-failure-policy]. | @@ -939,7 +939,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | delegatedOperatorJobTemplates.template.labels | object | `{}` | Additional labels for the `delegated-operator-executor` related objects. [Reference][labels-and-selectors]. | | delegatedOperatorJobTemplates.template.nodeSelector | object | `{}` | nodeSelector for `delegated-operator-executor`. [Reference][node-selector]. | | delegatedOperatorJobTemplates.template.podAnnotations | object | `{}` | Annotations for delegated-operator-executor pods. [Reference][annotations]. | -| delegatedOperatorJobTemplates.template.podSecurityContext | object | `{}` | Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context]. | +| delegatedOperatorJobTemplates.template.podSecurityContext | object | `{"fsGroup":1000,"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000}` | Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context]. Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. | | delegatedOperatorJobTemplates.template.resources | object | `{"limits":{},"requests":{}}` | Container resource requests and limits for `delegated-operator-executor`. [Reference][resources]. | | delegatedOperatorJobTemplates.template.secretEnv | object | `{}` | Secret variables to be passed to the delegated-operator-executor containers. | | delegatedOperatorJobTemplates.template.tolerations | list | `[]` | Allow the k8s scheduler to schedule delegated-operator-executor pods with matching taints. [Reference][taints-and-tolerations]. | @@ -994,7 +994,7 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | pluginsSettings.podDisruptionBudget | object | `{"enabled":false,"minAvailable":null}` | Pod Disruption Budget for pods for `teams-plugins`. [Reference][pod-disruption-budget]. | | pluginsSettings.podDisruptionBudget.enabled | bool | `false` | Whether a pod disruption budget is enabled for `teams-plugins`. | | pluginsSettings.podDisruptionBudget.minAvailable | string | `nil` | Sets the minimum available or maximum unavailable replicas for the deployment object. Either integers or percentages supported. `maxUnavailable` is also supported, however, only one setting can be used at a time. If both are set, `minAvailable` will be preferred. | -| pluginsSettings.podSecurityContext | object | `{}` | Pod-level security attributes and common container settings for `teams-plugins`. [Reference][security-context]. | +| pluginsSettings.podSecurityContext | object | `{"fsGroup":1000,"runAsGroup":1000,"runAsNonRoot":true,"runAsUser":1000}` | Pod-level security attributes and common container settings for `teams-plugins`. [Reference][security-context]. Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. | | pluginsSettings.readiness.failureThreshold | int | `5` | Number of times to retry the readiness probe for the `teams-plugins`. [Reference][probes]. | | pluginsSettings.readiness.periodSeconds | int | `15` | How often (in seconds) to perform the readiness probe for `teams-plugins`. [Reference][probes]. | | pluginsSettings.readiness.timeoutSeconds | int | `5` | Timeout for the readiness probe for the `teams-plugins`. [Reference][probes]. | @@ -1085,6 +1085,23 @@ If pods show unhealthy states (e.g., `0/1`, `CrashLoopBackOff`, `Pending`): | teamsAppSettings.updateStrategy | object | `{"type":"RollingUpdate"}` | Control how `teams-app` pods are redeployed during an upgrade. [Reference][upgrade-strategies] | | teamsAppSettings.volumeMounts | list | `[]` | Volume mounts for `teams-app` pods. [Reference][volumes]. | | teamsAppSettings.volumes | list | `[]` | Volumes for `teams-app` pods. [Reference][volumes]. | +| telemetry.enabled | bool | `true` | Master switch. When `true` (default), all telemetry resources and sidecars are rendered. When `false`, none are — note that the FiftyOne UI's delegated-operator log viewer depends on the sidecar and will be empty without it. | +| telemetry.redis.containerSecurityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true}` | Container-level security attributes for the telemetry Redis. `readOnlyRootFilesystem: true` is safe because writes go to the `/data` volume. [Reference][container-security-context]. | +| telemetry.redis.external.url | string | `""` | URL of an external Redis to use instead of the bundled one (e.g. `redis://my-redis.example.com:6379`). | +| telemetry.redis.image | string | `"redis:7-alpine"` | Container image for the telemetry Redis Deployment. | +| telemetry.redis.maxmemory | string | `"400mb"` | `--maxmemory` flag passed to `redis-server`. | +| telemetry.redis.maxmemoryPolicy | string | `"allkeys-lru"` | `--maxmemory-policy` flag passed to `redis-server`. | +| telemetry.redis.persistence.enabled | bool | `false` | Controls whether a `PersistentVolumeClaim` is created for the bundled telemetry Redis. Defaults to `false` (emptyDir) so the chart installs cleanly on clusters without a dynamic PV provisioner or a default `StorageClass`. Telemetry archives flush to MongoDB every ~10 minutes (`metrics_history`, `service_logs`, completed DO runs), so what you lose on a Redis pod restart is only the in-window dashboard backscroll (~10 minutes of un-archived data, plus any live op buffers). Other workload crashes do NOT affect Redis state — only a Redis pod restart drops the in-memory window. Set to `true` to provision a PVC and have Redis recover its in-window data across pod restarts (requires a working `StorageClass`, or set `existingClaim` below to point at a pre-provisioned PVC). | +| telemetry.redis.persistence.existingClaim | string | `""` | Name of an existing `PersistentVolumeClaim` (in `namespace.name`) to bind the telemetry Redis to. When set, the chart will NOT create a `PersistentVolumeClaim` and the `size` / `storageClass` fields above are ignored — you are responsible for provisioning the claim out of band. Use this on clusters without a dynamic PV provisioner, or to reuse an existing volume across releases. Has no effect when `persistence.enabled` is `false`. | +| telemetry.redis.persistence.size | string | `"1Gi"` | Storage size for the telemetry Redis `PersistentVolumeClaim`. | +| telemetry.redis.persistence.storageClass | string | `""` | `StorageClass` name for the telemetry Redis `PersistentVolumeClaim`. Leave unset to use the cluster's default `StorageClass`. | +| telemetry.redis.podSecurityContext | object | `{"fsGroup":999,"runAsGroup":999,"runAsNonRoot":true,"runAsUser":999}` | Pod-level security attributes for the telemetry Redis. Defaults to a non-root UID/GID matching the `redis` user in the upstream `redis:7-alpine` image (999). `fsGroup` ensures the mounted `/data` volume is group-writable. [Reference][security-context]. | +| telemetry.redis.resources | object | `{"limits":{"cpu":"250m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"256Mi"}}` | Resource requests/limits for the telemetry Redis container. [Reference][resources]. | +| telemetry.serviceAccounts | list | `[]` | ServiceAccount names (in `namespace.name`) bound to the telemetry pod-logs Role. Each entry becomes a `ServiceAccount` subject on the generated RoleBinding. When empty, the RoleBinding binds the chart's main app service account (used by sidecars on `fiftyone-app`, `teams-plugins`, and the delegated-operator workloads). The teams-api sidecar uses the teams-api RBAC service account, which already has `pods/log` GET via `api-role.yaml` and does not need to be re-bound. | +| telemetry.sidecar.image.pullPolicy | string | `"Always"` | Instruct when the kubelet should pull (download) the specified image. One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. | +| telemetry.sidecar.image.repository | string | `"voxel51/telemetry-sidecar"` | Container image for `telemetry-sidecar`. | +| telemetry.sidecar.image.tag | string | `""` | Image tag for `telemetry-sidecar`. Defaults to `Chart.AppVersion`. | +| telemetry.sidecar.resources | object | `{"limits":{"cpu":"100m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"512Mi"}}` | Resource requests/limits for each `telemetry-sidecar` container. | diff --git a/helm/fiftyone-teams-app/templates/NOTES.txt b/helm/fiftyone-teams-app/templates/NOTES.txt index c85b16b50..694f3cdf3 100644 --- a/helm/fiftyone-teams-app/templates/NOTES.txt +++ b/helm/fiftyone-teams-app/templates/NOTES.txt @@ -4,6 +4,13 @@ apiSettings.replicaCount will be set to 1 for this deployment. Please see https://helm.fiftyone.ai for details. {{ end }} - +{{- if and .Values.telemetry.enabled (not .Values.telemetry.redis.external.url) (not .Values.telemetry.redis.persistence.enabled) }} +[INFO] Telemetry Redis is running on an emptyDir volume (no PersistentVolumeClaim). + Long-term telemetry archives in MongoDB are unaffected, but the in-window + dashboard backscroll (~10 minutes of un-archived data) is lost if the + Redis pod restarts. To make telemetry survive Redis restarts, set + `telemetry.redis.persistence.enabled: true` (needs a working StorageClass + or `telemetry.redis.persistence.existingClaim` pointing at a pre-created PVC). +{{ end }} Visit the following URL to access your FiftyOne Enterprise application: http{{ if $.Values.ingress.tlsEnabled }}s{{ end }}://{{ .Values.teamsAppSettings.dnsName }} diff --git a/helm/fiftyone-teams-app/templates/_do_targets.tpl b/helm/fiftyone-teams-app/templates/_do_targets.tpl index 7806b82ca..1466435aa 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: {{ include "telemetry.redis.url" .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..848051e24 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: {{ include "telemetry.redis.url" .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..2daccc79e --- /dev/null +++ b/helm/fiftyone-teams-app/templates/_telemetry.tpl @@ -0,0 +1,243 @@ +{{/* +Name of the telemetry redis Deployment/Service/PVC. +*/}} +{{- define "telemetry.redis.name" -}} +{{- printf "%s-telemetry-redis" .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- end }} + +{{/* +Resolves the URL for the telemetry Redis backend. + +If `telemetry.redis.external.url` is set, returns it (chart skips the +bundled Redis Deployment/Service/PVC and consumer workloads + sidecars +are wired at the external URL instead — e.g. for managed Redis like +ElastiCache or MemoryStore). Otherwise returns the in-cluster Service +URL of the bundled Redis as a fully-qualified `..svc.cluster.local` +hostname so cross-namespace consumers (e.g. delegated-operator Jobs +scheduled into a different namespace) still resolve it. + +Always returns a non-empty URL when telemetry is enabled. +*/}} +{{- define "telemetry.redis.url" -}} +{{- if .Values.telemetry.redis.external.url -}} +{{- .Values.telemetry.redis.external.url -}} +{{- else -}} +{{- printf "redis://%s.%s.svc.cluster.local:6379" (include "telemetry.redis.name" .) .Values.namespace.name -}} +{{- end -}} +{{- 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 the main app service account — the SA used by the auto-injected +sidecars on app/plugins/delegated-operator pods. + +The teams-api sidecar uses the teams-api RBAC service account, which is already +granted `pods/log` GET by api-role.yaml; binding it here would be a redundant +duplicate. When `apiSettings.rbac.create` is false, api-deployment falls back to +the main app SA anyway, which this RoleBinding still covers. +*/}} +{{- define "telemetry.role.subjects" -}} +{{- $appSA := include "fiftyone-teams-app.serviceAccountName" . | trim -}} +{{- $defaultSubjects := list $appSA -}} +{{- $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: {{ include "telemetry.redis.url" .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.repository }}:{{ .ctx.Values.telemetry.sidecar.image.tag | default .ctx.Chart.AppVersion }}" + imagePullPolicy: {{ .ctx.Values.telemetry.sidecar.image.pullPolicy | default "Always" }} + env: + {{- include "telemetry.sidecar-env" . | nindent 4 }} + {{- with .ctx.Values.telemetry.sidecar.resources }} + resources: + {{- toYaml . | nindent 4 }} + {{- end }} + securityContext: + # Match the paired workload's UID so same-UID /proc reads work without elevated caps. + {{- if .targetUid }} + runAsUser: {{ .targetUid }} + runAsNonRoot: {{ ne (int .targetUid) 0 }} + {{- else }} + runAsNonRoot: false + runAsUser: 0 + {{- end }} + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + {{- if .executor }} + add: + - SYS_PTRACE + {{- end }} + {{- if .executor }} + volumeMounts: + - name: telemetry-socket + mountPath: /tmp/telemetry + {{- end }} +{{- end }} + +{{/* +A native-sidecar (initContainer with restartPolicy: Always) variant — for use under +spec.initContainers in DO Jobs, where a regular sidecar container that does not exit +would block Job completion. +*/}} +{{- define "telemetry.native-sidecar" -}} +- name: telemetry-sidecar + image: "{{ .ctx.Values.telemetry.sidecar.image.repository }}:{{ .ctx.Values.telemetry.sidecar.image.tag | default .ctx.Chart.AppVersion }}" + imagePullPolicy: {{ .ctx.Values.telemetry.sidecar.image.pullPolicy | default "Always" }} + restartPolicy: Always + env: + {{- include "telemetry.sidecar-env" . | nindent 4 }} + {{- 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: + # Match the paired workload's UID so same-UID /proc reads work without elevated caps. + {{- if .targetUid }} + runAsUser: {{ .targetUid }} + runAsNonRoot: {{ ne (int .targetUid) 0 }} + {{- else }} + runAsNonRoot: false + runAsUser: 0 + {{- end }} + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + {{- if .executor }} + add: + - SYS_PTRACE + {{- end }} + volumeMounts: + - name: telemetry-socket + mountPath: /tmp/telemetry +{{- 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: {{ include "telemetry.redis.url" . | 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..c03eb54c2 100644 --- a/helm/fiftyone-teams-app/templates/api-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/api-deployment.yaml @@ -44,6 +44,9 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ $serviceAccountName }} + {{- if .Values.telemetry.enabled }} + shareProcessNamespace: true + {{- end }} securityContext: {{- toYaml .Values.apiSettings.podSecurityContext | nindent 8 }} containers: @@ -89,6 +92,9 @@ spec: {{- with .Values.apiSettings.volumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} + {{- if .Values.telemetry.enabled }} + {{- include "telemetry.sidecar" (dict "ctx" . "serviceType" "teams-api" "targetName" "fiftyone-teams-api" "targetUid" ((.Values.apiSettings.podSecurityContext | default dict).runAsUser)) | nindent 8 }} + {{- end }} {{- if .Values.apiSettings.initContainers.enabled }} initContainers: {{- diff --git a/helm/fiftyone-teams-app/templates/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..0e3e1e9bb 100644 --- a/helm/fiftyone-teams-app/templates/app-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/app-deployment.yaml @@ -35,6 +35,9 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" . }} + {{- if .Values.telemetry.enabled }} + shareProcessNamespace: true + {{- end }} securityContext: {{- toYaml .Values.appSettings.podSecurityContext | nindent 8 }} containers: @@ -73,6 +76,9 @@ spec: volumeMounts: {{- toYaml . | nindent 12 }} {{- end }} + {{- if .Values.telemetry.enabled }} + {{- include "telemetry.sidecar" (dict "ctx" . "serviceType" "fiftyone-app" "targetName" "hypercorn" "targetUid" ((.Values.appSettings.podSecurityContext | default dict).runAsUser)) | nindent 8 }} + {{- end }} {{- if .Values.appSettings.initContainers.enabled }} initContainers: {{- diff --git a/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml b/helm/fiftyone-teams-app/templates/delegated-operator-instance-deployment.yaml index ca93b52ce..501227b13 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 $.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,9 @@ spec: failureThreshold: {{ ($v.startup).failureThreshold | default $baseTpl.startup.failureThreshold }} periodSeconds: {{ ($v.startup).periodSeconds | default $baseTpl.startup.periodSeconds }} timeoutSeconds: {{ ($v.startup).timeoutSeconds | default $baseTpl.startup.timeoutSeconds }} + {{- if $.Values.telemetry.enabled }} + {{- include "telemetry.sidecar" (dict "ctx" $ "serviceType" "delegated-operator" "targetName" "fiftyone delegated" "executor" true "targetUid" (coalesce (($v.podSecurityContext | default dict).runAsUser) (($baseTpl.podSecurityContext | default dict).runAsUser))) | nindent 8 }} + {{- end }} {{- with (merge (dict) ($v.nodeSelector | default dict) ($baseTpl.nodeSelector)) }} nodeSelector: {{- toYaml . | nindent 8 }} @@ -129,7 +146,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..d19d8ac8a 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,24 @@ data: {{- $mergedTolerations := $jobConfig.tolerations | default $baseTpl.tolerations }} {{- $mergedVolumeMounts := $jobConfig.volumeMounts | default $baseTpl.volumeMounts }} {{- $mergedVolumes := $jobConfig.volumes | default $baseTpl.volumes }} + + {{- if $.Values.telemetry.enabled }} + {{- $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 +106,9 @@ data: spec: restartPolicy: {{ $jobConfig.restartPolicy | default $baseTpl.restartPolicy | default "Never" }} serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" $ }} + {{- if $.Values.telemetry.enabled }} + shareProcessNamespace: true + {{- end }} {{- with $mergedPodSecurityContext }} securityContext: {{- toYaml . | nindent 12 }} @@ -119,6 +141,10 @@ data: volumeMounts: {{- toYaml . | nindent 16 }} {{- end }} + {{- if $.Values.telemetry.enabled }} + initContainers: + {{- include "telemetry.native-sidecar" (dict "ctx" $ "serviceType" "delegated-operator" "targetName" "fiftyone delegated" "targetContainer" "executor" "executor" true "targetUid" ($mergedPodSecurityContext.runAsUser)) | nindent 12 }} + {{- end }} {{- with $mergedNodeSelector }} nodeSelector: {{- 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..44f2baf01 100644 --- a/helm/fiftyone-teams-app/templates/plugins-deployment.yaml +++ b/helm/fiftyone-teams-app/templates/plugins-deployment.yaml @@ -36,6 +36,9 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "fiftyone-teams-app.serviceAccountName" . }} + {{- if .Values.telemetry.enabled }} + shareProcessNamespace: true + {{- end }} securityContext: {{- toYaml .Values.pluginsSettings.podSecurityContext | nindent 8 }} containers: @@ -74,6 +77,9 @@ spec: volumeMounts: {{- toYaml . | nindent 12 }} {{- end }} + {{- if .Values.telemetry.enabled }} + {{- include "telemetry.sidecar" (dict "ctx" . "serviceType" "teams-plugins" "targetName" "hypercorn" "targetUid" ((.Values.pluginsSettings.podSecurityContext | default dict).runAsUser)) | nindent 8 }} + {{- end }} {{- if .Values.pluginsSettings.initContainers.enabled }} initContainers: {{- diff --git a/helm/fiftyone-teams-app/templates/telemetry-redis.yaml b/helm/fiftyone-teams-app/templates/telemetry-redis.yaml new file mode 100644 index 000000000..d503cafce --- /dev/null +++ b/helm/fiftyone-teams-app/templates/telemetry-redis.yaml @@ -0,0 +1,93 @@ +{{- if and .Values.telemetry.enabled (not .Values.telemetry.redis.external.url) }} +{{- if and .Values.telemetry.redis.persistence.enabled (not .Values.telemetry.redis.persistence.existingClaim) }} +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 + {{- with .Values.telemetry.redis.persistence.storageClass }} + storageClassName: {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.telemetry.redis.persistence.size | default "1Gi" }} +--- +{{- end }} +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 + strategy: + type: Recreate + selector: + matchLabels: + {{- include "telemetry.redis.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "telemetry.redis.labels" . | nindent 8 }} + spec: + {{- with .Values.telemetry.redis.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: redis + image: {{ .Values.telemetry.redis.image | default "redis:7-alpine" }} + {{- with .Values.telemetry.redis.containerSecurityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + args: + - redis-server + - --save + - "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 + {{- if .Values.telemetry.redis.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.telemetry.redis.persistence.existingClaim | default (printf "%s-data" (include "telemetry.redis.name" .)) }} + {{- else }} + emptyDir: {} + {{- end }} +--- +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 0e25880c7..da1d9780f 100644 --- a/helm/fiftyone-teams-app/values.schema.json +++ b/helm/fiftyone-teams-app/values.schema.json @@ -367,7 +367,29 @@ "type": "object" }, "podSecurityContext": { - "description": "Pod-level security attributes and common container settings for `teams-api`. [Reference][security-context].", + "description": "Pod-level security attributes and common container settings for `teams-api`. [Reference][security-context].\nImage runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload.", + "properties": { + "fsGroup": { + "default": 1000, + "title": "fsGroup", + "type": "integer" + }, + "runAsGroup": { + "default": 1000, + "title": "runAsGroup", + "type": "integer" + }, + "runAsNonRoot": { + "default": true, + "title": "runAsNonRoot", + "type": "boolean" + }, + "runAsUser": { + "default": 1000, + "title": "runAsUser", + "type": "integer" + } + }, "required": [], "title": "podSecurityContext", "type": "object" @@ -1055,7 +1077,29 @@ "type": "object" }, "podSecurityContext": { - "description": "Pod-level security attributes and common container settings for `fiftyone-app`. [Reference][security-context].", + "description": "Pod-level security attributes and common container settings for `fiftyone-app`. [Reference][security-context].\nImage runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload.", + "properties": { + "fsGroup": { + "default": 1000, + "title": "fsGroup", + "type": "integer" + }, + "runAsGroup": { + "default": 1000, + "title": "runAsGroup", + "type": "integer" + }, + "runAsNonRoot": { + "default": true, + "title": "runAsNonRoot", + "type": "boolean" + }, + "runAsUser": { + "default": 1000, + "title": "runAsUser", + "type": "integer" + } + }, "required": [], "title": "podSecurityContext", "type": "object" @@ -2129,7 +2173,29 @@ "type": "object" }, "podSecurityContext": { - "description": "Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context].", + "description": "Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context].\nImage runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload.", + "properties": { + "fsGroup": { + "default": 1000, + "title": "fsGroup", + "type": "integer" + }, + "runAsGroup": { + "default": 1000, + "title": "runAsGroup", + "type": "integer" + }, + "runAsNonRoot": { + "default": true, + "title": "runAsNonRoot", + "type": "boolean" + }, + "runAsUser": { + "default": 1000, + "title": "runAsUser", + "type": "integer" + } + }, "required": [], "title": "podSecurityContext", "type": "object" @@ -2445,7 +2511,29 @@ "type": "object" }, "podSecurityContext": { - "description": "Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context].", + "description": "Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context].\nImage runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload.", + "properties": { + "fsGroup": { + "default": 1000, + "title": "fsGroup", + "type": "integer" + }, + "runAsGroup": { + "default": 1000, + "title": "runAsGroup", + "type": "integer" + }, + "runAsNonRoot": { + "default": true, + "title": "runAsNonRoot", + "type": "boolean" + }, + "runAsUser": { + "default": 1000, + "title": "runAsUser", + "type": "integer" + } + }, "required": [], "title": "podSecurityContext", "type": "object" @@ -3052,7 +3140,29 @@ "type": "object" }, "podSecurityContext": { - "description": "Pod-level security attributes and common container settings for `teams-plugins`. [Reference][security-context].", + "description": "Pod-level security attributes and common container settings for `teams-plugins`. [Reference][security-context].\nImage runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload.", + "properties": { + "fsGroup": { + "default": 1000, + "title": "fsGroup", + "type": "integer" + }, + "runAsGroup": { + "default": 1000, + "title": "runAsGroup", + "type": "integer" + }, + "runAsNonRoot": { + "default": true, + "title": "runAsNonRoot", + "type": "boolean" + }, + "runAsUser": { + "default": 1000, + "title": "runAsUser", + "type": "integer" + } + }, "required": [], "title": "podSecurityContext", "type": "object" @@ -3928,6 +4038,293 @@ "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 injected into the `teams-api`,\n `fiftyone-app`, `teams-plugins`, and delegated-operator workloads,\n and as a native sidecar `initContainer` on delegated-operator Jobs\n - `FIFTYONE_TELEMETRY_REDIS_URL` on every workload that talks to the\n redis backend (api, app, plugins, teams-app, delegated operators)\n\nSetting `enabled: false` skips all telemetry resources and sidecar\ninjection. Note that the FiftyOne UI's log viewer for delegated-\noperator runs depends on the sidecar to capture per-operation logs;\ndisabling telemetry will leave that log viewer empty.", + "properties": { + "enabled": { + "default": true, + "description": "Master switch. When `true` (default), all telemetry resources and\nsidecars are rendered. When `false`, none are — note that the\nFiftyOne UI's delegated-operator log viewer depends on the sidecar\nand will be empty without it.", + "title": "enabled", + "type": "boolean" + }, + "redis": { + "properties": { + "containerSecurityContext": { + "description": "Container-level security attributes for the telemetry Redis.\n`readOnlyRootFilesystem: true` is safe because writes go to the\n`/data` volume. [Reference][container-security-context].", + "properties": { + "allowPrivilegeEscalation": { + "default": false, + "title": "allowPrivilegeEscalation", + "type": "boolean" + }, + "capabilities": { + "properties": { + "drop": { + "items": { + "anyOf": [ + { + "type": "string" + } + ], + "required": [] + }, + "title": "drop", + "type": "array" + } + }, + "required": [], + "title": "capabilities", + "type": "object" + }, + "readOnlyRootFilesystem": { + "default": true, + "title": "readOnlyRootFilesystem", + "type": "boolean" + } + }, + "required": [], + "title": "containerSecurityContext", + "type": "object" + }, + "external": { + "description": "When `external.url` is set, the chart does NOT render the bundled\nRedis `Deployment`/`Service`/`PersistentVolumeClaim`; consumer\nworkloads and telemetry sidecars are wired at the external URL\ninstead. Use this for managed Redis (ElastiCache, MemoryStore, etc.)\nor an existing shared Redis you already operate.", + "properties": { + "url": { + "default": "", + "description": "URL of an external Redis to use instead of the bundled one\n(e.g. `redis://my-redis.example.com:6379`).", + "title": "url", + "type": "string" + } + }, + "required": [], + "title": "external", + "type": "object" + }, + "image": { + "default": "redis:7-alpine", + "description": "Container image for the telemetry Redis Deployment.", + "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" + }, + "persistence": { + "properties": { + "enabled": { + "default": false, + "description": "Controls whether a `PersistentVolumeClaim` is created for the\nbundled telemetry Redis. Defaults to `false` (emptyDir) so the chart\ninstalls cleanly on clusters without a dynamic PV provisioner or a\ndefault `StorageClass`. Telemetry archives flush to MongoDB every\n~10 minutes (`metrics_history`, `service_logs`, completed DO runs),\nso what you lose on a Redis pod restart is only the in-window\ndashboard backscroll (~10 minutes of un-archived data, plus any\nlive op buffers). Other workload crashes do NOT affect Redis state\n— only a Redis pod restart drops the in-memory window.\n\nSet to `true` to provision a PVC and have Redis recover its in-window\ndata across pod restarts (requires a working `StorageClass`, or set\n`existingClaim` below to point at a pre-provisioned PVC).", + "title": "enabled", + "type": "boolean" + }, + "existingClaim": { + "default": "", + "description": "Name of an existing `PersistentVolumeClaim` (in `namespace.name`) to\nbind the telemetry Redis to. When set, the chart will NOT create a\n`PersistentVolumeClaim` and the `size` / `storageClass` fields above\nare ignored — you are responsible for provisioning the claim out of\nband. Use this on clusters without a dynamic PV provisioner, or to\nreuse an existing volume across releases. Has no effect when\n`persistence.enabled` is `false`.", + "title": "existingClaim", + "type": "string" + }, + "size": { + "default": "1Gi", + "description": "Storage size for the telemetry Redis `PersistentVolumeClaim`.", + "title": "size", + "type": "string" + }, + "storageClass": { + "default": "", + "description": "`StorageClass` name for the telemetry Redis `PersistentVolumeClaim`.\nLeave unset to use the cluster's default `StorageClass`.", + "required": [], + "title": "storageClass", + "type": [ + "string", + "null" + ] + } + }, + "required": [], + "title": "persistence", + "type": "object" + }, + "podSecurityContext": { + "description": "Pod-level security attributes for the telemetry Redis. Defaults\nto a non-root UID/GID matching the `redis` user in the upstream\n`redis:7-alpine` image (999). `fsGroup` ensures the mounted `/data`\nvolume is group-writable. [Reference][security-context].", + "properties": { + "fsGroup": { + "default": 999, + "title": "fsGroup", + "type": "integer" + }, + "runAsGroup": { + "default": 999, + "title": "runAsGroup", + "type": "integer" + }, + "runAsNonRoot": { + "default": true, + "title": "runAsNonRoot", + "type": "boolean" + }, + "runAsUser": { + "default": 999, + "title": "runAsUser", + "type": "integer" + } + }, + "required": [], + "title": "podSecurityContext", + "type": "object" + }, + "resources": { + "description": "Resource requests/limits for the telemetry Redis container. [Reference][resources].", + "properties": { + "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" + } + }, + "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 binds the chart's\nmain app service account (used by sidecars on `fiftyone-app`,\n`teams-plugins`, and the delegated-operator workloads). The teams-api\nsidecar uses the teams-api RBAC service account, which already has\n`pods/log` GET via `api-role.yaml` and does not need to be re-bound.", + "items": { + "required": [] + }, + "title": "serviceAccounts", + "type": "array" + }, + "sidecar": { + "properties": { + "image": { + "properties": { + "pullPolicy": { + "default": "Always", + "description": "Instruct when the kubelet should pull (download) the specified image.\nOne of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy].", + "oneOf": [ + { + "enum": [ + "Always", + "IfNotPresent", + "Never" + ], + "required": [] + } + ], + "required": [], + "title": "pullPolicy" + }, + "repository": { + "default": "voxel51/telemetry-sidecar", + "description": "Container image for `telemetry-sidecar`.", + "title": "repository", + "type": "string" + }, + "tag": { + "default": "", + "description": "Image tag for `telemetry-sidecar`. Defaults to `Chart.AppVersion`.", + "title": "tag", + "type": "string" + } + }, + "required": [], + "title": "image", + "type": "object" + }, + "resources": { + "description": "Resource requests/limits for each `telemetry-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 890eb6032..a2c92687e 100644 --- a/helm/fiftyone-teams-app/values.yaml +++ b/helm/fiftyone-teams-app/values.yaml @@ -191,7 +191,12 @@ apiSettings: minAvailable: # maxUnavailable: "" # -- Pod-level security attributes and common container settings for `teams-api`. [Reference][security-context]. - podSecurityContext: {} + # Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. + podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 # -- RBAC roles, bindings, and service accounts which will be used to # submit on-demand delegated operators to the kubernetes API. # If `apiSettings.rbac.create=true`, these will be used by the `teams-api` pods. @@ -458,7 +463,12 @@ appSettings: minAvailable: # maxUnavailable: "" # -- Pod-level security attributes and common container settings for `fiftyone-app`. [Reference][security-context]. - podSecurityContext: {} + # Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. + podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 readiness: # -- Number of times to retry the readiness probe for the `fiftyone-app`. [Reference][probes]. failureThreshold: 5 @@ -788,7 +798,12 @@ delegatedOperatorDeployments: minAvailable: # maxUnavailable: "" # -- Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context]. - podSecurityContext: {} + # Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. + podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 readiness: # -- Number of times to retry the readiness probe for the `teams-do`. [Reference][probes]. failureThreshold: 5 @@ -972,7 +987,12 @@ delegatedOperatorJobTemplates: # -- Annotations for delegated-operator-executor pods. [Reference][annotations]. podAnnotations: {} # -- Pod-level security attributes and common container settings for `delegated-operator-executor`. [Reference][security-context]. - podSecurityContext: {} + # Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. + podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 # Instead of setting default resources, we require the user define them # This enables running on resource constrained environments # (like Minikube). To set resources, uncomment the following @@ -1295,7 +1315,12 @@ pluginsSettings: minAvailable: # maxUnavailable: "" # -- Pod-level security attributes and common container settings for `teams-plugins`. [Reference][security-context]. - podSecurityContext: {} + # Image runs as UID 1000:1000 — declared here so PSA `restricted` and pod-level runAsNonRoot policies accept the workload. + podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 readiness: # -- Number of times to retry the readiness probe for the `teams-plugins`. [Reference][probes]. failureThreshold: 5 @@ -1595,3 +1620,124 @@ 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 injected into the `teams-api`, +# `fiftyone-app`, `teams-plugins`, and delegated-operator workloads, +# and as a native sidecar `initContainer` on delegated-operator Jobs +# - `FIFTYONE_TELEMETRY_REDIS_URL` on every workload that talks to the +# redis backend (api, app, plugins, teams-app, delegated operators) +# +# Setting `enabled: false` skips all telemetry resources and sidecar +# injection. Note that the FiftyOne UI's log viewer for delegated- +# operator runs depends on the sidecar to capture per-operation logs; +# disabling telemetry will leave that log viewer empty. +telemetry: + # -- Master switch. When `true` (default), all telemetry resources and + # sidecars are rendered. When `false`, none are — note that the + # FiftyOne UI's delegated-operator log viewer depends on the sidecar + # and will be empty without it. + enabled: true + redis: + # -- Container image for the telemetry Redis Deployment. + image: redis:7-alpine + persistence: + # -- Controls whether a `PersistentVolumeClaim` is created for the + # bundled telemetry Redis. Defaults to `false` (emptyDir) so the chart + # installs cleanly on clusters without a dynamic PV provisioner or a + # default `StorageClass`. Telemetry archives flush to MongoDB every + # ~10 minutes (`metrics_history`, `service_logs`, completed DO runs), + # so what you lose on a Redis pod restart is only the in-window + # dashboard backscroll (~10 minutes of un-archived data, plus any + # live op buffers). Other workload crashes do NOT affect Redis state + # — only a Redis pod restart drops the in-memory window. + # + # Set to `true` to provision a PVC and have Redis recover its in-window + # data across pod restarts (requires a working `StorageClass`, or set + # `existingClaim` below to point at a pre-provisioned PVC). + enabled: false + # -- Storage size for the telemetry Redis `PersistentVolumeClaim`. + size: 1Gi + # @schema + # type: ["string", "null"] + # @schema + # -- `StorageClass` name for the telemetry Redis `PersistentVolumeClaim`. + # Leave unset to use the cluster's default `StorageClass`. + storageClass: "" + # -- Name of an existing `PersistentVolumeClaim` (in `namespace.name`) to + # bind the telemetry Redis to. When set, the chart will NOT create a + # `PersistentVolumeClaim` and the `size` / `storageClass` fields above + # are ignored — you are responsible for provisioning the claim out of + # band. Use this on clusters without a dynamic PV provisioner, or to + # reuse an existing volume across releases. Has no effect when + # `persistence.enabled` is `false`. + existingClaim: "" + # -- `--maxmemory` flag passed to `redis-server`. + maxmemory: 400mb + # -- `--maxmemory-policy` flag passed to `redis-server`. + maxmemoryPolicy: allkeys-lru + # -- Resource requests/limits for the telemetry Redis container. [Reference][resources]. + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 250m + memory: 512Mi + # -- Pod-level security attributes for the telemetry Redis. Defaults + # to a non-root UID/GID matching the `redis` user in the upstream + # `redis:7-alpine` image (999). `fsGroup` ensures the mounted `/data` + # volume is group-writable. [Reference][security-context]. + podSecurityContext: + runAsNonRoot: true + runAsUser: 999 + runAsGroup: 999 + fsGroup: 999 + # -- Container-level security attributes for the telemetry Redis. + # `readOnlyRootFilesystem: true` is safe because writes go to the + # `/data` volume. [Reference][container-security-context]. + containerSecurityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] + # When `external.url` is set, the chart does NOT render the bundled + # Redis `Deployment`/`Service`/`PersistentVolumeClaim`; consumer + # workloads and telemetry sidecars are wired at the external URL + # instead. Use this for managed Redis (ElastiCache, MemoryStore, etc.) + # or an existing shared Redis you already operate. + external: + # -- URL of an external Redis to use instead of the bundled one + # (e.g. `redis://my-redis.example.com:6379`). + url: "" + sidecar: + image: + # @schema + # oneOf: + # - enum: ["Always", "IfNotPresent", "Never"] + # @schema + # -- Instruct when the kubelet should pull (download) the specified image. + # One of `IfNotPresent`, `Always` or `Never`. [Reference][image-pull-policy]. + pullPolicy: Always + # -- Container image for `telemetry-sidecar`. + repository: voxel51/telemetry-sidecar + # -- Image tag for `telemetry-sidecar`. Defaults to `Chart.AppVersion`. + tag: "" + # -- Resource requests/limits for each `telemetry-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 binds the chart's + # main app service account (used by sidecars on `fiftyone-app`, + # `teams-plugins`, and the delegated-operator workloads). The teams-api + # sidecar uses the teams-api RBAC service account, which already has + # `pods/log` GET via `api-role.yaml` and does not need to be re-bound. + serviceAccounts: [] diff --git a/tests/fixtures/docker/compose.override.mongodb.yaml b/tests/fixtures/docker/compose.override.mongodb.yaml index 406789402..a853438f1 100644 --- a/tests/fixtures/docker/compose.override.mongodb.yaml +++ b/tests/fixtures/docker/compose.override.mongodb.yaml @@ -11,6 +11,10 @@ services: teams-cas: depends_on: [mongodb] image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-teams-cas:${FIFTYONE_TEAMS_CAS_VERSION} + fiftyone-app-telemetry: + image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/telemetry-sidecar:${FIFTYONE_APP_VERSION} + teams-api-telemetry: + image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/telemetry-sidecar:${FIFTYONE_APP_VERSION} mongodb: image: "mongo:8.0.0" restart: always diff --git a/tests/fixtures/docker/compose.override.mongodb_do.yaml b/tests/fixtures/docker/compose.override.mongodb_do.yaml index a9d83928d..76770b55f 100644 --- a/tests/fixtures/docker/compose.override.mongodb_do.yaml +++ b/tests/fixtures/docker/compose.override.mongodb_do.yaml @@ -2,3 +2,5 @@ services: teams-do: depends_on: [mongodb] image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app:${FIFTYONE_APP_VERSION} + teams-do-telemetry: + image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/telemetry-sidecar:${FIFTYONE_APP_VERSION} diff --git a/tests/fixtures/docker/compose.override.mongodb_plugins.yaml b/tests/fixtures/docker/compose.override.mongodb_plugins.yaml index 0e28fbd06..c4318c2f8 100644 --- a/tests/fixtures/docker/compose.override.mongodb_plugins.yaml +++ b/tests/fixtures/docker/compose.override.mongodb_plugins.yaml @@ -2,3 +2,5 @@ services: teams-plugins: depends_on: [mongodb] image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app:${FIFTYONE_APP_VERSION} + teams-plugins-telemetry: + image: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/telemetry-sidecar:${FIFTYONE_APP_VERSION} diff --git a/tests/fixtures/docker/integration_internal_auth.env b/tests/fixtures/docker/integration_internal_auth.env index 48e46857a..9bae4792d 100644 --- a/tests/fixtures/docker/integration_internal_auth.env +++ b/tests/fixtures/docker/integration_internal_auth.env @@ -31,10 +31,10 @@ FIFTYONE_ENV=development CAS_DEBUG="cas:*" # Image tags for existing artifacts -VERSION=v2.19.0rc5 -FIFTYONE_APP_VERSION=v2.19.0rc5 -FIFTYONE_TEAMS_API_VERSION=v2.19.0rc5 -FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.4 -FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.4 +VERSION=v2.19.0rc11 +FIFTYONE_APP_VERSION=v2.19.0rc11 +FIFTYONE_TEAMS_API_VERSION=v2.19.0rc11 +FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.10 +FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.10 FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=1 diff --git a/tests/fixtures/docker/integration_legacy_auth.env b/tests/fixtures/docker/integration_legacy_auth.env index f1728dbd3..a9a9f022f 100644 --- a/tests/fixtures/docker/integration_legacy_auth.env +++ b/tests/fixtures/docker/integration_legacy_auth.env @@ -39,10 +39,10 @@ FIFTYONE_ENV=development CAS_DEBUG="cas:*" # Image tags for existing artifacts -VERSION=v2.19.0rc5 -FIFTYONE_APP_VERSION=v2.19.0rc5 -FIFTYONE_TEAMS_API_VERSION=v2.19.0rc5 -FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.4 -FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.4 +VERSION=v2.19.0rc11 +FIFTYONE_APP_VERSION=v2.19.0rc11 +FIFTYONE_TEAMS_API_VERSION=v2.19.0rc11 +FIFTYONE_TEAMS_APP_VERSION=v2.19.0-rc.10 +FIFTYONE_TEAMS_CAS_VERSION=v2.19.0-rc.10 FIFTYONE_DELEGATED_OPERATOR_WORKER_REPLICAS=1 diff --git a/tests/fixtures/helm/integration_values.yaml b/tests/fixtures/helm/integration_values.yaml index cb75b1268..b25c0fe3b 100644 --- a/tests/fixtures/helm/integration_values.yaml +++ b/tests/fixtures/helm/integration_values.yaml @@ -9,7 +9,7 @@ apiSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-teams-api pullPolicy: IfNotPresent - tag: v2.19.0rc5 + tag: v2.19.0rc11 service: liveness: initialDelaySeconds: 15 @@ -27,7 +27,7 @@ appSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc5 + tag: v2.19.0rc11 # TODO: Test `minikube addons configure registry-creds` or # When using minikube's addon registry-creds, we may also need to create the k8s secret `regcred` # See https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ @@ -42,21 +42,21 @@ casSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-teams-cas pullPolicy: IfNotPresent - tag: v2.19.0-rc.4 + tag: v2.19.0-rc.10 delegatedOperatorDeployments: deployments: teamsDoCpuDefault: image: repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc5 + tag: v2.19.0rc11 delegatedOperatorTemplates: jobs: teamsDoCpuDefaultK8s: image: repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc5 + tag: v2.19.0rc11 ingress: annotations: # For using the nginx-ingress controller with cert-manager self signed certificates @@ -121,7 +121,13 @@ pluginsSettings: # See https://console.cloud.google.com/artifacts/docker/computer-vision-team/us-central1/dev-docker?project=computer-vision-team repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/fiftyone-app pullPolicy: IfNotPresent - tag: v2.19.0rc5 + tag: v2.19.0rc11 +telemetry: + sidecar: + image: + repository: us-central1-docker.pkg.dev/computer-vision-team/dev-docker/telemetry-sidecar + pullPolicy: IfNotPresent + tag: v2.19.0rc11 teamsAppSettings: dnsName: local.fiftyone.ai # env: @@ -138,4 +144,4 @@ teamsAppSettings: # The others are `vW.X.Y.devZ` (note `.devZ` vs `-dev.Z`). # This is a byproduct of `npm` versioning versus Python PEP 440. pullPolicy: IfNotPresent - tag: v2.19.0-rc.4 + tag: v2.19.0-rc.10 diff --git a/tests/unit/compose/docker-compose-internal-auth_test.go b/tests/unit/compose/docker-compose-internal-auth_test.go index f96cccd51..81c20c314 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.19.0", }, + { + "telemetryRedis", + "telemetry-redis", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + "redis:7-alpine", + }, + { + "telemetrySidecarFiftyoneApp", + "fiftyone-app-telemetry", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:v2.19.0", + }, + { + "telemetrySidecarTeamsApi", + "teams-api-telemetry", + []string{internalAuthComposeFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:v2.19.0", + }, + { + "telemetrySidecarTeamsDo", + "teams-do-telemetry", + []string{ + internalAuthComposeFile, + internalAuthComposeDelegatedOperationsFile, + }, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:v2.19.0", + }, + { + "telemetrySidecarTeamsPlugins", + "teams-plugins-telemetry", + []string{internalAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:v2.19.0", + }, } 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 1a48c5c4d..375559b2c 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.19.0", }, + { + "telemetryRedis", + "telemetry-redis", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + "redis:7-alpine", + }, + { + "telemetrySidecarFiftyoneApp", + "fiftyone-app-telemetry", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:v2.19.0", + }, + { + "telemetrySidecarTeamsApi", + "teams-api-telemetry", + []string{legacyAuthComposeFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:v2.19.0", + }, + { + "telemetrySidecarTeamsDo", + "teams-do-telemetry", + []string{ + legacyAuthComposeFile, + legacyAuthComposeDelegatedOperationsFile, + }, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:v2.19.0", + }, + { + "telemetrySidecarTeamsPlugins", + "teams-plugins-telemetry", + []string{legacyAuthComposeDedicatedPluginsFile}, + s.dotEnvFiles, + "voxel51/telemetry-sidecar:v2.19.0", + }, } 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..b4231b993 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 @@ -2395,11 +2400,11 @@ func (s *deploymentApiTemplateTest) TestPodSecurityContext() { "defaultValues", nil, func(podSecurityContext *corev1.PodSecurityContext) { - s.Nil(podSecurityContext.FSGroup, "should be nil") + s.Equal(int64(1000), *podSecurityContext.FSGroup, "fsGroup should be 1000 (image UID)") + s.Equal(int64(1000), *podSecurityContext.RunAsGroup, "runAsGroup should be 1000") + s.True(*podSecurityContext.RunAsNonRoot, "runAsNonRoot should be true") + s.Equal(int64(1000), *podSecurityContext.RunAsUser, "runAsUser should be 1000") s.Nil(podSecurityContext.FSGroupChangePolicy, "should be nil") - s.Nil(podSecurityContext.RunAsGroup, "should be nil") - s.Nil(podSecurityContext.RunAsNonRoot, "should be nil") - s.Nil(podSecurityContext.RunAsUser, "should be nil") s.Nil(podSecurityContext.SeccompProfile, "should be nil") s.Nil(podSecurityContext.SELinuxOptions, "should be nil") s.Nil(podSecurityContext.SupplementalGroups, "should be nil") @@ -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..71e5ad3f3 100644 --- a/tests/unit/helm/api-role_test.go +++ b/tests/unit/helm/api-role_test.go @@ -286,6 +286,28 @@ func (s *apiRoleTemplateTest) TestMetadataLabels() { } func (s *apiRoleTemplateTest) TestRules() { + // The pods/log GET rule must remain present regardless of telemetry + // settings — the api uses it for its own DO log retrieval flows, and + // the telemetry RoleBinding piggybacks on this rule for the api SA + // (see _telemetry.tpl: telemetry.role.subjects). Render both with + // telemetry on and off to lock that in. + expectedRulesJson := `[ + { + "apiGroups": ["batch"], + "resources": ["jobs"], + "verbs": ["create", "get", "list", "watch", "update", "delete"] + }, + { + "apiGroups": [""], + "resources": ["pods"], + "verbs": ["get", "list", "watch", "delete"] + }, + { + "apiGroups": [""], + "resources": ["pods/log"], + "verbs": ["get"] + } + ]` testCases := []struct { name string values map[string]string @@ -295,24 +317,23 @@ func (s *apiRoleTemplateTest) TestRules() { "defaultValues", nil, func(rules []rbacv1.PolicyRule) { - expectedRulesJson := `[ - { - "apiGroups": ["batch"], - "resources": ["jobs"], - "verbs": ["create", "get", "list", "watch", "update", "delete"] - }, - { - "apiGroups": [""], - "resources": ["pods"], - "verbs": ["get", "list", "watch", "delete"] - } - ]` var expectedRules []rbacv1.PolicyRule err := json.Unmarshal([]byte(expectedRulesJson), &expectedRules) s.NoError(err) s.Equal(expectedRules, rules, "Rules should be equal") }, }, + { + "telemetryDisabled", + map[string]string{"telemetry.enabled": "false"}, + func(rules []rbacv1.PolicyRule) { + var expectedRules []rbacv1.PolicyRule + err := json.Unmarshal([]byte(expectedRulesJson), &expectedRules) + s.NoError(err) + s.Equal(expectedRules, rules, + "api-role rules must be unchanged when telemetry is disabled") + }, + }, } for _, testCase := range testCases { diff --git a/tests/unit/helm/app-deployment_test.go b/tests/unit/helm/app-deployment_test.go index bfb1b9298..5637978c2 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 @@ -2091,11 +2091,11 @@ func (s *deploymentAppTemplateTest) TestPodSecurityContext() { "defaultValues", nil, func(podSecurityContext *corev1.PodSecurityContext) { - s.Nil(podSecurityContext.FSGroup, "should be nil") + s.Equal(int64(1000), *podSecurityContext.FSGroup, "fsGroup should be 1000 (image UID)") + s.Equal(int64(1000), *podSecurityContext.RunAsGroup, "runAsGroup should be 1000") + s.True(*podSecurityContext.RunAsNonRoot, "runAsNonRoot should be true") + s.Equal(int64(1000), *podSecurityContext.RunAsUser, "runAsUser should be 1000") s.Nil(podSecurityContext.FSGroupChangePolicy, "should be nil") - s.Nil(podSecurityContext.RunAsGroup, "should be nil") - s.Nil(podSecurityContext.RunAsNonRoot, "should be nil") - s.Nil(podSecurityContext.RunAsUser, "should be nil") s.Nil(podSecurityContext.SeccompProfile, "should be nil") s.Nil(podSecurityContext.SELinuxOptions, "should be nil") s.Nil(podSecurityContext.SupplementalGroups, "should be nil") @@ -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..80b0d34de 100644 --- a/tests/unit/helm/common_test.go +++ b/tests/unit/helm/common_test.go @@ -1,5 +1,5 @@ -//go:build all || helm || unit || unitApiDeployment || unitApiService || unitAppDeployment || unitAppHpa || unitAppService || unitCasDeployment || unitCasService || unitIngress || unitNamespace || unitPluginsDeployment || unitHpaPlugins || unitPluginsService || unitSecrets || unitServiceAccount || unitTeamsAppDeployment || unitTeamsAppHpa || unitTeamsAppService -// +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 || unitTelemetrySidecar +// +build all helm unit unitApiDeployment unitApiService unitAppDeployment unitAppHpa unitAppService unitCasDeployment unitCasService unitIngress unitNamespace unitPluginsDeployment unitHpaPlugins unitPluginsService unitSecrets unitServiceAccount unitTeamsAppDeployment unitTeamsAppHpa unitTeamsAppService unitTelemetryRedis unitTelemetryRoleBinding unitTelemetrySidecar package unit diff --git a/tests/unit/helm/delegated-operator-instance-deployment_test.go b/tests/unit/helm/delegated-operator-instance-deployment_test.go index ba744ea5e..f190c57f2 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, "---") @@ -3652,11 +3652,11 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestPodSecurityContext []func(podSecurityContext *corev1.PodSecurityContext){ func(podSecurityContext *corev1.PodSecurityContext) { - s.Nil(podSecurityContext.FSGroup, "should be nil") + s.Equal(int64(1000), *podSecurityContext.FSGroup, "fsGroup should be 1000 (image UID)") + s.Equal(int64(1000), *podSecurityContext.RunAsGroup, "runAsGroup should be 1000") + s.True(*podSecurityContext.RunAsNonRoot, "runAsNonRoot should be true") + s.Equal(int64(1000), *podSecurityContext.RunAsUser, "runAsUser should be 1000") s.Nil(podSecurityContext.FSGroupChangePolicy, "should be nil") - s.Nil(podSecurityContext.RunAsGroup, "should be nil") - s.Nil(podSecurityContext.RunAsNonRoot, "should be nil") - s.Nil(podSecurityContext.RunAsUser, "should be nil") s.Nil(podSecurityContext.SeccompProfile, "should be nil") s.Nil(podSecurityContext.SELinuxOptions, "should be nil") s.Nil(podSecurityContext.SupplementalGroups, "should be nil") @@ -3672,11 +3672,11 @@ func (s *deploymentDelegatedOperatorInstanceTemplateTest) TestPodSecurityContext }, []func(podSecurityContext *corev1.PodSecurityContext){ func(podSecurityContext *corev1.PodSecurityContext) { - s.Nil(podSecurityContext.FSGroup, "should be nil") + s.Equal(int64(1000), *podSecurityContext.FSGroup, "fsGroup should be 1000 (image UID)") + s.Equal(int64(1000), *podSecurityContext.RunAsGroup, "runAsGroup should be 1000") + s.True(*podSecurityContext.RunAsNonRoot, "runAsNonRoot should be true") + s.Equal(int64(1000), *podSecurityContext.RunAsUser, "runAsUser should be 1000") s.Nil(podSecurityContext.FSGroupChangePolicy, "should be nil") - s.Nil(podSecurityContext.RunAsGroup, "should be nil") - s.Nil(podSecurityContext.RunAsNonRoot, "should be nil") - s.Nil(podSecurityContext.RunAsUser, "should be nil") s.Nil(podSecurityContext.SeccompProfile, "should be nil") s.Nil(podSecurityContext.SELinuxOptions, "should be nil") s.Nil(podSecurityContext.SupplementalGroups, "should be nil") @@ -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..0c28841f5 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) diff --git a/tests/unit/helm/plugins-deployment_test.go b/tests/unit/helm/plugins-deployment_test.go index 72ba89406..f0009eebb 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 @@ -2522,11 +2522,11 @@ func (s *deploymentPluginsTemplateTest) TestPodSecurityContext() { "pluginsSettings.enabled": "true", }, func(podSecurityContext *corev1.PodSecurityContext) { - s.Nil(podSecurityContext.FSGroup, "should be nil") + s.Equal(int64(1000), *podSecurityContext.FSGroup, "fsGroup should be 1000 (image UID)") + s.Equal(int64(1000), *podSecurityContext.RunAsGroup, "runAsGroup should be 1000") + s.True(*podSecurityContext.RunAsNonRoot, "runAsNonRoot should be true") + s.Equal(int64(1000), *podSecurityContext.RunAsUser, "runAsUser should be 1000") s.Nil(podSecurityContext.FSGroupChangePolicy, "should be nil") - s.Nil(podSecurityContext.RunAsGroup, "should be nil") - s.Nil(podSecurityContext.RunAsNonRoot, "should be nil") - s.Nil(podSecurityContext.RunAsUser, "should be nil") s.Nil(podSecurityContext.SeccompProfile, "should be nil") s.Nil(podSecurityContext.SELinuxOptions, "should be nil") s.Nil(podSecurityContext.SupplementalGroups, "should be nil") @@ -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..6d95d94e1 --- /dev/null +++ b/tests/unit/helm/telemetry-redis_test.go @@ -0,0 +1,423 @@ +//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.NotContains(output, "kind: PersistentVolumeClaim", + "Redis PVC should NOT be rendered by default — persistence.enabled defaults to false so the chart installs cleanly on clusters without a default StorageClass") +} + +func (s *telemetryRedisTemplateTest) TestExplicitlyDisabled() { + 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") +} + +// TestExternalUrlSkipsBundled ensures that setting telemetry.redis.external.url +// causes the bundled Redis Deployment/Service/PVC to NOT be rendered. The +// chart should leave Redis provisioning to the operator in this case. +func (s *telemetryRedisTemplateTest) TestExternalUrlSkipsBundled() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.external.url": "redis://my-managed-redis:6379", + }} + + _, err := helm.RenderTemplateE(s.T(), options, s.chartPath, s.releaseName, s.templates) + s.ErrorContains(err, "could not find template templates/telemetry-redis.yaml in chart") +} + +// TestExternalUrlWiresApiDeployment ensures that setting external.url causes +// FIFTYONE_TELEMETRY_REDIS_URL on the api deployment to point at the external +// URL rather than the in-cluster Service. +func (s *telemetryRedisTemplateTest) TestExternalUrlWiresApiDeployment() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.external.url": "redis://my-managed-redis:6379", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, + []string{"templates/api-deployment.yaml"}) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(s.T(), output, &deployment) + s.Require().Len(deployment.Spec.Template.Spec.Containers, 2, + "Expected api + telemetry-sidecar containers") + + for _, container := range deployment.Spec.Template.Spec.Containers { + var found *corev1.EnvVar + for i, ev := range container.Env { + if ev.Name == "FIFTYONE_TELEMETRY_REDIS_URL" { + found = &container.Env[i] + break + } + } + s.Require().NotNil(found, + "FIFTYONE_TELEMETRY_REDIS_URL should be set on %s container", container.Name) + s.Equal("redis://my-managed-redis:6379", found.Value, + "FIFTYONE_TELEMETRY_REDIS_URL on %s should point at the external URL", + container.Name) + } +} + +// TestBundledUrlWiresApiDeployment ensures the default in-cluster URL is +// release-scoped on the api deployment's containers. +func (s *telemetryRedisTemplateTest) TestBundledUrlWiresApiDeployment() { + options := &helm.Options{SetValues: nil} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, + []string{"templates/api-deployment.yaml"}) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(s.T(), output, &deployment) + + expectedURL := fmt.Sprintf("redis://%s-telemetry-redis.%s.svc.cluster.local:6379", + s.releaseName, "fiftyone-teams") + for _, container := range deployment.Spec.Template.Spec.Containers { + for _, ev := range container.Env { + if ev.Name == "FIFTYONE_TELEMETRY_REDIS_URL" { + s.Equal(expectedURL, ev.Value, + "FIFTYONE_TELEMETRY_REDIS_URL on %s should be release-scoped in-cluster URL", + container.Name) + } + } + } +} + +// TestExternalUrlWiresDelegatedOperatorDeployment regression-tests that the +// delegated-operator deployment's workload-container env honors external.url. +// Earlier the DO env-vars helper built the URL inline via `printf "redis://..."` +// rather than going through the `telemetry.redis.url` helper, so it always +// pointed at the in-cluster Service even when external.url was set. +func (s *telemetryRedisTemplateTest) TestExternalUrlWiresDelegatedOperatorDeployment() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.external.url": "redis://my-managed-redis:6379", + "delegatedOperatorDeployments.deployments.teamsDoCpuDefault.enabled": "true", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, + []string{"templates/delegated-operator-instance-deployment.yaml"}) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(s.T(), output, &deployment) + + // Both the workload container and the auto-injected sidecar should see + // the external URL. + s.Require().GreaterOrEqual(len(deployment.Spec.Template.Spec.Containers), 1, + "Expected at least one container in DO deployment") + for _, container := range deployment.Spec.Template.Spec.Containers { + var found *corev1.EnvVar + for i, ev := range container.Env { + if ev.Name == "FIFTYONE_TELEMETRY_REDIS_URL" { + found = &container.Env[i] + break + } + } + s.Require().NotNil(found, + "FIFTYONE_TELEMETRY_REDIS_URL should be set on %s container", container.Name) + s.Equal("redis://my-managed-redis:6379", found.Value, + "FIFTYONE_TELEMETRY_REDIS_URL on %s should point at the external URL", + container.Name) + } +} + +// TestExternalUrlWiresDelegatedOperatorJobConfigMap regression-tests that the +// DO Job template (rendered into the do-templates ConfigMap) honors +// external.url. Same root cause as the DO deployment bug above — the +// templates env-vars helper built the URL inline rather than going through +// `telemetry.redis.url`. +func (s *telemetryRedisTemplateTest) TestExternalUrlWiresDelegatedOperatorJobConfigMap() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.external.url": "redis://my-managed-redis:6379", + "delegatedOperatorJobTemplates.jobs.test-job.enabled": "true", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, + []string{"templates/delegated-operator-job-configmap.yaml"}) + + // The ConfigMap embeds the Job spec as a multi-line YAML string in + // .data.. The render output is plain text, so we just need to + // ensure no occurrence of the in-cluster URL leaks through. + inClusterURL := fmt.Sprintf("redis://%s-telemetry-redis.%s.svc.cluster.local:6379", + s.releaseName, "fiftyone-teams") + s.NotContains(output, inClusterURL, + "DO Job ConfigMap must not embed the in-cluster Redis URL when external.url is set") + s.Contains(output, "redis://my-managed-redis:6379", + "DO Job ConfigMap should embed the external Redis URL") +} + +// extractDeployment finds the Deployment document in the multi-doc render output. +func (s *telemetryRedisTemplateTest) extractDeployment(output string) appsv1.Deployment { + for _, doc := range splitYAMLDocs(output) { + 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() { + // persistence.enabled defaults to false; re-enable to exercise the PVC-rendering path. + options := &helm.Options{SetValues: map[string]string{ + "telemetry.enabled": "true", + "telemetry.redis.persistence.enabled": "true", + "telemetry.redis.persistence.size": "5Gi", + "telemetry.redis.persistence.storageClass": "gp3", + }} + + 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()) + s.Require().NotNil(pvc.Spec.StorageClassName, "StorageClassName should be set") + s.Equal("gp3", *pvc.Spec.StorageClassName) + return + } + s.Fail("PVC document not found in rendered output") +} + +// TestPersistenceDisabledSkipsPVCAndUsesEmptyDir ensures that with +// persistence.enabled=false, the PVC is not rendered and the Deployment +// switches the redis-data volume to emptyDir. +func (s *telemetryRedisTemplateTest) TestPersistenceDisabledSkipsPVCAndUsesEmptyDir() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.persistence.enabled": "false", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + + s.NotContains(output, "kind: PersistentVolumeClaim", + "PVC should not render when persistence.enabled=false") + + deployment := s.extractDeployment(output) + volumes := deployment.Spec.Template.Spec.Volumes + s.Require().Len(volumes, 1, "Deployment should have exactly one volume") + s.Equal("redis-data", volumes[0].Name) + s.NotNil(volumes[0].EmptyDir, + "redis-data volume should be emptyDir when persistence is disabled") + s.Nil(volumes[0].PersistentVolumeClaim, + "redis-data volume should NOT reference a PVC when persistence is disabled") +} + +// TestExistingClaimSkipsPVCAndMountsNamedClaim ensures that +// persistence.existingClaim points the Deployment at the named PVC and +// suppresses chart-managed PVC creation — the path for clusters without +// a dynamic PV provisioner where the operator pre-creates the claim. +func (s *telemetryRedisTemplateTest) TestExistingClaimSkipsPVCAndMountsNamedClaim() { + // persistence.enabled defaults to false; re-enable to exercise the existingClaim mount path. + options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.persistence.enabled": "true", + "telemetry.redis.persistence.existingClaim": "my-prebuilt-redis-pvc", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + + s.NotContains(output, "kind: PersistentVolumeClaim", + "PVC should not render when persistence.existingClaim is set") + + deployment := s.extractDeployment(output) + volumes := deployment.Spec.Template.Spec.Volumes + s.Require().Len(volumes, 1, "Deployment should have exactly one volume") + s.Equal("redis-data", volumes[0].Name) + s.Require().NotNil(volumes[0].PersistentVolumeClaim, + "redis-data volume should reference a PVC when existingClaim is set") + s.Equal("my-prebuilt-redis-pvc", volumes[0].PersistentVolumeClaim.ClaimName, + "claimName should match the user-supplied existingClaim, not the chart-generated name") + s.Nil(volumes[0].EmptyDir, + "redis-data volume should NOT be emptyDir when existingClaim is set") +} + +// TestExistingClaimIgnoresStorageClassAndSize ensures that size/storageClass +// have no effect once the user has taken over claim provisioning via +// existingClaim — the chart has no PVC to apply them to. +func (s *telemetryRedisTemplateTest) TestExistingClaimIgnoresStorageClassAndSize() { + // persistence.enabled defaults to false; re-enable so the test exercises the existingClaim path with size/storageClass set. + options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.persistence.enabled": "true", + "telemetry.redis.persistence.existingClaim": "my-prebuilt-redis-pvc", + "telemetry.redis.persistence.size": "100Gi", + "telemetry.redis.persistence.storageClass": "gp3", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + + s.NotContains(output, "kind: PersistentVolumeClaim", + "PVC should not render when existingClaim is set, even with size/storageClass") + s.NotContains(output, "100Gi", + "size should not leak into the rendered output when existingClaim is set") + s.NotContains(output, "storageClassName", + "storageClassName should not appear in any rendered object when existingClaim is set") +} + +// TestPersistenceDisabledBeatsExistingClaim ensures persistence.enabled=false +// remains the kill-switch — even with existingClaim set, the user opting out +// of persistence should yield an emptyDir volume rather than a stale claim +// mount. +func (s *telemetryRedisTemplateTest) TestPersistenceDisabledBeatsExistingClaim() { + options := &helm.Options{SetValues: map[string]string{ + "telemetry.redis.persistence.enabled": "false", + "telemetry.redis.persistence.existingClaim": "my-prebuilt-redis-pvc", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + + s.NotContains(output, "kind: PersistentVolumeClaim", + "PVC should not render when persistence.enabled=false") + + deployment := s.extractDeployment(output) + volumes := deployment.Spec.Template.Spec.Volumes + s.Require().Len(volumes, 1) + s.NotNil(volumes[0].EmptyDir, + "persistence.enabled=false should take precedence and yield an emptyDir volume") + s.Nil(volumes[0].PersistentVolumeClaim, + "redis-data volume should NOT reference the existingClaim when persistence is disabled") +} diff --git a/tests/unit/helm/telemetry-rolebinding_test.go b/tests/unit/helm/telemetry-rolebinding_test.go new file mode 100644 index 000000000..30db01ee2 --- /dev/null +++ b/tests/unit/helm/telemetry-rolebinding_test.go @@ -0,0 +1,185 @@ +//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 — the SA used by sidecars on + // fiftyone-app, teams-plugins, and delegated-operator workloads. The + // teams-api SA is intentionally NOT bound here; api-role.yaml already + // grants it pods/log GET. + options := &helm.Options{SetValues: map[string]string{ + "telemetry.enabled": "true", + }} + + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, s.templates) + rb, ok := s.extractRoleBinding(output) + s.Require().True(ok, "RoleBinding document not found in rendered output") + + s.Require().Len(rb.Subjects, 1, "Default RoleBinding should bind only to the main app SA") + s.Equal("ServiceAccount", rb.Subjects[0].Kind) + s.Equal("fiftyone-teams", rb.Subjects[0].Namespace) + // Main app SA: chart default serviceAccount.name = "fiftyone-teams" + s.Equal("fiftyone-teams", rb.Subjects[0].Name) +} + +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/telemetry-sidecar_test.go b/tests/unit/helm/telemetry-sidecar_test.go new file mode 100644 index 000000000..61ae13263 --- /dev/null +++ b/tests/unit/helm/telemetry-sidecar_test.go @@ -0,0 +1,261 @@ +//go:build kubeall || helm || unit || unitTelemetrySidecar +// +build kubeall helm unit unitTelemetrySidecar + +package unit + +import ( + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" +) + +type telemetrySidecarTemplateTest struct { + suite.Suite + chartPath string + releaseName string + namespace string +} + +func TestTelemetrySidecarTemplate(t *testing.T) { + t.Parallel() + + helmChartPath, err := filepath.Abs(chartPath) + require.NoError(t, err) + + suite.Run(t, &telemetrySidecarTemplateTest{ + Suite: suite.Suite{}, + chartPath: helmChartPath, + releaseName: "fiftyone-test", + namespace: "fiftyone-" + strings.ToLower(random.UniqueId()), + }) +} + +// findSidecar locates the telemetry-sidecar container in a slice of containers. +func findSidecar(containers []corev1.Container) *corev1.Container { + for i, c := range containers { + if c.Name == "telemetry-sidecar" { + return &containers[i] + } + } + return nil +} + +// TestShareProcessNamespaceEnabledByDefault asserts that the api, app, +// plugins, and delegated-operator deployments all opt into PID-namespace +// sharing when telemetry is enabled (the default). The sidecar relies on +// /proc//fd/1 access in the target container's PID namespace, so +// dropping this would silently break log capture. +func (s *telemetrySidecarTemplateTest) TestShareProcessNamespaceEnabledByDefault() { + cases := []struct { + template string + values map[string]string + }{ + {"templates/api-deployment.yaml", nil}, + {"templates/app-deployment.yaml", nil}, + { + "templates/plugins-deployment.yaml", + map[string]string{"pluginsSettings.enabled": "true"}, + }, + { + "templates/delegated-operator-instance-deployment.yaml", + map[string]string{ + "delegatedOperatorDeployments.deployments.teamsDoCpuDefault.enabled": "true", + }, + }, + } + + for _, tc := range cases { + tc := tc + s.Run(tc.template, func() { + options := &helm.Options{SetValues: tc.values} + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, + []string{tc.template}) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(s.T(), output, &deployment) + + s.Require().NotNil(deployment.Spec.Template.Spec.ShareProcessNamespace, + "shareProcessNamespace should be set on %s", tc.template) + s.True(*deployment.Spec.Template.Spec.ShareProcessNamespace, + "shareProcessNamespace should be true on %s", tc.template) + }) + } +} + +// TestShareProcessNamespaceDisabledWithTelemetryOff asserts that +// shareProcessNamespace is NOT set on workloads when telemetry is +// disabled — otherwise we'd be punching an unnecessary hole in pod +// isolation for users who opt out of telemetry. +func (s *telemetrySidecarTemplateTest) TestShareProcessNamespaceDisabledWithTelemetryOff() { + cases := []struct { + template string + values map[string]string + }{ + {"templates/api-deployment.yaml", nil}, + {"templates/app-deployment.yaml", nil}, + { + "templates/plugins-deployment.yaml", + map[string]string{"pluginsSettings.enabled": "true"}, + }, + { + "templates/delegated-operator-instance-deployment.yaml", + map[string]string{ + "delegatedOperatorDeployments.deployments.teamsDoCpuDefault.enabled": "true", + }, + }, + } + + for _, tc := range cases { + tc := tc + s.Run(tc.template, func() { + options := &helm.Options{SetValues: disableTelemetry(tc.values)} + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, + []string{tc.template}) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(s.T(), output, &deployment) + + if deployment.Spec.Template.Spec.ShareProcessNamespace != nil { + s.False(*deployment.Spec.Template.Spec.ShareProcessNamespace, + "shareProcessNamespace should not be true on %s when telemetry is disabled", + tc.template) + } + }) + } +} + +// TestSidecarSecurityContext asserts the sidecar's securityContext follows +// the paired workload's runAsUser, drops all default caps, and only adds +// SYS_PTRACE on executor (DO) sidecars where py-spy crash-stack archives are +// load-bearing. Service-mode sidecars (api/app/plugins) get no elevated caps. +func (s *telemetrySidecarTemplateTest) TestSidecarSecurityContext() { + cases := []struct { + template string + values map[string]string + executor bool + expectRunAsUid int64 + }{ + {"templates/api-deployment.yaml", nil, false, 1000}, + {"templates/app-deployment.yaml", nil, false, 1000}, + { + "templates/plugins-deployment.yaml", + map[string]string{"pluginsSettings.enabled": "true"}, + false, + 1000, + }, + { + "templates/delegated-operator-instance-deployment.yaml", + map[string]string{ + "delegatedOperatorDeployments.deployments.teamsDoCpuDefault.enabled": "true", + }, + true, + 1000, + }, + } + + for _, tc := range cases { + tc := tc + s.Run(tc.template, func() { + options := &helm.Options{SetValues: tc.values} + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, + []string{tc.template}) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(s.T(), output, &deployment) + + sidecar := findSidecar(deployment.Spec.Template.Spec.Containers) + s.Require().NotNil(sidecar, "telemetry-sidecar container should be injected into %s", tc.template) + + sc := sidecar.SecurityContext + s.Require().NotNil(sc, "telemetry-sidecar should have a securityContext") + + s.Require().NotNil(sc.RunAsUser, "RunAsUser should be set on sidecar") + s.EqualValues(tc.expectRunAsUid, *sc.RunAsUser, "sidecar UID should match paired workload") + + s.Require().NotNil(sc.AllowPrivilegeEscalation, "allowPrivilegeEscalation should be set") + s.False(*sc.AllowPrivilegeEscalation, "sidecar must not allow privilege escalation") + + s.Require().NotNil(sc.Capabilities, "Capabilities should be set on sidecar") + s.Contains(sc.Capabilities.Drop, corev1.Capability("ALL"), + "sidecar must drop all default capabilities") + + var hasPtrace bool + for _, capability := range sc.Capabilities.Add { + if capability == "SYS_PTRACE" { + hasPtrace = true + break + } + } + if tc.executor { + s.True(hasPtrace, "executor sidecar must add SYS_PTRACE for py-spy crash archives") + } else { + s.False(hasPtrace, "service-mode sidecar must not add SYS_PTRACE") + } + }) + } +} + +// TestSidecarMatchesWorkloadUid asserts the sidecar's runAsUser follows +// the paired workload's podSecurityContext.runAsUser override, so same-UID +// /proc reads work without root or SYS_PTRACE. +func (s *telemetrySidecarTemplateTest) TestSidecarMatchesWorkloadUid() { + const overrideUid = "1500" + + cases := []struct { + template string + values map[string]string + }{ + { + "templates/api-deployment.yaml", + map[string]string{"apiSettings.podSecurityContext.runAsUser": overrideUid}, + }, + { + "templates/app-deployment.yaml", + map[string]string{"appSettings.podSecurityContext.runAsUser": overrideUid}, + }, + { + "templates/plugins-deployment.yaml", + map[string]string{ + "pluginsSettings.enabled": "true", + "pluginsSettings.podSecurityContext.runAsUser": overrideUid, + }, + }, + { + "templates/delegated-operator-instance-deployment.yaml", + map[string]string{ + "delegatedOperatorDeployments.deployments.teamsDoCpuDefault.enabled": "true", + "delegatedOperatorDeployments.template.podSecurityContext.runAsUser": overrideUid, + }, + }, + } + + for _, tc := range cases { + tc := tc + s.Run(tc.template, func() { + options := &helm.Options{SetValues: tc.values} + output := helm.RenderTemplate(s.T(), options, s.chartPath, s.releaseName, + []string{tc.template}) + + var deployment appsv1.Deployment + helm.UnmarshalK8SYaml(s.T(), output, &deployment) + + sidecar := findSidecar(deployment.Spec.Template.Spec.Containers) + s.Require().NotNil(sidecar, "telemetry-sidecar should be injected into %s", tc.template) + + sc := sidecar.SecurityContext + s.Require().NotNil(sc.RunAsUser, "RunAsUser should follow workload override") + s.EqualValues(1500, *sc.RunAsUser, "sidecar UID should match workload override") + + s.Require().NotNil(sc.RunAsNonRoot, "RunAsNonRoot should be set") + s.True(*sc.RunAsNonRoot, "non-zero UID implies runAsNonRoot: true") + }) + } +} diff --git a/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-cpu-default-override-template-values.yaml b/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-cpu-default-override-template-values.yaml index b5f52a368..c64fababb 100644 --- a/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-cpu-default-override-template-values.yaml +++ b/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-cpu-default-override-template-values.yaml @@ -33,6 +33,9 @@ spec: restartPolicy: Never serviceAccountName: fiftyone-teams securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true runAsUser: 1000 containers: - name: executor diff --git a/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-cpu-default.yaml b/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-cpu-default.yaml index 260f66378..5f0afd093 100644 --- a/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-cpu-default.yaml +++ b/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-cpu-default.yaml @@ -23,6 +23,11 @@ spec: spec: restartPolicy: Never serviceAccountName: fiftyone-teams + securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true + runAsUser: 1000 containers: - name: executor image: "voxel51/fiftyone-teams-cv-full:v{{CHART_VERSION}}" diff --git a/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-override-example-cascading-template.yaml b/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-override-example-cascading-template.yaml index 3af3c3d21..e1354b1cd 100644 --- a/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-override-example-cascading-template.yaml +++ b/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-override-example-cascading-template.yaml @@ -33,6 +33,9 @@ spec: restartPolicy: Never serviceAccountName: fiftyone-teams securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true runAsUser: 1000 containers: - name: executor diff --git a/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-override-example-template.yaml b/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-override-example-template.yaml index 5dd8e0d4e..0f5fcca41 100644 --- a/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-override-example-template.yaml +++ b/tests/unit/helm/test_data/delegated-operator-job-configmap_test/expected-override-example-template.yaml @@ -33,6 +33,9 @@ spec: restartPolicy: Never serviceAccountName: fiftyone-teams securityContext: + fsGroup: 1000 + runAsGroup: 1000 + runAsNonRoot: true runAsUser: 3000 containers: - name: executor 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/bump-fixtures-helm.sh b/utils/bump-fixtures-helm.sh index 9e0eed242..90db16d30 100755 --- a/utils/bump-fixtures-helm.sh +++ b/utils/bump-fixtures-helm.sh @@ -139,6 +139,10 @@ if yq -e ".delegatedOperatorTemplates.jobs" "${file}" >/dev/null; then yq "$yq_flags" ".delegatedOperatorTemplates.jobs.*.image.tag = \"${FIFTYONE_APP_VERSION}\"" "${file}" fi +if yq -e ".telemetry.sidecar.image" "${file}" >/dev/null; then + yq "$yq_flags" ".telemetry.sidecar.image.tag = \"${FIFTYONE_APP_VERSION}\"" "${file}" +fi + # Remove temporary file if dry-run if [[ $DRY_RUN == "true" ]]; then cat "${file}" diff --git a/utils/validate-docker-pulls.sh b/utils/validate-docker-pulls.sh index 2af6b79c2..73da62b3c 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" @@ -23,6 +26,7 @@ EXPECTED_IMAGES=( "voxel51/fiftyone-teams-app" "voxel51/fiftyone-teams-cas" "voxel51/fiftyone-teams-cv-full" + "voxel51/telemetry-sidecar" ) . "$(dirname "$0")/common.sh" @@ -101,7 +105,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. redis:7-alpine); + # 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 +130,14 @@ helm_images=$( read -r -a helm_images_array <<<"${helm_images}" +known_images_for_template_check=("${expected_images_with_tag[@]}") + 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