Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP Add grafana service account feature to Grafana controller #1907

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

ndk
Copy link

@ndk ndk commented Mar 17, 2025

Not yet done :)

#1469

This PR is a work in progress. I based my implementation on the existing Datasource controller, and at this point the core functionality appears to be complete. However, the code still needs refactoring to improve testability, and I'm currently adding tests that will likely uncover bugs and missing pieces. As I'm new to writing Kubernetes Operators, I'm carefully reviewing tutorials and documentation to ensure that I follow best practices and don’t miss anything important. Feedback is very welcome!

@github-actions github-actions bot added the documentation Issues relating to documentation, missing, non-clear etc. label Mar 17, 2025
@weisdd weisdd added the feature this PR introduces a new feature label Mar 19, 2025
Copy link
Collaborator

@Baarsgaard Baarsgaard left a comment

Choose a reason for hiding this comment

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

Hi, thanks for the PR! :D
As this is WIP I have mostly commented on style rather than functionality or wording.

Since you diligent with adding logs, I would like to direct you to: https://github.com/grafana/grafana-operator/blob/master/controllers/contactpoint_controller.go#L209
It's one of the few examples of debug level logs in the project by adding the .V(1)
It could be useful to you if you've been wanting to add more logs but have been holding back.
In case you have questions, feel free to leave comments!

Comment on lines +110 to +112

// NoMatchingInstances indicates that the instanceSelector cannot find any matching Grafana instances.
NoMatchingInstances bool `json:"NoMatchingInstances,omitempty"`
Copy link
Collaborator

Choose a reason for hiding this comment

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

The plan is to deprecate the status.NoMatchingInstances in favour of using conditions.
Hence we would like to avoid it on new CRs :)

This means we won't have it as a status column either.

Suggested change
// NoMatchingInstances indicates that the instanceSelector cannot find any matching Grafana instances.
NoMatchingInstances bool `json:"NoMatchingInstances,omitempty"`

Copy link
Collaborator

@Baarsgaard Baarsgaard Mar 21, 2025

Choose a reason for hiding this comment

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

I recently (#1895) removed the grafana prefix from the controller files due to some files having the prefix and some not.

Comment on lines +140 to +144
// ResyncPeriodHasElapsed returns true if the time since last resync has exceeded the desired period.
func (in *GrafanaServiceAccount) ResyncPeriodHasElapsed() bool {
deadline := in.Status.LastResync.Add(in.Spec.ResyncPeriod.Duration)
return time.Now().After(deadline)
}
Copy link
Collaborator

@Baarsgaard Baarsgaard Mar 21, 2025

Choose a reason for hiding this comment

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

We have generally moved to relying on the controller.Runtime instead of checking the ResyncPeriod.
You can have a look at newer reconcilers like AlertRuleGroup which does not include this function nor the check.

Suggested change
// ResyncPeriodHasElapsed returns true if the time since last resync has exceeded the desired period.
func (in *GrafanaServiceAccount) ResyncPeriodHasElapsed() bool {
deadline := in.Status.LastResync.Add(in.Spec.ResyncPeriod.Duration)
return time.Now().After(deadline)
}


// Reconcile contains the main reconciliation logic for GrafanaServiceAccount.
func (r *GrafanaServiceAccountReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx).WithName("GrafanaServiceAccountReconciler")
Copy link
Collaborator

Choose a reason for hiding this comment

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

This does save characters but it does not conform well to the rest of the code base :)

Suggested change
l := log.FromContext(ctx).WithName("GrafanaServiceAccountReconciler")
log := log.FromContext(ctx).WithName("GrafanaServiceAccountReconciler")

ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"
Copy link
Collaborator

@Baarsgaard Baarsgaard Mar 21, 2025

Choose a reason for hiding this comment

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

To use log from above, the import needs to be changed.

Suggested change
"sigs.k8s.io/controller-runtime/pkg/log"
logf "sigs.k8s.io/controller-runtime/pkg/log"

Comment on lines +374 to +379
if err := ctrl.NewControllerManagedBy(mgr).
For(&v1beta1.GrafanaServiceAccount{}).
WithEventFilter(ignoreStatusUpdates()).
Complete(r); err != nil {
return err
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Embedding the initialization of err inside the if is less useful when it obscures where the body of the if starts and can in this case hurt code readability.

Comment on lines +519 to +523
// Update CR status with new tokens.
if err := r.Client.Status().Update(ctx, cr); err != nil {
log.FromContext(ctx).Error(err, "failed to update GrafanaServiceAccount status with new tokens")
return err
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is it necessary to update the status multiple times when the status is always updated after a reconcile in the defer?

Comment on lines +608 to +610
} else {
l.Info("updated permission for user", "userID")
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

The else body can be omitted if the error case calls continue
I've been reading Effective Go recently which has a good example of this.

Suggested change
} else {
l.Info("updated permission for user", "userID")
}
} else {
l.Info("updated permission for user", "userID")
}

Comment on lines +600 to +605
if _, err := accessControlClient.SetResourcePermissionsForUser( // nolint:errcheck
access_control.NewSetResourcePermissionsForUserParamsWithContext(ctx).
WithBody(&models.SetPermissionCommand{Permission: desiredPerm}).
WithResource(resource).
WithResourceID(resourceID).
WithUserID(curr.UserID),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same note on initialization hiding that it's a standard if err != nil {}


// Save the service account ID in CR status and update Grafana status.
cr.Status.ID = serviceAccountID
ctx = log.IntoContext(ctx, log.FromContext(ctx).WithValues("serviceAccountID", serviceAccountID))
Copy link
Collaborator

Choose a reason for hiding this comment

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

You could clean this up a bit by moving the log.FromContext(ctx)... into both the r.reconcileTokens and r.reconcilePermissions as the serviceAccountID is passed to both.
This keeps the pattern of the first thing in each function is extracting a logger from the context.

@ndk
Copy link
Author

ndk commented Mar 23, 2025

I found lots of issues and missed cases in my code. Lemme rework it yet. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Issues relating to documentation, missing, non-clear etc. feature this PR introduces a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants