Skip to content

feat(Grafana): Update Grafana Deployment when referenced Secret or Configmap contents change#2525

Open
vignesh-codes wants to merge 8 commits intografana:masterfrom
vignesh-codes:feat/grafana-cr-secret-configmap-watcher
Open

feat(Grafana): Update Grafana Deployment when referenced Secret or Configmap contents change#2525
vignesh-codes wants to merge 8 commits intografana:masterfrom
vignesh-codes:feat/grafana-cr-secret-configmap-watcher

Conversation

@vignesh-codes
Copy link
Copy Markdown
Contributor

@vignesh-codes vignesh-codes commented Feb 22, 2026

Description

This PR adds automatic rolling restarts for Grafana pods when Secrets or ConfigMaps referenced in a Grafana CR are changed. Previously, the operator had no mechanism to react to secret changes, leaving Grafana pods running with stale credentials until manually restarted.

Related Issue

Fixes #2484

What Changed

Added a new SecretsReconciler to the grafana controller:

  • Watches all secrets and configmaps referenced in grafana CR's deployment spec
  • Computes a SHA-256 hash of the ResourceVersion of each referenced object without storing the actual secret data.
  • Injects the hash as a secret_hash env var on the grafana pod template
  • Triggers k8s rolling restart automatically when any referecned secret or configmap changes

References are collected from:

  • spec.deployment.spec.template.spec.containers[ * ] and initContainers[ * ] - secretKeyRef, secretRef, configMapKeyRef, configMapRef
  • spec.deployment.spec.template.spec.volumes[ * ] - secret, configMap
  • spec.external - apiKey, adminUser, adminPassword, tls.certSecretRef
  • spec.client.tls.certSecretRef

Tests

Deployed this grafana

# grafana.yaml
apiVersion: grafana.integreatly.org/v1beta1
kind: Grafana
metadata:
  name: test-grafana
  namespace: grafana-operator-test
spec:
  deployment:
    spec:
      template:
        spec:
          containers:
            - name: grafana
              env:
                - name: GF_DATABASE_PASSWORD
                  valueFrom:
                    secretKeyRef:
                      name: test-db-secret
                      key: password

Deployed this secret

# secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: test-db-secret
  namespace: grafana-operator-test
  labels:
    app.kubernetes.io/managed-by: grafana-operator
type: Opaque
stringData:
  password: initial-password
  username: grafana

Changed the secret to check if the grafana pod restarts
Screenshot from 2026-02-22 15-33-44

Pod restarted

image

Unit Tests

  • added basic unit tests

@github-actions github-actions bot added the feature this PR introduces a new feature label Feb 22, 2026
Copy link
Copy Markdown
Collaborator

@theSuess theSuess left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the work you put into this! I took an initial look at this and will chat with the other maintainers about this in our sync today!

Comment on lines +83 to +117
func ReferencedSecretsAndConfigMaps(cr *v1beta1.Grafana) ([]string, []string) {
secretSet := make(map[string]struct{})
configMapSet := make(map[string]struct{})

addSecret := func(name string) {
if name != "" {
secretSet[name] = struct{}{}
}
}

addConfigMap := func(name string) {
if name != "" {
configMapSet[name] = struct{}{}
}
}

collectDeploymentRefs(cr, addSecret, addConfigMap)
collectExternalRefs(cr, addSecret)
collectClientRefs(cr, addSecret)

secretNames := make([]string, 0, len(secretSet))
for name := range secretSet {
secretNames = append(secretNames, name)
}

configMapNames := make([]string, 0, len(configMapSet))
for name := range configMapSet {
configMapNames = append(configMapNames, name)
}

sort.Strings(secretNames)
sort.Strings(configMapNames)

return secretNames, configMapNames
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a struct function of the v1beta1.Grafana type directly

return secretNames, configMapNames
}

func collectDeploymentRefs(cr *v1beta1.Grafana, addSecret, addConfigMap func(string)) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these functions would be cleaner when returning arrays directly rather than relying on passed down closures

// Grafana CR's deployment spec and external config, hashes them, and stores the result in
// vars.SecretsHash. The deployment reconciler then injects this as a SECRETS_HASH env var,
// so any secret rotation causes a pod template change and triggers a rolling restart.
func (r *SecretsReconciler) Reconcile(ctx context.Context, cr *v1beta1.Grafana, vars *v1beta1.OperatorReconcileVars, scheme *runtime.Scheme) (v1beta1.OperatorStageStatus, error) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can have this be part of the deployment reconciler

@Baarsgaard / @weisdd what do you think?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talked about this in the weekly sync, please refactor this to be part of the deployment reconciler

Comment on lines +176 to +180
{
// helps to restart Grafana when referenced secrets or configmaps are rotated
Name: "SECRETS_HASH",
Value: vars.SecretsHash,
},
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is not actually used, it's better to have this be an annotation on the pod instead. This will also trigger a rollout without polluting the environment variables

vignesh-codes and others added 3 commits February 28, 2026 15:52
…edSecretsAndConfigMaps on Grafana and used GetEnvVarConfigMapSource from tk8s
…ncedSecretsAndConfigMaps and deployment reconciler cheksum annotation process
@vignesh-codes
Copy link
Copy Markdown
Contributor Author

Hey @theSuess I have updated based on ur comments. Lemme know if any more changes required. thanks :)

Copy link
Copy Markdown
Collaborator

@theSuess theSuess left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late review

Looks good from my end, just have a few style suggestions

Comment on lines +352 to +356
// Refs are collected from: (1) deployment pod template — container Env ValueFrom (SecretKeyRef/
// ConfigMapKeyRef) and EnvFrom (SecretRef/ConfigMapRef), plus volume Secret and ConfigMap;
// (2) external — AdminUser, AdminPassword, APIKey, TLS cert Secret if set; (3) client TLS cert
// Secret if set. The deployment reconciler uses this to compute a hash of those resources'
// ResourceVersions and sets the checksum/secrets pod template annotation.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we can probably format this better so that each point has a linebreak

return secretNames, configMapNames
}

func (in *Grafana) collectDeploymentRefs() (secrets, configMaps []string) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming this deploymentRefs() would be more idiomatic go

return secrets, configMaps
}

func collectContainerEnvRefs(c corev1.Container) (secrets, configMaps []string) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func collectContainerEnvRefs(c corev1.Container) (secrets, configMaps []string) {
func containerEnvRefs(c corev1.Container) (secrets, configMaps []string) {

return secrets, configMaps
}

func (in *Grafana) collectExternalRefs() []string {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since external instances don't need restarting (and credentials are updated immediately), we don't need to index these

})
})

func TestGrafana_ReferencedSecretsAndConfigMaps(t *testing.T) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since many test cases only test against the container env vars, can you extract these to their own test and run against collectContainerEnvRefs directly?

I think this would reduce the overhead a bit.

Another alternative would be to create a top level "blank" &Grafana{} on top and then set the required fields using struct accessors like basic.Spec.Deployment.Spec.Template.Spec.Containers = []corev1.Container{...} which would drastically cut down on } } } } } waterfalls 😅

@vignesh-codes
Copy link
Copy Markdown
Contributor Author

vignesh-codes commented Mar 30, 2026

Hey @theSuess,
was supposed to push my changes last week - completely forgot. I have updated the codes. lemme know if its fine.
Thanks :)

Copy link
Copy Markdown
Collaborator

@theSuess theSuess left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last minor thing and then this is good to go!

Comment on lines +458 to +464
func (in *Grafana) collectClientRefs() []string {
if in.Spec.Client == nil || in.Spec.Client.TLS == nil || in.Spec.Client.TLS.CertSecretRef == nil {
return nil
}

return []string{in.Spec.Client.TLS.CertSecretRef.Name}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Client secrets are not used in the deployment so this is not needed

@Baarsgaard Baarsgaard changed the title feat: watch referenced secrets and configmaps in Grafana CR and triger rolling restart on change feat(Grafana): Update Grafana Deployment when referenced Secret or Configmap contents change Mar 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature this PR introduces a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Operator should trigger Deployment rollout when secrets referenced in Grafana CR change

2 participants