Skip to content

feat!: Reference Secrets through secretRef and record an Event per action - #76

Merged
as51340 merged 1 commit into
mainfrom
feat/secret-refs-and-events
Oct 16, 2026
Merged

as51340 merged 1 commit into
mainfrom
feat/secret-refs-and-events

Conversation

@as51340

@as51340 as51340 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Why

Secret references. The Kubernetes API conventions recommend a struct for an object reference (secretRef: {name}) over a bare secretName string, so a qualifier such as namespace can be added later without a breaking change. On v1alpha1 the rename is cheap now; after v1 it would be a breaking change.

Events. The operator recorded what it did only in its own log, and the operator emitted no Kubernetes Events at all. The Kubernetes audit log covers every write the operator makes to the API server, but nothing it sends to Memgraph over Bolt (REGISTER INSTANCE, promoting a MAIN, DEMOTE INSTANCE, UNREGISTER INSTANCE, REMOVE COORDINATOR, SET DATABASE SETTING). I surveyed 13 operators' source: every one that reports in-database actions (CloudNativePG, Zalando, cass-operator, Strimzi, RabbitMQ, Percona XtraDB, ClickHouse) records Events on its custom resource, and none keeps an audit log of its own.

What

secretRef

Before After
awsCredentials.secretName awsCredentials.secretRef.name
tls.bolt.secretName tls.bolt.secretRef.name
tls.intraCluster.secretName tls.intraCluster.secretRef.name
monitoring.vmagentRemote.remoteWrite.basicAuth.secretName …basicAuth.secretRef.name
monitoring.vectorRemote.auth.secretName …auth.secretRef.name
  • Type: one shared SecretReference with name only; the name validation moved onto it. There is no namespace field yet: every one of these Secrets reaches the pods as a mount or an env reference, and a pod can only read Secrets of its own namespace. Supporting another namespace would mean the operator copying Secrets.
  • Unchanged: the secrets license block keeps the HA chart's name/licenseKey/organizationKey shape. It's already a struct, so a namespace can be added beside name later.

Events

  • What: one events.k8s.io/v1 Event on the MemgraphCluster per action, with one reason per action. The touched pod, claim or StatefulSet is the Event's related object.
    • Registration: CoordinatorAdded, InstanceRegistered, MainPromoted, MainPromotionRefused, MainDemoted, InstanceUnregistered, CoordinatorRemoved, LeadershipYielded, BoltServerUpdated, RegistrationFailed.
    • Settings: SettingApplied, SettingRejected, CoordinatorSettingApplied.
    • Kubernetes actions: PodRestarted, StatefulSetShrunk, VolumeExpansionRequested, StatefulSetOrphaned.
  • Registration Events come from recordingClient, a wrapper around the leader's memgraph.Client (internal/controller/events.go). They record what Memgraph accepted or refused, not what the plan intended. A promotion tries its candidates inside one command, and only the client sees which one won: a refused candidate gets MainPromotionRefused, and the instance that really became MAIN gets MainPromoted.
  • Settings Events are recorded in applySettings with the same description the conditions use, so values read from a Secret (license, organization, AWS) never reach an Event.
  • No Events for waiting: waiting stays in the conditions, and a pass that changes nothing records nothing.
  • RBAC: create;patch on events.k8s.io events, generated from the marker into config/rbac/role.yaml and the chart. The recorder is mgr.GetEventRecorder("memgraph-operator").
  • Docs: the new docs/events.md lists every reason and when it fires; the README links to it.

Also

  • CLAUDE.md: a convention for each change: Secret references are secretRef structs, and the Events design.
  • Chart: version 1.0.1-rc.3 → 1.0.1-rc.4 for the CRD and RBAC changes.

Testing

  • Envtest Events: a test recorder (internal/controller/events_test.go) keeps Events in order. Existing specs now also assert the Events of:
    • bootstrap (exact list and order, and nothing on a no-op pass);
    • scale-down handover, promotion fallback, and restore of a refused handover;
    • leadership yield and coordinator removal, plus the shed;
    • the roll's first restart (related pod);
    • claim resize and StatefulSet orphan delete;
    • run-time flags and a rejected setting;
    • coordinator settings;
    • license and AWS credentials, asserting no secret value appears.
  • Mutation check: changing one Event message made the two specs asserting it fail, so the new assertions do run.
  • Envtest secretRef: the validation specs expect the new …secretRef.name paths.
  • Commands run: make test, make lint, make chart-verify, make helm-lint, make chart-version-check and go vet -tags e2e ./test/e2e/ all pass. I didn't run the e2e suite. Its manifests and patches use the new field names, but Events haven't yet been seen on a real cluster.

…tion

Two changes to how the operator talks about what it touches.

Secret references. The four bare secretName fields become a secretRef
struct, the shape the Kubernetes API conventions recommend for a single-kind
object reference, so a qualifier can be added later without breaking a
stored object:

- awsCredentials.secretName -> awsCredentials.secretRef.name
- tls.bolt.secretName -> tls.bolt.secretRef.name
- tls.intraCluster.secretName -> tls.intraCluster.secretRef.name
- monitoring.vmagentRemote.remoteWrite.basicAuth.secretName and
  monitoring.vectorRemote.auth.secretName -> ....secretRef.name

The struct carries name only. The Secret must still live in the cluster's
namespace, because every one of them reaches the pods as a mount or an env
reference, and a pod can only read Secrets of its own namespace; a namespace
field would need the operator to copy the Secret. The secrets license block
keeps the HA chart's name/licenseKey/organizationKey shape, already a struct.

Events. The operator recorded what it did only in its own log, and the Bolt
commands it sends to Memgraph are invisible to the Kubernetes audit log. It
now records an events.k8s.io/v1 Event on the MemgraphCluster for every
action, one reason per action, with the touched pod, claim or StatefulSet as
the related object: CoordinatorAdded, InstanceRegistered, MainPromoted,
MainPromotionRefused, MainDemoted, InstanceUnregistered, CoordinatorRemoved,
LeadershipYielded, BoltServerUpdated, RegistrationFailed, SettingApplied,
SettingRejected, CoordinatorSettingApplied, PodRestarted, StatefulSetShrunk,
VolumeExpansionRequested and StatefulSetOrphaned. Registration Events come
from a decorator over the leader's client, so they record what Memgraph
accepted or refused rather than what the plan intended: a promotion tries
its candidates inside one command, and only the client sees which one won.
Values read from a Secret never reach an Event. Waiting records nothing;
that is what the conditions report. The manager gains create and patch on
events.k8s.io events. docs/events.md lists every reason.

BREAKING CHANGE: the secretName fields are gone from the schema, so the API
server prunes them. A cluster using awsCredentials, tls, or the monitoring
basic-auth blocks must be rewritten to secretRef.name before the CRD is
upgraded; tls.intraCluster in particular cannot be dropped and re-added on a
live cluster, so stop the operator, upgrade the CRD, rewrite those clusters,
then start the new operator.
@as51340 as51340 self-assigned this Oct 16, 2026
@as51340
as51340 merged commit 4dbc712 into main Oct 16, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant