Skip to content

Commit 294b2b6

Browse files
committed
metrics: implement generator and parser for custom resource configuration and a clusterrole
1 parent ffe4112 commit 294b2b6

File tree

3 files changed

+460
-0
lines changed

3 files changed

+460
-0
lines changed

cmd/controller-gen/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"sigs.k8s.io/controller-tools/pkg/genall/help"
3131
prettyhelp "sigs.k8s.io/controller-tools/pkg/genall/help/pretty"
3232
"sigs.k8s.io/controller-tools/pkg/markers"
33+
"sigs.k8s.io/controller-tools/pkg/metrics"
3334
"sigs.k8s.io/controller-tools/pkg/rbac"
3435
"sigs.k8s.io/controller-tools/pkg/schemapatcher"
3536
"sigs.k8s.io/controller-tools/pkg/version"
@@ -53,6 +54,7 @@ var (
5354
"object": deepcopy.Generator{},
5455
"webhook": webhook.Generator{},
5556
"schemapatch": schemapatcher.Generator{},
57+
"metrics": metrics.Generator{},
5658
}
5759

5860
// allOutputRules defines the list of all known output rules, giving

pkg/metrics/generator.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors All rights reserved.
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 metrics contain libraries for generating custom resource metrics configurations
18+
// for kube-state-metrics from metrics markers in Go source files.
19+
package metrics
20+
21+
import (
22+
"fmt"
23+
"sort"
24+
"strings"
25+
26+
"github.com/gobuffalo/flect"
27+
rbacv1 "k8s.io/api/rbac/v1"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
30+
"sigs.k8s.io/controller-tools/pkg/crd"
31+
"sigs.k8s.io/controller-tools/pkg/genall"
32+
"sigs.k8s.io/controller-tools/pkg/loader"
33+
ctrlmarkers "sigs.k8s.io/controller-tools/pkg/markers"
34+
"sigs.k8s.io/controller-tools/pkg/metrics/internal/config"
35+
"sigs.k8s.io/controller-tools/pkg/metrics/markers"
36+
"sigs.k8s.io/controller-tools/pkg/rbac"
37+
"sigs.k8s.io/controller-tools/pkg/version"
38+
)
39+
40+
// Generator generates kube-state-metrics custom resource configuration files.
41+
type Generator struct{}
42+
43+
var _ genall.Generator = &Generator{}
44+
var _ genall.NeedsTypeChecking = &Generator{}
45+
46+
// RegisterMarkers registers all markers needed by this Generator
47+
// into the given registry.
48+
func (g Generator) RegisterMarkers(into *ctrlmarkers.Registry) error {
49+
for _, m := range markers.MarkerDefinitions {
50+
if err := m.Register(into); err != nil {
51+
return err
52+
}
53+
}
54+
55+
return nil
56+
}
57+
58+
const headerText = `# Generated by controller-gen version %s
59+
# Generated based on types for kube-state-metrics %s
60+
`
61+
62+
// Generate generates artifacts produced by this marker.
63+
// It's called after RegisterMarkers has been called.
64+
func (g Generator) Generate(ctx *genall.GenerationContext) error {
65+
// Create the parser which is specific to the metric generator.
66+
parser := newParser(
67+
&crd.Parser{
68+
Collector: ctx.Collector,
69+
Checker: ctx.Checker,
70+
},
71+
)
72+
73+
// Loop over all passed packages.
74+
for _, pkg := range ctx.Roots {
75+
// skip packages which don't import metav1 because they can't define a CRD without meta v1.
76+
metav1 := pkg.Imports()["k8s.io/apimachinery/pkg/apis/meta/v1"]
77+
if metav1 == nil {
78+
continue
79+
}
80+
81+
// parse the given package to feed crd.FindKubeKinds with Kubernetes Objects.
82+
parser.NeedPackage(pkg)
83+
84+
kubeKinds := crd.FindKubeKinds(parser.Parser, metav1)
85+
if len(kubeKinds) == 0 {
86+
// no objects in the roots
87+
return nil
88+
}
89+
90+
// Create metrics for all Custom Resources in this package.
91+
// This creates the customresourcestate.Resource object which contains all metric
92+
// definitions for the Custom Resource, if it is part of the package.
93+
for _, gv := range kubeKinds {
94+
if err := parser.NeedResourceFor(pkg, gv); err != nil {
95+
return err
96+
}
97+
}
98+
}
99+
100+
// Initialize empty customresourcestate configuration file and fill it with the
101+
// customresourcestate.Resource objects from the parser.
102+
metrics := config.Metrics{
103+
Spec: config.MetricsSpec{
104+
Resources: []config.Resource{},
105+
},
106+
}
107+
108+
rules := []*rbac.Rule{}
109+
110+
for _, resource := range parser.CustomResourceStates {
111+
if resource == nil {
112+
continue
113+
}
114+
if len(resource.Metrics) > 0 {
115+
// Sort the metrics to get a deterministic output.
116+
sort.Slice(resource.Metrics, func(i, j int) bool {
117+
return resource.Metrics[i].Name < resource.Metrics[j].Name
118+
})
119+
120+
metrics.Spec.Resources = append(metrics.Spec.Resources, *resource)
121+
122+
rules = append(rules, &rbac.Rule{
123+
Groups: []string{resource.GroupVersionKind.Group},
124+
Resources: []string{strings.ToLower(flect.Pluralize(resource.GroupVersionKind.Kind))},
125+
Verbs: []string{"get", "list", "watch"},
126+
})
127+
}
128+
}
129+
130+
// Sort the resources by GVK to get a deterministic output.
131+
sort.Slice(metrics.Spec.Resources, func(i, j int) bool {
132+
a := metrics.Spec.Resources[i].GroupVersionKind.String()
133+
b := metrics.Spec.Resources[j].GroupVersionKind.String()
134+
return a < b
135+
})
136+
137+
header := fmt.Sprintf(headerText, version.Version(), config.KubeStateMetricsVersion)
138+
139+
// Write the rendered yaml to the context which will result in stdout.
140+
virtualFilePath := "metrics.yaml"
141+
if err := ctx.WriteYAML(virtualFilePath, header, []interface{}{metrics}, genall.WithTransform(addCustomResourceStateKind)); err != nil {
142+
return fmt.Errorf("WriteYAML to %s: %w", virtualFilePath, err)
143+
}
144+
145+
clusterRole := rbacv1.ClusterRole{
146+
TypeMeta: metav1.TypeMeta{
147+
Kind: "ClusterRole",
148+
APIVersion: rbacv1.SchemeGroupVersion.String(),
149+
},
150+
ObjectMeta: metav1.ObjectMeta{
151+
Name: "manager-metrics-role",
152+
Labels: map[string]string{
153+
"kube-state-metrics/aggregate-to-manager": "true",
154+
},
155+
},
156+
Rules: rbac.NormalizeRules(rules),
157+
}
158+
159+
virtualFilePath = "rbac.yaml"
160+
if err := ctx.WriteYAML(virtualFilePath, "", []interface{}{clusterRole}, genall.WithTransform(genall.TransformRemoveCreationTimestamp)); err != nil {
161+
return fmt.Errorf("WriteYAML to %s: %w", virtualFilePath, err)
162+
}
163+
164+
return nil
165+
}
166+
167+
// CheckFilter indicates the loader.NodeFilter (if any) that should be used
168+
// to prune out unused types/packages when type-checking (nodes for which
169+
// the filter returns true are considered "interesting"). This filter acts
170+
// as a baseline -- all types the pass through this filter will be checked,
171+
// but more than that may also be checked due to other generators' filters.
172+
func (Generator) CheckFilter() loader.NodeFilter {
173+
// Re-use controller-tools filter to filter out unrelated nodes that aren't used
174+
// in CRD generation, like interfaces and struct fields without JSON tag.
175+
return crd.Generator{}.CheckFilter()
176+
}
177+
178+
// addCustomResourceStateKind adds the correct kind because we don't have a correct
179+
// kubernetes-style object as configuration definition.
180+
func addCustomResourceStateKind(obj map[string]interface{}) error {
181+
obj["kind"] = "CustomResourceStateMetrics"
182+
return nil
183+
}

0 commit comments

Comments
 (0)