Skip to content

Commit 12800fc

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

File tree

66 files changed

+4914
-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.

66 files changed

+4914
-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: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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+
// +kubebuilder:validation:XValidation:rule="duration(self) > duration('0s')",message="spec.resyncPeriod must be greater than 0"
50+
ResyncPeriod metav1.Duration `json:"resyncPeriod,omitempty"`
51+
52+
// Name of the Grafana instance to create the service account for
53+
// +kubebuilder:validation:Required
54+
// +kubebuilder:validation:MinLength=1
55+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.instanceName is immutable"
56+
InstanceName string `json:"instanceName"`
57+
58+
// Name of the service account in Grafana
59+
// +kubebuilder:validation:Required
60+
// +kubebuilder:validation:MinLength=1
61+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.name is immutable"
62+
Name string `json:"name"`
63+
64+
// Role of the service account (Viewer, Editor, Admin)
65+
// +kubebuilder:validation:Required
66+
// +kubebuilder:validation:Enum=Viewer;Editor;Admin
67+
Role string `json:"role"`
68+
69+
// Whether the service account is disabled
70+
// +optional
71+
// +kubebuilder:default=false
72+
IsDisabled bool `json:"isDisabled,omitempty"`
73+
74+
// Tokens to create for the service account
75+
// +optional
76+
// +listType=map
77+
// +listMapKey=name
78+
Tokens []GrafanaServiceAccountTokenSpec `json:"tokens,omitempty"`
79+
}
80+
81+
// GrafanaServiceAccountSecretStatus describes a Secret created in Kubernetes to store the service account token.
82+
type GrafanaServiceAccountSecretStatus struct {
83+
Namespace string `json:"namespace,omitempty"`
84+
Name string `json:"name,omitempty"`
85+
}
86+
87+
// GrafanaServiceAccountTokenStatus describes a token created in Grafana.
88+
type GrafanaServiceAccountTokenStatus struct {
89+
Name string `json:"name"`
90+
91+
// Expiration time of the token
92+
// N.B. There's possible discrepancy with the expiration time in spec
93+
// It happens because Grafana API accepts TTL in seconds then calculates the expiration time against the current time
94+
Expires *metav1.Time `json:"expires,omitempty"`
95+
96+
// ID of the token in Grafana
97+
ID int64 `json:"id"`
98+
99+
// Name of the secret containing the token
100+
Secret *GrafanaServiceAccountSecretStatus `json:"secret,omitempty"`
101+
}
102+
103+
// GrafanaServiceAccountInfo describes the Grafana service account information.
104+
type GrafanaServiceAccountInfo struct {
105+
Name string `json:"name"`
106+
Login string `json:"login"`
107+
108+
// ID of the service account in Grafana
109+
ID int64 `json:"id"`
110+
111+
// Role is the Grafana role for the service account (Viewer, Editor, Admin)
112+
Role string `json:"role"`
113+
114+
// IsDisabled indicates if the service account is disabled
115+
IsDisabled bool `json:"isDisabled"`
116+
117+
// Information about tokens
118+
// +optional
119+
Tokens []GrafanaServiceAccountTokenStatus `json:"tokens,omitempty"`
120+
}
121+
122+
// GrafanaServiceAccountStatus defines the observed state of a GrafanaServiceAccount
123+
type GrafanaServiceAccountStatus struct {
124+
GrafanaCommonStatus `json:",inline"`
125+
126+
// LastGeneration is the Generation of the last reconciled resource
127+
LastGeneration int64 `json:"lastGeneration"`
128+
129+
// Info contains the Grafana service account information
130+
Account *GrafanaServiceAccountInfo `json:"account,omitempty"`
131+
}
132+
133+
//+kubebuilder:object:root=true
134+
//+kubebuilder:subresource:status
135+
136+
// GrafanaServiceAccount is the Schema for the grafanaserviceaccounts API
137+
// +kubebuilder:printcolumn:name="Instance",type="string",JSONPath=".spec.instanceName",description="Grafana instance name"
138+
// +kubebuilder:printcolumn:name="Name",type="string",JSONPath=".spec.name",description="Service account name"
139+
// +kubebuilder:printcolumn:name="Role",type="string",JSONPath=".spec.role",description="Service account role"
140+
// +kubebuilder:printcolumn:name="Last resync",type="date",format="date-time",JSONPath=".status.lastResync",description=""
141+
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description=""
142+
// +kubebuilder:resource:categories={grafana-operator}
143+
type GrafanaServiceAccount struct {
144+
metav1.TypeMeta `json:",inline"`
145+
metav1.ObjectMeta `json:"metadata,omitempty"`
146+
147+
Spec GrafanaServiceAccountSpec `json:"spec,omitempty"`
148+
Status GrafanaServiceAccountStatus `json:"status,omitempty"`
149+
}
150+
151+
//+kubebuilder:object:root=true
152+
153+
// GrafanaServiceAccountList contains a list of GrafanaServiceAccount
154+
type GrafanaServiceAccountList struct {
155+
metav1.TypeMeta `json:",inline"`
156+
metav1.ListMeta `json:"metadata,omitempty"`
157+
Items []GrafanaServiceAccount `json:"items"`
158+
}
159+
160+
// Find searches for a GrafanaServiceAccount by namespace/name in the list.
161+
func (in *GrafanaServiceAccountList) Find(namespace, name string) *GrafanaServiceAccount {
162+
for i := range in.Items {
163+
if in.Items[i].Namespace == namespace && in.Items[i].Name == name {
164+
return &in.Items[i]
165+
}
166+
}
167+
168+
return nil
169+
}
170+
171+
// MatchNamespace returns the namespace where this service account is defined.
172+
func (in *GrafanaServiceAccount) MatchNamespace() string {
173+
return in.Namespace
174+
}
175+
176+
// AllowCrossNamespace indicates whether cross-namespace import is allowed for this resource.
177+
func (in *GrafanaServiceAccount) AllowCrossNamespace() bool {
178+
// return in.Spec.AllowCrossNamespaceImport
179+
return false
180+
}
181+
182+
func (in *GrafanaServiceAccount) CommonStatus() *GrafanaCommonStatus {
183+
return &in.Status.GrafanaCommonStatus
184+
}
185+
186+
func init() {
187+
SchemeBuilder.Register(&GrafanaServiceAccount{}, &GrafanaServiceAccountList{})
188+
}

0 commit comments

Comments
 (0)