Skip to content

Commit 6f0d7a4

Browse files
committed
Implement metric-gen tool
Implements the metric-gen tool which could get used to create custom resource configurations directly from code, similar to what controller-gen does.
1 parent e3dd5ff commit 6f0d7a4

File tree

11 files changed

+1286
-7
lines changed

11 files changed

+1286
-7
lines changed

exp/metric-gen/go.mod

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
module k8s.io/kube-state-metrics/v2/exp/metric-gen
2+
3+
go 1.19
4+
5+
replace k8s.io/kube-state-metrics/v2 => ../..
6+
7+
require (
8+
github.com/spf13/pflag v1.0.5
9+
k8s.io/apimachinery v0.28.0
10+
k8s.io/client-go v0.28.0
11+
k8s.io/klog/v2 v2.100.1
12+
k8s.io/kube-state-metrics/v2 v2.0.0-00010101000000-000000000000
13+
k8s.io/utils v0.0.0-20230711102312-30195339c3c7
14+
sigs.k8s.io/controller-tools v0.13.0
15+
)
16+
17+
require (
18+
github.com/beorn7/perks v1.0.1 // indirect
19+
github.com/blang/semver/v4 v4.0.0 // indirect
20+
github.com/cespare/xxhash/v2 v2.2.0 // indirect
21+
github.com/davecgh/go-spew v1.1.1 // indirect
22+
github.com/dgryski/go-jump v0.0.0-20211018200510-ba001c3ffce0 // indirect
23+
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
24+
github.com/fatih/color v1.15.0 // indirect
25+
github.com/go-logr/logr v1.2.4 // indirect
26+
github.com/go-openapi/jsonpointer v0.19.6 // indirect
27+
github.com/go-openapi/jsonreference v0.20.2 // indirect
28+
github.com/go-openapi/swag v0.22.3 // indirect
29+
github.com/gobuffalo/flect v1.0.2 // indirect
30+
github.com/gogo/protobuf v1.3.2 // indirect
31+
github.com/golang/protobuf v1.5.3 // indirect
32+
github.com/google/gnostic-models v0.6.8 // indirect
33+
github.com/google/go-cmp v0.5.9 // indirect
34+
github.com/google/gofuzz v1.2.0 // indirect
35+
github.com/google/uuid v1.3.0 // indirect
36+
github.com/imdario/mergo v0.3.6 // indirect
37+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
38+
github.com/josharian/intern v1.0.0 // indirect
39+
github.com/json-iterator/go v1.1.12 // indirect
40+
github.com/mailru/easyjson v0.7.7 // indirect
41+
github.com/mattn/go-colorable v0.1.13 // indirect
42+
github.com/mattn/go-isatty v0.0.17 // indirect
43+
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
44+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
45+
github.com/modern-go/reflect2 v1.0.2 // indirect
46+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
47+
github.com/prometheus/client_golang v1.16.0 // indirect
48+
github.com/prometheus/client_model v0.4.0 // indirect
49+
github.com/prometheus/common v0.44.0 // indirect
50+
github.com/prometheus/procfs v0.10.1 // indirect
51+
github.com/robfig/cron/v3 v3.0.1 // indirect
52+
github.com/spf13/cobra v1.7.0 // indirect
53+
golang.org/x/mod v0.12.0 // indirect
54+
golang.org/x/net v0.14.0 // indirect
55+
golang.org/x/oauth2 v0.8.0 // indirect
56+
golang.org/x/sys v0.11.0 // indirect
57+
golang.org/x/term v0.11.0 // indirect
58+
golang.org/x/text v0.12.0 // indirect
59+
golang.org/x/time v0.3.0 // indirect
60+
golang.org/x/tools v0.12.0 // indirect
61+
google.golang.org/appengine v1.6.7 // indirect
62+
google.golang.org/protobuf v1.30.0 // indirect
63+
gopkg.in/inf.v0 v0.9.1 // indirect
64+
gopkg.in/yaml.v2 v2.4.0 // indirect
65+
gopkg.in/yaml.v3 v3.0.1 // indirect
66+
k8s.io/api v0.28.0 // indirect
67+
k8s.io/apiextensions-apiserver v0.28.0 // indirect
68+
k8s.io/component-base v0.28.0 // indirect
69+
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect
70+
k8s.io/sample-controller v0.27.4 // indirect
71+
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
72+
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
73+
sigs.k8s.io/yaml v1.3.0 // indirect
74+
)

exp/metric-gen/go.sum

Lines changed: 204 additions & 0 deletions
Large diffs are not rendered by default.

exp/metric-gen/main.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
Copyright 2023 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+
package main
17+
18+
import (
19+
"fmt"
20+
"os"
21+
22+
"github.com/spf13/pflag"
23+
"k8s.io/kube-state-metrics/v2/exp/metric-gen/metric"
24+
"sigs.k8s.io/controller-tools/pkg/genall"
25+
"sigs.k8s.io/controller-tools/pkg/genall/help"
26+
prettyhelp "sigs.k8s.io/controller-tools/pkg/genall/help/pretty"
27+
"sigs.k8s.io/controller-tools/pkg/loader"
28+
"sigs.k8s.io/controller-tools/pkg/markers"
29+
)
30+
31+
const (
32+
generatorName = "metric"
33+
)
34+
35+
var (
36+
// optionsRegistry contains all the marker definitions used to process command line options
37+
optionsRegistry = &markers.Registry{}
38+
)
39+
40+
func main() {
41+
var whichMarkersFlag bool
42+
43+
pflag.CommandLine.BoolVarP(&whichMarkersFlag, "which-markers", "w", false, "print out all markers available with the requested generators")
44+
45+
pflag.Usage = func() {
46+
fmt.Fprintf(os.Stderr, "Usage of %s:\n\n", os.Args[0])
47+
fmt.Fprintf(os.Stderr, " metric-gen [flags] /path/to/package [/path/to/package]\n\n")
48+
fmt.Fprintf(os.Stderr, "Flags:\n")
49+
pflag.PrintDefaults()
50+
fmt.Fprintf(os.Stderr, "\n")
51+
}
52+
53+
pflag.Parse()
54+
55+
// Register the metric generator itself as marker so genall.FromOptions is able to initialize the runtime properly.
56+
// This also registers the markers inside the optionsRegistry so its available to print the marker docs.
57+
metricGenerator := metric.Generator{}
58+
defn := markers.Must(markers.MakeDefinition(generatorName, markers.DescribesPackage, metricGenerator))
59+
if err := optionsRegistry.Register(defn); err != nil {
60+
panic(err)
61+
}
62+
63+
if whichMarkersFlag {
64+
printMarkerDocs()
65+
return
66+
}
67+
68+
// Check if package paths got passed as input parameters.
69+
if len(os.Args[1:]) == 0 {
70+
fmt.Fprint(os.Stderr, "error: Please provide package paths as parameters\n\n")
71+
pflag.Usage()
72+
os.Exit(1)
73+
}
74+
75+
// Load the passed packages as roots.
76+
roots, err := loader.LoadRoots(os.Args[1:]...)
77+
if err != nil {
78+
fmt.Fprint(os.Stderr, fmt.Sprintf("error: loading packages %v\n", err))
79+
os.Exit(1)
80+
}
81+
82+
// Set up the generator runtime using controller-tools and passing our optionsRegistry.
83+
rt, err := genall.FromOptions(optionsRegistry, []string{generatorName})
84+
if err != nil {
85+
fmt.Fprint(os.Stderr, fmt.Sprintf("error: %v\n", err))
86+
os.Exit(1)
87+
}
88+
89+
// Setup the generation context with the loaded roots.
90+
rt.GenerationContext.Roots = roots
91+
// Setup the runtime to output to stdout.
92+
rt.OutputRules = genall.OutputRules{Default: genall.OutputToStdout}
93+
94+
// Run the generator using the runtime.
95+
if hadErrs := rt.Run(); hadErrs {
96+
fmt.Fprint(os.Stderr, "generator did not run successfully\n")
97+
os.Exit(1)
98+
}
99+
}
100+
101+
// printMarkerDocs prints out marker help for the given generators specified in
102+
// the rawOptions
103+
func printMarkerDocs() error {
104+
// just grab a registry so we don't lag while trying to load roots
105+
// (like we'd do if we just constructed the full runtime).
106+
reg, err := genall.RegistryFromOptions(optionsRegistry, []string{generatorName})
107+
if err != nil {
108+
return err
109+
}
110+
111+
helpInfo := help.ByCategory(reg, help.SortByCategory)
112+
113+
for _, cat := range helpInfo {
114+
if cat.Category == "" {
115+
continue
116+
}
117+
contents := prettyhelp.MarkersDetails(false, cat.Category, cat.Markers)
118+
if err := contents.WriteTo(os.Stderr); err != nil {
119+
return err
120+
}
121+
}
122+
return nil
123+
}

exp/metric-gen/metric/generator.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
Copyright 2023 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+
package metric
17+
18+
import (
19+
"fmt"
20+
"sort"
21+
22+
"k8s.io/klog/v2"
23+
"sigs.k8s.io/controller-tools/pkg/crd"
24+
"sigs.k8s.io/controller-tools/pkg/genall"
25+
"sigs.k8s.io/controller-tools/pkg/loader"
26+
"sigs.k8s.io/controller-tools/pkg/markers"
27+
28+
"k8s.io/kube-state-metrics/v2/pkg/customresourcestate"
29+
)
30+
31+
type Generator struct{}
32+
33+
func (Generator) CheckFilter() loader.NodeFilter {
34+
// Re-use controller-tools filter to filter out unrelated nodes that aren't used
35+
// in CRD generation, like interfaces and struct fields without JSON tag.
36+
return crd.Generator{}.CheckFilter()
37+
}
38+
39+
func (g Generator) Generate(ctx *genall.GenerationContext) error {
40+
// Create the parser which is specific to the metric generator.
41+
parser := newParser(
42+
&crd.Parser{
43+
Collector: ctx.Collector,
44+
Checker: ctx.Checker,
45+
},
46+
)
47+
48+
// Loop over all passed packages.
49+
for _, root := range ctx.Roots {
50+
// skip packages which don't import metav1 because they can't define a CRD without meta v1.
51+
metav1 := root.Imports()["k8s.io/apimachinery/pkg/apis/meta/v1"]
52+
if metav1 == nil {
53+
continue
54+
}
55+
56+
// parse the given package to feed crd.FindKubeKinds to find CRD objects.
57+
parser.NeedPackage(root)
58+
kubeKinds := crd.FindKubeKinds(parser.Parser, metav1)
59+
if len(kubeKinds) == 0 {
60+
klog.Fatalf("no objects in the roots")
61+
}
62+
63+
for _, gv := range kubeKinds {
64+
// Create customresourcestate.Resource for each CRD which contains all metric
65+
// definitions for the CRD.
66+
parser.NeedResourceFor(gv)
67+
}
68+
}
69+
70+
// Build customresourcestate configuration file from generated data.
71+
metrics := customresourcestate.Metrics{
72+
Spec: customresourcestate.MetricsSpec{
73+
Resources: []customresourcestate.Resource{},
74+
},
75+
}
76+
77+
// Sort the resources to get a deterministic output.
78+
79+
for _, resource := range parser.CustomResourceStates {
80+
if len(resource.Metrics) > 0 {
81+
// sort the metrics
82+
sort.Slice(resource.Metrics, func(i, j int) bool {
83+
return resource.Metrics[i].Name < resource.Metrics[j].Name
84+
})
85+
86+
metrics.Spec.Resources = append(metrics.Spec.Resources, resource)
87+
}
88+
}
89+
90+
sort.Slice(metrics.Spec.Resources, func(i, j int) bool {
91+
if metrics.Spec.Resources[i].MetricNamePrefix == nil && metrics.Spec.Resources[j].MetricNamePrefix == nil {
92+
a := metrics.Spec.Resources[i].GroupVersionKind.Group + "/" + metrics.Spec.Resources[i].GroupVersionKind.Version + "/" + metrics.Spec.Resources[i].GroupVersionKind.Kind
93+
b := metrics.Spec.Resources[j].GroupVersionKind.Group + "/" + metrics.Spec.Resources[j].GroupVersionKind.Version + "/" + metrics.Spec.Resources[j].GroupVersionKind.Kind
94+
return a < b
95+
}
96+
97+
// Either a or b will not be the empty string, so we can compare them.
98+
var a, b string
99+
if metrics.Spec.Resources[i].MetricNamePrefix == nil {
100+
a = *metrics.Spec.Resources[i].MetricNamePrefix
101+
}
102+
if metrics.Spec.Resources[j].MetricNamePrefix != nil {
103+
b = *metrics.Spec.Resources[j].MetricNamePrefix
104+
}
105+
return a < b
106+
})
107+
108+
// Write the rendered yaml to the context which will result in stdout.
109+
filePath := "metrics.yaml"
110+
if err := ctx.WriteYAML(filePath, "", []interface{}{metrics}, genall.WithTransform(addCustomResourceStateKind)); err != nil {
111+
return fmt.Errorf("WriteYAML to %s: %w", filePath, err)
112+
}
113+
114+
return nil
115+
}
116+
117+
// addCustomResourceStateKind adds the correct kind because we don't have a correct
118+
// kubernetes-style object as configuration definition.
119+
func addCustomResourceStateKind(obj map[string]interface{}) error {
120+
obj["kind"] = "CustomResourceStateMetrics"
121+
return nil
122+
}
123+
124+
func (g Generator) RegisterMarkers(into *markers.Registry) error {
125+
for _, m := range markerDefinitions {
126+
if err := m.Register(into); err != nil {
127+
return err
128+
}
129+
}
130+
131+
return nil
132+
}

0 commit comments

Comments
 (0)