@@ -52,6 +52,54 @@ type OperatorReconcileVars struct {
52
52
Plugins string
53
53
}
54
54
55
+ // GrafanaServiceAccountTokenSpec describes a token to create.
56
+ type GrafanaServiceAccountTokenSpec struct {
57
+ // Name is the name of the Kubernetes Secret (and token identifier in Grafana). The secret will contain the token value.
58
+ // +kubebuilder:validation:Required
59
+ Name string `json:"name"`
60
+
61
+ // Expires is the optional expiration time for the token. After this time, the operator may rotate the token.
62
+ // +kubebuilder:validation:Optional
63
+ Expires * metav1.Time `json:"expires,omitempty"`
64
+ }
65
+
66
+ // GrafanaServiceAccountSpec defines the desired state of a GrafanaServiceAccount.
67
+ type GrafanaServiceAccountSpec struct {
68
+ // ID is a kind of unique identifier to distinguish between service accounts if the name is changed.
69
+ // +kubebuilder:validation:Required
70
+ ID string `json:"id"`
71
+
72
+ // Name is the desired name of the service account in Grafana.
73
+ // +kubebuilder:validation:Required
74
+ Name string `json:"name"`
75
+
76
+ // Role is the Grafana role for the service account (Viewer, Editor, Admin).
77
+ // +kubebuilder:validation:Required
78
+ // +kubebuilder:validation:Enum=Viewer;Editor;Admin
79
+ Role string `json:"role"`
80
+
81
+ // IsDisabled indicates if the service account should be disabled in Grafana.
82
+ // +kubebuilder:validation:Optional
83
+ IsDisabled bool `json:"isDisabled,omitempty"`
84
+
85
+ // Tokens defines API tokens to create for this service account. Each token will be stored in a Kubernetes Secret with the given name.
86
+ // +kubebuilder:validation:Optional
87
+ Tokens []GrafanaServiceAccountTokenSpec `json:"tokens,omitempty"`
88
+ }
89
+
90
+ type GrafanaServiceAccounts struct {
91
+ // Accounts lists Grafana service accounts to manage.
92
+ // Each service account is uniquely identified by its ID.
93
+ // +listType=map
94
+ // +listMapKey=id
95
+ Accounts []GrafanaServiceAccountSpec `json:"accounts,omitempty"`
96
+
97
+ // GenerateTokenSecret, if true, will create one default API token in a Secret if no Tokens are specified.
98
+ // If false, no token is created unless explicitly listed in Tokens.
99
+ // +kubebuilder:default=true
100
+ GenerateTokenSecret bool `json:"generateTokenSecret,omitempty"`
101
+ }
102
+
55
103
// GrafanaSpec defines the desired state of Grafana
56
104
type GrafanaSpec struct {
57
105
// +kubebuilder:pruning:PreserveUnknownFields
@@ -83,6 +131,8 @@ type GrafanaSpec struct {
83
131
// DisableDefaultSecurityContext prevents the operator from populating securityContext on deployments
84
132
// +kubebuilder:validation:Enum=Pod;Container;All
85
133
DisableDefaultSecurityContext string `json:"disableDefaultSecurityContext,omitempty"`
134
+ // Grafana Service Accounts
135
+ GrafanaServiceAccounts * GrafanaServiceAccounts `json:"grafanaServiceAccounts,omitempty"`
86
136
}
87
137
88
138
type External struct {
@@ -134,22 +184,68 @@ type GrafanaPreferences struct {
134
184
HomeDashboardUID string `json:"homeDashboardUid,omitempty"`
135
185
}
136
186
187
+ type GrafanaServiceAccountSecretStatus struct {
188
+ Namespace string `json:"namespace,omitempty"`
189
+ Name string `json:"name,omitempty"`
190
+ }
191
+
192
+ // GrafanaServiceAccountTokenStatus describes a token created in Grafana.
193
+ type GrafanaServiceAccountTokenStatus struct {
194
+ // Name is the name of the Kubernetes Secret. The secret will contain the token value.
195
+ Name string `json:"name"`
196
+
197
+ // Expires is the expiration time for the token.
198
+ // N.B. There's possible discrepancy with the expiration time in spec.
199
+ // It happens because Grafana API accepts TTL in seconds then calculates the expiration time against the current time.
200
+ Expires * metav1.Time `json:"expires,omitempty"`
201
+
202
+ // ID is the Grafana-assigned ID of the token.
203
+ ID int64 `json:"tokenId"`
204
+
205
+ // Secret is the Kubernetes Secret that stores the actual token value.
206
+ // This may seem redundant if the Secret name usually matches the token's Name,
207
+ // but it's stored explicitly in Status for clarity and future flexibility.
208
+ Secret * GrafanaServiceAccountSecretStatus `json:"secret,omitempty"`
209
+ }
210
+
211
+ // GrafanaServiceAccountStatus holds status for one Grafana instance.
212
+ type GrafanaServiceAccountStatus struct {
213
+ // SpecID is a kind of unique identifier to distinguish between service accounts if the name is changed.
214
+ SpecID string `json:"specId"`
215
+
216
+ // Name is the name of the service account in Grafana.
217
+ Name string `json:"name"`
218
+
219
+ // ServiceAccountID is the numeric ID of the service account in this Grafana.
220
+ ServiceAccountID int64 `json:"serviceAccountId"`
221
+
222
+ // Role is the Grafana role for the service account (Viewer, Editor, Admin).
223
+ Role string `json:"role"`
224
+
225
+ // IsDisabled indicates if the service account is disabled.
226
+ IsDisabled bool `json:"isDisabled,omitempty"`
227
+
228
+ // Tokens is the status of tokens for this service account in Grafana.
229
+ Tokens []GrafanaServiceAccountTokenStatus `json:"tokens,omitempty"`
230
+ }
231
+
137
232
// GrafanaStatus defines the observed state of Grafana
138
233
type GrafanaStatus struct {
139
- Stage OperatorStageName `json:"stage,omitempty"`
140
- StageStatus OperatorStageStatus `json:"stageStatus,omitempty"`
141
- LastMessage string `json:"lastMessage,omitempty"`
142
- AdminURL string `json:"adminUrl,omitempty"`
143
- AlertRuleGroups NamespacedResourceList `json:"alertRuleGroups,omitempty"`
144
- ContactPoints NamespacedResourceList `json:"contactPoints,omitempty"`
145
- Dashboards NamespacedResourceList `json:"dashboards,omitempty"`
146
- Datasources NamespacedResourceList `json:"datasources,omitempty"`
147
- Folders NamespacedResourceList `json:"folders,omitempty"`
148
- LibraryPanels NamespacedResourceList `json:"libraryPanels,omitempty"`
149
- MuteTimings NamespacedResourceList `json:"muteTimings,omitempty"`
150
- NotificationTemplates NamespacedResourceList `json:"notificationTemplates,omitempty"`
151
- Version string `json:"version,omitempty"`
152
- Conditions []metav1.Condition `json:"conditions,omitempty"`
234
+ Stage OperatorStageName `json:"stage,omitempty"`
235
+ StageStatus OperatorStageStatus `json:"stageStatus,omitempty"`
236
+ LastMessage string `json:"lastMessage,omitempty"`
237
+ AdminURL string `json:"adminUrl,omitempty"`
238
+ AlertRuleGroups NamespacedResourceList `json:"alertRuleGroups,omitempty"`
239
+ ContactPoints NamespacedResourceList `json:"contactPoints,omitempty"`
240
+ Dashboards NamespacedResourceList `json:"dashboards,omitempty"`
241
+ Datasources NamespacedResourceList `json:"datasources,omitempty"`
242
+ Folders NamespacedResourceList `json:"folders,omitempty"`
243
+ LibraryPanels NamespacedResourceList `json:"libraryPanels,omitempty"`
244
+ MuteTimings NamespacedResourceList `json:"muteTimings,omitempty"`
245
+ NotificationTemplates NamespacedResourceList `json:"notificationTemplates,omitempty"`
246
+ Version string `json:"version,omitempty"`
247
+ Conditions []metav1.Condition `json:"conditions,omitempty"`
248
+ GrafanaServiceAccounts []GrafanaServiceAccountStatus `json:"serviceAccounts,omitempty"`
153
249
}
154
250
155
251
// +kubebuilder:object:root=true
0 commit comments