Thanks for your interest in synthetics-operator. This file covers the practical bits — how to build, test, and ship changes. For what the project is and why, read the top-level README.md; for the developer-experience backlog, read DX_AUDIT.md.
Requirements:
- Go matching
go.mod - Docker (or OrbStack) —
make devexpects a running daemon kind,kubectl,helmtiltfor the inner dev loop
One-time setup:
make tools # installs ko, golangci-lint, setup-envtest, controller-gen into ./binInner loop:
make dev # creates kind cluster "synthetics-dev", runs `tilt up` with live-reloadmake dev watches the source, rebuilds the operator image on save, and re-deploys. Probes you kubectl apply to the dev cluster will be picked up by the running operator.
| Target | What it runs | Speed |
|---|---|---|
make test |
Unit tests for everything except envtest. | Seconds. |
make test-envtest |
Unit tests + envtest (spins up an ephemeral API server via setup-envtest). |
~30s. |
make lint |
golangci-lint. |
Seconds. |
make helm-lint / make helm-template |
Chart validation. | Seconds. |
CI runs the same targets on every PR (.github/workflows/ci.yaml). A kind-smoke job runs on pushes to main and does a full end-to-end check: operator image built, loaded into kind, chart installed with NATS enabled, an HTTPProbe and a PlaywrightTest applied, and their metrics verified on /metrics.
Before opening a PR, at minimum:
make lint
make test-envtest
make helm-lintThe API types in api/v1alpha1/ drive both the zz_generated.deepcopy.go and the CRD YAML in config/crd/bases/ (copied into charts/synthetics-operator/crds/). After changing any spec struct, marker, or kubebuilder annotation:
make generateUsing PlaywrightTest as the reference pattern, a new synthetic kind needs:
- Types in
api/v1alpha1/<kind>_types.go— spec + status structs, SchemeBuilder registration,+kubebuilder:printcolumnmarkers. - Webhook in
api/v1alpha1/<kind>_webhook.go— defaulter + validator. Validator struct holdsclient.Reader(seeHTTPProbeValidator) for dep existence and cycle checks. CallValidateDependsandValidateMetricLabelsfromvalidate(). - Reconciler in
controllers/<kind>_controller.go. Model onplaywrighttest_controller.gofor CronJob-backed kinds orhttp_probe_controller.gofor in-process kinds. Must callMetrics.Delete,Metrics.ClearDepends, andMetrics.ClearMetricLabelson deletion andMetrics.SetDepends+Metrics.SetMetricLabelson reconcile. - Runner image (tests only) in
images/<kind>-runner/. Writes aTestResultJSON to/results/output.json; thetest-sidecarpicks it up and ships over NATS. - Main wiring in
main.go— construct the reconciler, register its webhook, pass image flags through. - Helm — add the kind to
charts/synthetics-operator/templates/clusterrole.yamlandclusterrole-webhook.yaml, add mutating + validating webhook config entries inwebhooks.yaml, add anykind-specific-imagevalues. - Store — if the kind has family-specific metrics (like
synthetics_test_playwright_case_*), add observable gauges ininternal/metrics/store.goand plumb them into the observe callback. - DependencyKind enum — add the new kind to
api/v1alpha1/depends.go(DependencyKind<Kind>constant and the switch inValidateDepends/fetchDepends/checkDepExists). - Docs — README §2 CRD table, §3 metric schema,
UBIQUITOUS_LANGUAGE.mdif the kind introduces new vocabulary. - Tests — webhook test mirroring the existing patterns, at least one envtest covering admission + reconciliation, example YAML in
examples/. - kind-smoke — extend
.github/workflows/ci.yamlto apply the new kind and assert on at least one emitted metric.
Run make generate && make test-envtest && make helm-lint to shake out missing pieces.
Releases are triggered by pushing a tag vX.Y.Z:
git tag v1.2.3
git push origin v1.2.3The release workflow builds and publishes:
- Operator image,
test-sidecar, andk6-runnerviakotoghcr.io/loks0n/synthetics-*. playwright-runnervia Docker buildx to the same registry.- Helm chart packaged as OCI artifact at
oci://ghcr.io/loks0n/charts/synthetics-operator. - GitHub Release notes generated from commits since the last tag.
See .github/workflows/release.yaml and .goreleaser.yaml for the exact steps.
- One logical change per PR. If you're renaming a thing and adding a feature, those are two PRs.
- Regenerate CRDs + run
make lintbefore pushing. - For new CRD kinds or schema changes, update the README metric schema and
UBIQUITOUS_LANGUAGE.md. - Include test coverage for new behaviour.
- Commit messages: brief subject (~70 chars), body explaining why if non-obvious. The commit log is the project's history; make it readable.
In order of increasing work:
kubectl describe httpprobe my-probe— transition events (ProbeActive,ProbeFailed) show a timeline.curl $(kubectl -n synthetics-system get svc synthetics-operator-metrics -o jsonpath='{.spec.clusterIP}'):8080/metrics | grep synthetics_probe— current pass/fail +resultlabel.kubectl -n synthetics-system logs deployment/synthetics-operator— reconcile and scheduler output.- For
K6Test/PlaywrightTest:kubectl get cronjob my-testandkubectl logs -l job-name=my-test-<id>on the most recent job pod.
| Path | What it is |
|---|---|
api/v1alpha1/ |
CRD types, webhooks, generated deepcopy |
controllers/ |
Reconcilers for each kind, plus scheduler.go (in-process probes) |
internal/probes/ |
Per-kind Probe executors and Scheduler |
internal/metrics/ |
Prometheus/OTel store, synthetics_probe / synthetics_test gauges |
internal/events/ |
Transition notifier emitting ProbeActive / ProbeFailed events |
internal/natsconsumer/ |
NATS subscriber feeding test results into the metrics store |
internal/results/ |
Shared NATS message format (TestResult) |
internal/webhookcerts/ |
Self-managed webhook serving certs, hot-reload |
images/ |
Runner image sources (k6-runner, playwright-runner, test-sidecar) |
charts/synthetics-operator/ |
Helm chart |
dashboards/ |
Grafana dashboards (source JSON; hack/dashboard-configmaps.yaml is generated) |
alerts/ |
PrometheusRule spec |
examples/ |
Reference CRD YAMLs |
hack/ |
kind config, tool installers, generated artifacts |