This repo contains a Kubernetes Operator based on the kopf and kubernetes Python packages that is used by the Informatics Matters Squonk2 Data Manager API to create interactive data visualisation instances for the Data Manager service.
It follows the same pattern as the Squonk2 Jupyter operator: the Data Manager creates a custom resource and this operator responds by creating a Kubernetes Deployment, Service and Ingress that run the squonk2-viz-app container image.
The Data Manager supplies only the image tag (via
spec.imDataManager.imageTag); the operator combines it with the image
repository it is configured with: -
ghcr.io/informaticsmatters/squonk2-viz-app(seeoperator/handlers.py, overridable with theSVO_IMAGEenvironment variable)
So, given an imageTag of 0.1.4, the operator runs
ghcr.io/informaticsmatters/squonk2-viz-app:0.1.4. The imageTag is
required — if it is missing the operator treats the resource as an
unrecoverable error and does not retry.
This operator sits between two related repositories: -
- Upstream — squonk2-viz-app: the container image
(
ghcr.io/informaticsmatters/squonk2-viz-app) that the operator runs for eachDataVisualisationinstance. Changes to the app's runtime contract (its port, requiredDM_*environment variables and Project volume) drive the behaviour of this operator. - Downstream — squonk2-data-manager-viz-operator-ansible: the Ansible
role/playbook that deploys this operator (and its CRD, RBAC and
SVO_configuration) into a Kubernetes cluster. It consumes the operator image published from this repository.
It also follows the same pattern as the sibling squonk2-data-manager-jupyter-operator — consult that repo when a behaviour here is unclear.
The operator watches for the following Custom Resource: -
- Group:
squonk.it - Version:
v1 - Kind:
DataVisualisation(pluraldatavisualisations)
Data-Manager-provided material is namespaced under the imDataManager property
of the resource spec. The only required property is imageTag (the
container image tag, e.g. 0.1.4). The remaining properties are optional, with
operator defaults, and include serviceAccountName, resources,
securityContext (runAsUser, runAsGroup), project (claimName, id),
ingressClass, ingressDomain, ingressTlsSecret, ingressProxyBodySize,
imagePullSecrets (a list of Secret names) and labels (a list of
key=value strings).
Issue #1 stated that no environment variables are needed for the Pod. In practice the
squonk2-viz-appimage exits at start-up unlessDM_PROJECT_DIRis set, and reads its data from a mounted Project volume. The operator therefore mounts the Project PVC and injects theDM_*variables described below; without them the Pod would crash-loop.
For each instance the operator: -
- Mounts the Data Manager Project PVC (
project.claimName, sub-pathproject.id) at/project. - Injects the environment variables
DM_PROJECT_DIR(/project),DM_PROJECT_ID,DM_INSTANCE_IDandDM_INSTANCE_OWNER.
The viz-app's Express server listens on container port 5170, which is exposed by the Service and routed to by the path-based Ingress.
Following the Jupyter operator's JO_ convention, operator-controlling
variables are prefixed SVO_ (Squonk2 Viz Operator): -
| Variable | Default | Purpose |
|---|---|---|
INGRESS_DOMAIN |
(required) | Default ingress host for instances |
INGRESS_TLS_SECRET |
(unset) | Default TLS secret; if unset, cert-manager is used |
INGRESS_CERT_ISSUER |
(unset) | cert-manager cluster issuer (when no TLS secret) |
SVO_IMAGE |
ghcr.io/informaticsmatters/squonk2-viz-app |
Image repository (the tag comes from imageTag) |
SVO_INGRESS_CLASS |
nginx |
Default ingress class for instances |
SVO_IMAGE_PULL_SECRET |
(unset) | Name of a dockerconfigjson Secret for the (private) image registry |
SVO_POD_NODE_SELECTOR_KEY |
informaticsmatters.com/purpose-application |
Pod node-selector key |
SVO_POD_NODE_SELECTOR_VALUE |
yes |
Pod node-selector value |
SVO_APPLY_POD_PRIORITY_CLASS |
(unset) | Any value applies a Pod priority class |
SVO_DEFAULT_POD_PRIORITY_CLASS |
im-application-low |
Priority class to apply |
The default image (ghcr.io/informaticsmatters/squonk2-viz-app) lives in a
private registry, so the instance Pod needs an image pull secret to
pull it. The operator references such a secret by name and adds it to the
Deployment's Pod spec as imagePullSecrets; it never holds or creates registry
credentials itself.
- Set
SVO_IMAGE_PULL_SECRETto the Secret name for all instances, and/or override per-instance viaspec.imDataManager.imagePullSecrets(a list of names). A per-instance value takes precedence over the operator default. If neither is set, Pods are created withoutimagePullSecrets(fine for a public image). - The named Secret (type
kubernetes.io/dockerconfigjson) must already exist in each Data Manager namespace where instances are launched — it is provisioned out-of-band, e.g.: -
kubectl create secret docker-registry ghcr-pull-secret \
--docker-server=ghcr.io \
--docker-username=<github-user> \
--docker-password=<PAT-with-read:packages> \
-n <dm-namespace>
The project uses: -
- pre-commit to enforce linting of files prior to committing them to the upstream repository
- Commitizen to enforce a Conventional Commit commit message format
- Black as a code formatter
You MUST comply with these choices in order to contribute to the project.
To get started, set up your local clone: -
pip install -r build-requirements.txt
pre-commit install -t commit-msg -t pre-commit
Now the project's rules will run on every commit, and you can check the current health of your clone with: -
pre-commit run --all-files
The operator logic has unit tests (see tests/). Install the operator's
runtime requirements and run them with pytest: -
python -m venv venv
source venv/bin/activate
pip install -r operator/requirements.txt -r build-requirements.txt
pytest
Pre-requisites: -
- Docker Compose (v2)
The operator container, residing in the operator directory, is automatically
built and pushed using GitHub Actions. You can build and push the image
yourself using docker-compose. The following will build an operator image with
a specific tag: -
export IMAGE_TAG=35.0.0-alpha.1
docker compose build
docker compose push
The image tag's major version must match the major version of the
kubernetesPyPI package the operator is built against (currently35, for Kubernetes 1.35).
Releases are cut from the latest main as a Git tag, which triggers the
build-tag workflow to build and push the image. The repository ships a
Claude Code release skill (.claude/skills/release/) that automates this:
it refuses to release when CI has failed, enforces that the release major
matches the pinned kubernetes package, and supports semver pre-releases —
alpha, beta and rc (each numbered from 1, e.g. 35.0.0-alpha.1, and
marked as a GitHub Pre-release) — as well as full releases.
The pure numbering logic lives in .claude/skills/release/next_release.py and
is unit tested (tests/test_release.py); it can also be run directly: -
python .claude/skills/release/next_release.py next \
--channel alpha --requirements operator/requirements.txt
In order to expose the CRD as an Application in the Data Manager API service you will need to a) annotate the CRD and b) provide a Role and RoleBinding.
For the CRD to be recognised by the Data Manager API it will need a number
of annotations in its metadata -> annotations block: -
data-manager.informaticsmatters.com/applicationset to'yes'data-manager.informaticsmatters.com/application-namespacesset to a colon-separated list of namespaces the Application is to be used in, e.g.'data-manager-api:data-manager-api-staging'data-manager.informaticsmatters.com/application-url-locationset toviz.url— the operator writes the instance URL to the custom resource'sstatus.viz.url.
So that Pod instances can be recognised by the Data Manager API the application's Pod must contain the label: -
data-manager.informaticsmatters.com/instance
with a value matching the name given to the operator by the Data Manager.
The Data Manager passes this in the imDataManager.labels list; the operator
copies all such labels onto the Pod template.
The Custom Resource must expose properties that allow a custom SecurityContext to be applied, otherwise the application instance will not be able to access the Data Manager Project files: -
spec.imDataManager.securityContext.runAsUserspec.imDataManager.securityContext.runAsGroup
The container runs without privileges, as the user/group assigned by the Data
Manager API, with fsGroup 100 so the Project files are accessible.
To place Data-Manager Project files the CRD must expose: -
spec.imDataManager.project.claimNamespec.imDataManager.project.id
These provide the Project PVC and sub-path mounted at /project.