Skip to content

Commit 079dbac

Browse files
committed
feat(Grafana): Manage Grafana Service Accounts
1 parent 8ecc3a0 commit 079dbac

File tree

75 files changed

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

75 files changed

+5091
-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+
// +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+
// Suspend pauses reconciliation of the service account
53+
// +optional
54+
// +kubebuilder:default=false
55+
Suspend bool `json:"suspend,omitempty"`
56+
57+
// Name of the Grafana instance to create the service account for
58+
// +kubebuilder:validation:Required
59+
// +kubebuilder:validation:MinLength=1
60+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.instanceName is immutable"
61+
InstanceName string `json:"instanceName"`
62+
63+
// Name of the service account in Grafana
64+
// +kubebuilder:validation:Required
65+
// +kubebuilder:validation:MinLength=1
66+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="spec.name is immutable"
67+
Name string `json:"name"`
68+
69+
// Role of the service account (Viewer, Editor, Admin)
70+
// +kubebuilder:validation:Required
71+
// +kubebuilder:validation:Enum=Viewer;Editor;Admin
72+
Role string `json:"role"`
73+
74+
// Whether the service account is disabled
75+
// +optional
76+
// +kubebuilder:default=false
77+
IsDisabled bool `json:"isDisabled,omitempty"`
78+
79+
// Tokens to create for the service account
80+
// +optional
81+
// +listType=map
82+
// +listMapKey=name
83+
Tokens []GrafanaServiceAccountTokenSpec `json:"tokens,omitempty"`
84+
}
85+
86+
// GrafanaServiceAccountSecretStatus describes a Secret created in Kubernetes to store the service account token.
87+
type GrafanaServiceAccountSecretStatus struct {
88+
Namespace string `json:"namespace,omitempty"`
89+
Name string `json:"name,omitempty"`
90+
}
91+
92+
// GrafanaServiceAccountTokenStatus describes a token created in Grafana.
93+
type GrafanaServiceAccountTokenStatus struct {
94+
Name string `json:"name"`
95+
96+
// Expiration time of the token
97+
// N.B. There's possible discrepancy with the expiration time in spec
98+
// It happens because Grafana API accepts TTL in seconds then calculates the expiration time against the current time
99+
Expires *metav1.Time `json:"expires,omitempty"`
100+
101+
// ID of the token in Grafana
102+
ID int64 `json:"id"`
103+
104+
// Name of the secret containing the token
105+
Secret *GrafanaServiceAccountSecretStatus `json:"secret,omitempty"`
106+
}
107+
108+
// GrafanaServiceAccountInfo describes the Grafana service account information.
109+
type GrafanaServiceAccountInfo struct {
110+
Name string `json:"name"`
111+
Login string `json:"login"`
112+
113+
// ID of the service account in Grafana
114+
ID int64 `json:"id"`
115+
116+
// Role is the Grafana role for the service account (Viewer, Editor, Admin)
117+
Role string `json:"role"`
118+
119+
// IsDisabled indicates if the service account is disabled
120+
IsDisabled bool `json:"isDisabled"`
121+
122+
// Information about tokens
123+
// +optional
124+
Tokens []GrafanaServiceAccountTokenStatus `json:"tokens,omitempty"`
125+
}
126+
127+
// GrafanaServiceAccountStatus defines the observed state of a GrafanaServiceAccount
128+
type GrafanaServiceAccountStatus struct {
129+
GrafanaCommonStatus `json:",inline"`
130+
131+
// Info contains the Grafana service account information
132+
Account *GrafanaServiceAccountInfo `json:"account,omitempty"`
133+
}
134+
135+
//+kubebuilder:object:root=true
136+
//+kubebuilder:subresource:status
137+
138+
// GrafanaServiceAccount is the Schema for the grafanaserviceaccounts API
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 i := range in.Items {
162+
if in.Items[i].Namespace == namespace && in.Items[i].Name == name {
163+
return &in.Items[i]
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)