Skip to content

Commit e2c8577

Browse files
committed
feat(Grafana): Manage Grafana Service Accounts
1 parent 60b98bb commit e2c8577

File tree

63 files changed

+4695
-4
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+4695
-4
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ vendor
2727

2828
.vscode/
2929
.DS_Store
30+
.mirrord
3031

3132
# Audit lab
3233
kube-apiserver-audit.log

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,11 @@ deploy-chainsaw: $(KUSTOMIZE) manifests ## Deploy controller to the K8s cluster
211211
$(info $(M) running $@)
212212
$(KUSTOMIZE) build deploy/kustomize/overlays/chainsaw | kubectl apply --server-side --force-conflicts -f -
213213

214+
.PHONY: deploy-chainsaw-debug
215+
deploy-chainsaw-debug: $(KUSTOMIZE) manifests ## Deploy debug controller (http-echo) to the K8s cluster for mirrord (https://github.com/metalbear-co/mirrord) debugging.
216+
$(info $(M) running $@)
217+
$(KUSTOMIZE) build deploy/kustomize/overlays/chainsaw-debug | kubectl apply --server-side --force-conflicts -f -
218+
214219
.PHONY: undeploy
215220
undeploy: $(KUSTOMIZE) ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
216221
$(info $(M) running $@)

api/v1beta1/grafana_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ type GrafanaStatus struct {
153153
ContactPoints NamespacedResourceList `json:"contactPoints,omitempty"`
154154
Dashboards NamespacedResourceList `json:"dashboards,omitempty"`
155155
Datasources NamespacedResourceList `json:"datasources,omitempty"`
156+
ServiceAccounts NamespacedResourceList `json:"serviceaccounts,omitempty"`
156157
Folders NamespacedResourceList `json:"folders,omitempty"`
157158
LibraryPanels NamespacedResourceList `json:"libraryPanels,omitempty"`
158159
MuteTimings NamespacedResourceList `json:"muteTimings,omitempty"`
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/*
2+
Copyright 2025.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1beta1
18+
19+
import (
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
)
22+
23+
// GrafanaServiceAccountTokenSpec defines a token for a service account
24+
type GrafanaServiceAccountTokenSpec struct {
25+
// Name of the token
26+
// +kubebuilder:validation:Required
27+
// +kubebuilder:validation:MinLength=1
28+
Name string `json:"name"`
29+
30+
// Expiration date of the token. If not set, the token never expires
31+
// +optional
32+
// +kubebuilder:validation:Type=string
33+
// +kubebuilder:validation:Format=date-time
34+
Expires *metav1.Time `json:"expires,omitempty"`
35+
36+
// Name of the secret to store the token. If not set, a name will be generated
37+
// +optional
38+
// +kubebuilder:validation:MinLength=1
39+
SecretName string `json:"secretName,omitempty"`
40+
}
41+
42+
// GrafanaServiceAccountSpec defines the desired state of a GrafanaServiceAccount.
43+
type GrafanaServiceAccountSpec struct {
44+
// How often the resource is synced, defaults to 10m0s if not set
45+
// +optional
46+
// +kubebuilder:validation:Type=string
47+
// +kubebuilder:validation:Pattern="^([0-9]+(\\.[0-9]+)?(ns|us|µs|ms|s|m|h))+$"
48+
// +kubebuilder:default="10m0s"
49+
ResyncPeriod metav1.Duration `json:"resyncPeriod,omitempty"`
50+
51+
// Name of the Grafana instance to create the service account for
52+
// +kubebuilder:validation:Required
53+
// +kubebuilder:validation:MinLength=1
54+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.instanceName is immutable"
55+
InstanceName string `json:"instanceName"`
56+
57+
// Name of the service account in Grafana
58+
// +kubebuilder:validation:Required
59+
// +kubebuilder:validation:MinLength=1
60+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.name is immutable"
61+
Name string `json:"name"`
62+
63+
// Role of the service account (Viewer, Editor, Admin)
64+
// +kubebuilder:validation:Required
65+
// +kubebuilder:validation:Enum=Viewer;Editor;Admin
66+
Role string `json:"role"`
67+
68+
// Whether the service account is disabled
69+
// +optional
70+
// +kubebuilder:default=false
71+
IsDisabled bool `json:"isDisabled,omitempty"`
72+
73+
// Tokens to create for the service account
74+
// +optional
75+
// +listType=map
76+
// +listMapKey=name
77+
Tokens []GrafanaServiceAccountTokenSpec `json:"tokens,omitempty"`
78+
}
79+
80+
// GrafanaServiceAccountSecretStatus describes a Secret created in Kubernetes to store the service account token.
81+
type GrafanaServiceAccountSecretStatus struct {
82+
Namespace string `json:"namespace,omitempty"`
83+
Name string `json:"name,omitempty"`
84+
}
85+
86+
// GrafanaServiceAccountTokenStatus describes a token created in Grafana.
87+
type GrafanaServiceAccountTokenStatus struct {
88+
Name string `json:"name"`
89+
90+
// Expiration time of the token
91+
// N.B. There's possible discrepancy with the expiration time in spec
92+
// It happens because Grafana API accepts TTL in seconds then calculates the expiration time against the current time
93+
Expires *metav1.Time `json:"expires,omitempty"`
94+
95+
// ID of the token in Grafana
96+
ID int64 `json:"id"`
97+
98+
// Name of the secret containing the token
99+
Secret *GrafanaServiceAccountSecretStatus `json:"secret,omitempty"`
100+
}
101+
102+
// GrafanaServiceAccountInfo describes the Grafana service account information.
103+
type GrafanaServiceAccountInfo struct {
104+
Name string `json:"name"`
105+
Login string `json:"login"`
106+
107+
// ID of the service account in Grafana
108+
ID int64 `json:"id"`
109+
110+
// Role is the Grafana role for the service account (Viewer, Editor, Admin)
111+
Role string `json:"role"`
112+
113+
// IsDisabled indicates if the service account is disabled
114+
IsDisabled bool `json:"isDisabled"`
115+
116+
// Information about tokens
117+
// +optional
118+
Tokens []GrafanaServiceAccountTokenStatus `json:"tokens,omitempty"`
119+
}
120+
121+
// GrafanaServiceAccountStatus defines the observed state of a GrafanaServiceAccount
122+
type GrafanaServiceAccountStatus struct {
123+
GrafanaCommonStatus `json:",inline"`
124+
125+
// LastGeneration is the Generation of the last reconciled resource
126+
LastGeneration int64 `json:"lastGeneration"`
127+
128+
// Info contains the Grafana service account information
129+
Account *GrafanaServiceAccountInfo `json:"account,omitempty"`
130+
}
131+
132+
//+kubebuilder:object:root=true
133+
//+kubebuilder:subresource:status
134+
135+
// GrafanaServiceAccount is the Schema for the grafanaserviceaccounts API
136+
// +kubebuilder:printcolumn:name="Instance",type="string",JSONPath=".spec.instanceName",description="Grafana instance name"
137+
// +kubebuilder:printcolumn:name="Name",type="string",JSONPath=".spec.name",description="Service account name"
138+
// +kubebuilder:printcolumn:name="Role",type="string",JSONPath=".spec.role",description="Service account role"
139+
// +kubebuilder:printcolumn:name="Last resync",type="date",format="date-time",JSONPath=".status.lastResync",description=""
140+
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
141+
// +kubebuilder:resource:categories={grafana-operator}
142+
type GrafanaServiceAccount struct {
143+
metav1.TypeMeta `json:",inline"`
144+
metav1.ObjectMeta `json:"metadata,omitempty"`
145+
146+
Spec GrafanaServiceAccountSpec `json:"spec,omitempty"`
147+
Status GrafanaServiceAccountStatus `json:"status,omitempty"`
148+
}
149+
150+
//+kubebuilder:object:root=true
151+
152+
// GrafanaServiceAccountList contains a list of GrafanaServiceAccount
153+
type GrafanaServiceAccountList struct {
154+
metav1.TypeMeta `json:",inline"`
155+
metav1.ListMeta `json:"metadata,omitempty"`
156+
Items []GrafanaServiceAccount `json:"items"`
157+
}
158+
159+
// Find searches for a GrafanaServiceAccount by namespace/name in the list.
160+
func (in *GrafanaServiceAccountList) Find(namespace, name string) *GrafanaServiceAccount {
161+
for _, serviceAccount := range in.Items {
162+
if serviceAccount.Namespace == namespace && serviceAccount.Name == name {
163+
return &serviceAccount
164+
}
165+
}
166+
167+
return nil
168+
}
169+
170+
// MatchNamespace returns the namespace where this service account is defined.
171+
func (in *GrafanaServiceAccount) MatchNamespace() string {
172+
return in.Namespace
173+
}
174+
175+
// AllowCrossNamespace indicates whether cross-namespace import is allowed for this resource.
176+
func (in *GrafanaServiceAccount) AllowCrossNamespace() bool {
177+
// return in.Spec.AllowCrossNamespaceImport
178+
return false
179+
}
180+
181+
func (in *GrafanaServiceAccount) CommonStatus() *GrafanaCommonStatus {
182+
return &in.Status.GrafanaCommonStatus
183+
}
184+
185+
func init() {
186+
SchemeBuilder.Register(&GrafanaServiceAccount{}, &GrafanaServiceAccountList{})
187+
}

0 commit comments

Comments
 (0)