Skip to content

Manage Grafana Service Accounts from the Grafana CR #1907

New issue

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

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

Already on GitHub? # to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

# Binaries for programs and plugins
__debug_bin*
*.exe
*.exe~
*.dll
Expand All @@ -26,6 +27,7 @@ vendor
*~

.vscode/
.mirrord/
.DS_Store

# Audit lab
Expand Down
126 changes: 107 additions & 19 deletions api/v1beta1/grafana_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,16 @@ type OperatorStageName string
type OperatorStageStatus string

const (
OperatorStageGrafanaConfig OperatorStageName = "config"
OperatorStageAdminUser OperatorStageName = "admin user"
OperatorStagePvc OperatorStageName = "pvc"
OperatorStageServiceAccount OperatorStageName = "service account"
OperatorStageService OperatorStageName = "service"
OperatorStageIngress OperatorStageName = "ingress"
OperatorStagePlugins OperatorStageName = "plugins"
OperatorStageDeployment OperatorStageName = "deployment"
OperatorStageComplete OperatorStageName = "complete"
OperatorStageGrafanaConfig OperatorStageName = "config"
OperatorStageAdminUser OperatorStageName = "admin user"
OperatorStagePvc OperatorStageName = "pvc"
OperatorStageServiceAccount OperatorStageName = "service account"
OperatorStageService OperatorStageName = "service"
OperatorStageIngress OperatorStageName = "ingress"
OperatorStagePlugins OperatorStageName = "plugins"
OperatorStageDeployment OperatorStageName = "deployment"
OperatorStageGrafanaServiceAccounts OperatorStageName = "grafana service-accounts"
OperatorStageComplete OperatorStageName = "complete"
)

const (
Expand All @@ -52,6 +53,50 @@ type OperatorReconcileVars struct {
Plugins string
}

// GrafanaServiceAccountTokenSpec describes a token to create.
type GrafanaServiceAccountTokenSpec struct {
// Name is the name of the Kubernetes Secret (and token identifier in Grafana). The secret will contain the token value.
// +kubebuilder:validation:Required
Name string `json:"name"`

// Expires is the optional expiration time for the token. After this time, the operator may rotate the token.
// +kubebuilder:validation:Optional
Expires *metav1.Time `json:"expires,omitempty"`
}

// GrafanaServiceAccountSpec defines the desired state of a GrafanaServiceAccount.
type GrafanaServiceAccountSpec struct {
// ID is a kind of unique identifier to distinguish between service accounts if the name is changed.
// +kubebuilder:validation:Required
ID string `json:"id"`

// Name is the desired name of the service account in Grafana.
// +kubebuilder:validation:Required
Name string `json:"name"`

// Role is the Grafana role for the service account (Viewer, Editor, Admin).
// +kubebuilder:validation:Required
// +kubebuilder:validation:Enum=Viewer;Editor;Admin
Role string `json:"role"`

// IsDisabled indicates if the service account should be disabled in Grafana.
// +kubebuilder:validation:Optional
IsDisabled bool `json:"isDisabled,omitempty"`

// Tokens defines API tokens to create for this service account. Each token will be stored in a Kubernetes Secret with the given name.
// +kubebuilder:validation:Optional
Tokens []GrafanaServiceAccountTokenSpec `json:"tokens,omitempty"`
}

type GrafanaServiceAccounts struct {
Accounts []GrafanaServiceAccountSpec `json:"accounts,omitempty"`

// GenerateTokenSecret, if true, will create one default API token in a Secret if no Tokens are specified.
// If false, no token is created unless explicitly listed in Tokens.
// +kubebuilder:default=true
GenerateTokenSecret bool `json:"generateTokenSecret,omitempty"`
}

// GrafanaSpec defines the desired state of Grafana
type GrafanaSpec struct {
// +kubebuilder:pruning:PreserveUnknownFields
Expand Down Expand Up @@ -80,6 +125,8 @@ type GrafanaSpec struct {
Preferences *GrafanaPreferences `json:"preferences,omitempty"`
// DisableDefaultAdminSecret prevents operator from creating default admin-credentials secret
DisableDefaultAdminSecret bool `json:"disableDefaultAdminSecret,omitempty"`
// Grafana Service Accounts
GrafanaServiceAccounts *GrafanaServiceAccounts `json:"grafanaServiceAccounts,omitempty"`
}

type External struct {
Expand Down Expand Up @@ -131,18 +178,59 @@ type GrafanaPreferences struct {
HomeDashboardUID string `json:"homeDashboardUid,omitempty"`
}

// GrafanaServiceAccountTokenStatus describes a token created in Grafana.
type GrafanaServiceAccountTokenStatus struct {
// Name is the name of the Kubernetes Secret. The secret will contain the token value.
Name string `json:"name"`

// Expires is the expiration time for the token.
// N.B. There's possible discrepancy with the expiration time in spec.
// It happens because Grafana API accepts TTL in seconds then calculates the expiration time against the current time.
Expires *metav1.Time `json:"expires,omitempty"`

// ID is the Grafana-assigned ID of the token.
ID int64 `json:"tokenId"`

// SecretName is the name of the Kubernetes Secret that stores the actual token value.
// This may seem redundant if the Secret name usually matches the token's Name,
// but it's stored explicitly in Status for clarity and future flexibility.
SecretName string `json:"secretName"`
}

// GrafanaServiceAccountStatus holds status for one Grafana instance.
type GrafanaServiceAccountStatus struct {
// SpecID is a kind of unique identifier to distinguish between service accounts if the name is changed.
SpecID string `json:"specId"`

// Name is the name of the service account in Grafana.
Name string `json:"name"`

// ServiceAccountID is the numeric ID of the service account in this Grafana.
ServiceAccountID int64 `json:"serviceAccountId"`

// Role is the Grafana role for the service account (Viewer, Editor, Admin).
Role string `json:"role"`

// IsDisabled indicates if the service account is disabled.
IsDisabled bool `json:"isDisabled,omitempty"`

// Tokens is the status of tokens for this service account in Grafana.
Tokens []GrafanaServiceAccountTokenStatus `json:"tokens,omitempty"`
}

// GrafanaStatus defines the observed state of Grafana
type GrafanaStatus struct {
Stage OperatorStageName `json:"stage,omitempty"`
StageStatus OperatorStageStatus `json:"stageStatus,omitempty"`
LastMessage string `json:"lastMessage,omitempty"`
AdminURL string `json:"adminUrl,omitempty"`
ContactPoints NamespacedResourceList `json:"contactPoints,omitempty"`
Dashboards NamespacedResourceList `json:"dashboards,omitempty"`
Datasources NamespacedResourceList `json:"datasources,omitempty"`
Folders NamespacedResourceList `json:"folders,omitempty"`
LibraryPanels NamespacedResourceList `json:"libraryPanels,omitempty"`
Version string `json:"version,omitempty"`
Stage OperatorStageName `json:"stage,omitempty"`
StageStatus OperatorStageStatus `json:"stageStatus,omitempty"`
LastMessage string `json:"lastMessage,omitempty"`
AdminURL string `json:"adminUrl,omitempty"`
ContactPoints NamespacedResourceList `json:"contactPoints,omitempty"`
Dashboards NamespacedResourceList `json:"dashboards,omitempty"`
Datasources NamespacedResourceList `json:"datasources,omitempty"`
Folders NamespacedResourceList `json:"folders,omitempty"`
LibraryPanels NamespacedResourceList `json:"libraryPanels,omitempty"`
GrafanaServiceAccounts []GrafanaServiceAccountStatus `json:"serviceAccounts,omitempty"`
Version string `json:"version,omitempty"`
}

// +kubebuilder:object:root=true
Expand Down
116 changes: 116 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading