-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implements the metric-gen tool which could get used to create custom resource configurations directly from code, similar to what controller-gen does.
- Loading branch information
Showing
11 changed files
with
1,286 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
module k8s.io/kube-state-metrics/v2/exp/metric-gen | ||
|
||
go 1.19 | ||
|
||
replace k8s.io/kube-state-metrics/v2 => ../.. | ||
|
||
require ( | ||
github.com/spf13/pflag v1.0.5 | ||
k8s.io/apimachinery v0.28.0 | ||
k8s.io/client-go v0.28.0 | ||
k8s.io/klog/v2 v2.100.1 | ||
k8s.io/kube-state-metrics/v2 v2.0.0-00010101000000-000000000000 | ||
k8s.io/utils v0.0.0-20230711102312-30195339c3c7 | ||
sigs.k8s.io/controller-tools v0.13.0 | ||
) | ||
|
||
require ( | ||
github.com/beorn7/perks v1.0.1 // indirect | ||
github.com/blang/semver/v4 v4.0.0 // indirect | ||
github.com/cespare/xxhash/v2 v2.2.0 // indirect | ||
github.com/davecgh/go-spew v1.1.1 // indirect | ||
github.com/dgryski/go-jump v0.0.0-20211018200510-ba001c3ffce0 // indirect | ||
github.com/emicklei/go-restful/v3 v3.9.0 // indirect | ||
github.com/fatih/color v1.15.0 // indirect | ||
github.com/go-logr/logr v1.2.4 // indirect | ||
github.com/go-openapi/jsonpointer v0.19.6 // indirect | ||
github.com/go-openapi/jsonreference v0.20.2 // indirect | ||
github.com/go-openapi/swag v0.22.3 // indirect | ||
github.com/gobuffalo/flect v1.0.2 // indirect | ||
github.com/gogo/protobuf v1.3.2 // indirect | ||
github.com/golang/protobuf v1.5.3 // indirect | ||
github.com/google/gnostic-models v0.6.8 // indirect | ||
github.com/google/go-cmp v0.5.9 // indirect | ||
github.com/google/gofuzz v1.2.0 // indirect | ||
github.com/google/uuid v1.3.0 // indirect | ||
github.com/imdario/mergo v0.3.6 // indirect | ||
github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||
github.com/josharian/intern v1.0.0 // indirect | ||
github.com/json-iterator/go v1.1.12 // indirect | ||
github.com/mailru/easyjson v0.7.7 // indirect | ||
github.com/mattn/go-colorable v0.1.13 // indirect | ||
github.com/mattn/go-isatty v0.0.17 // indirect | ||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect | ||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect | ||
github.com/modern-go/reflect2 v1.0.2 // indirect | ||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | ||
github.com/prometheus/client_golang v1.16.0 // indirect | ||
github.com/prometheus/client_model v0.4.0 // indirect | ||
github.com/prometheus/common v0.44.0 // indirect | ||
github.com/prometheus/procfs v0.10.1 // indirect | ||
github.com/robfig/cron/v3 v3.0.1 // indirect | ||
github.com/spf13/cobra v1.7.0 // indirect | ||
golang.org/x/mod v0.12.0 // indirect | ||
golang.org/x/net v0.14.0 // indirect | ||
golang.org/x/oauth2 v0.8.0 // indirect | ||
golang.org/x/sys v0.11.0 // indirect | ||
golang.org/x/term v0.11.0 // indirect | ||
golang.org/x/text v0.12.0 // indirect | ||
golang.org/x/time v0.3.0 // indirect | ||
golang.org/x/tools v0.12.0 // indirect | ||
google.golang.org/appengine v1.6.7 // indirect | ||
google.golang.org/protobuf v1.30.0 // indirect | ||
gopkg.in/inf.v0 v0.9.1 // indirect | ||
gopkg.in/yaml.v2 v2.4.0 // indirect | ||
gopkg.in/yaml.v3 v3.0.1 // indirect | ||
k8s.io/api v0.28.0 // indirect | ||
k8s.io/apiextensions-apiserver v0.28.0 // indirect | ||
k8s.io/component-base v0.28.0 // indirect | ||
k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect | ||
k8s.io/sample-controller v0.27.4 // indirect | ||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect | ||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect | ||
sigs.k8s.io/yaml v1.3.0 // indirect | ||
) |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/* | ||
Copyright 2023 The Kubernetes Authors All rights reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
|
||
"github.com/spf13/pflag" | ||
"k8s.io/kube-state-metrics/v2/exp/metric-gen/metric" | ||
"sigs.k8s.io/controller-tools/pkg/genall" | ||
"sigs.k8s.io/controller-tools/pkg/genall/help" | ||
prettyhelp "sigs.k8s.io/controller-tools/pkg/genall/help/pretty" | ||
"sigs.k8s.io/controller-tools/pkg/loader" | ||
"sigs.k8s.io/controller-tools/pkg/markers" | ||
) | ||
|
||
const ( | ||
generatorName = "metric" | ||
) | ||
|
||
var ( | ||
// optionsRegistry contains all the marker definitions used to process command line options | ||
optionsRegistry = &markers.Registry{} | ||
) | ||
|
||
func main() { | ||
var whichMarkersFlag bool | ||
|
||
pflag.CommandLine.BoolVarP(&whichMarkersFlag, "which-markers", "w", false, "print out all markers available with the requested generators") | ||
|
||
pflag.Usage = func() { | ||
fmt.Fprintf(os.Stderr, "Usage of %s:\n\n", os.Args[0]) | ||
fmt.Fprintf(os.Stderr, " metric-gen [flags] /path/to/package [/path/to/package]\n\n") | ||
fmt.Fprintf(os.Stderr, "Flags:\n") | ||
pflag.PrintDefaults() | ||
fmt.Fprintf(os.Stderr, "\n") | ||
} | ||
|
||
pflag.Parse() | ||
|
||
// Register the metric generator itself as marker so genall.FromOptions is able to initialize the runtime properly. | ||
// This also registers the markers inside the optionsRegistry so its available to print the marker docs. | ||
metricGenerator := metric.Generator{} | ||
defn := markers.Must(markers.MakeDefinition(generatorName, markers.DescribesPackage, metricGenerator)) | ||
if err := optionsRegistry.Register(defn); err != nil { | ||
panic(err) | ||
} | ||
|
||
if whichMarkersFlag { | ||
printMarkerDocs() | ||
return | ||
} | ||
|
||
// Check if package paths got passed as input parameters. | ||
if len(os.Args[1:]) == 0 { | ||
fmt.Fprint(os.Stderr, "error: Please provide package paths as parameters\n\n") | ||
pflag.Usage() | ||
os.Exit(1) | ||
} | ||
|
||
// Load the passed packages as roots. | ||
roots, err := loader.LoadRoots(os.Args[1:]...) | ||
if err != nil { | ||
fmt.Fprint(os.Stderr, fmt.Sprintf("error: loading packages %v\n", err)) | ||
os.Exit(1) | ||
} | ||
|
||
// Set up the generator runtime using controller-tools and passing our optionsRegistry. | ||
rt, err := genall.FromOptions(optionsRegistry, []string{generatorName}) | ||
if err != nil { | ||
fmt.Fprint(os.Stderr, fmt.Sprintf("error: %v\n", err)) | ||
os.Exit(1) | ||
} | ||
|
||
// Setup the generation context with the loaded roots. | ||
rt.GenerationContext.Roots = roots | ||
// Setup the runtime to output to stdout. | ||
rt.OutputRules = genall.OutputRules{Default: genall.OutputToStdout} | ||
|
||
// Run the generator using the runtime. | ||
if hadErrs := rt.Run(); hadErrs { | ||
fmt.Fprint(os.Stderr, "generator did not run successfully\n") | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
// printMarkerDocs prints out marker help for the given generators specified in | ||
// the rawOptions | ||
func printMarkerDocs() error { | ||
// just grab a registry so we don't lag while trying to load roots | ||
// (like we'd do if we just constructed the full runtime). | ||
reg, err := genall.RegistryFromOptions(optionsRegistry, []string{generatorName}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
helpInfo := help.ByCategory(reg, help.SortByCategory) | ||
|
||
for _, cat := range helpInfo { | ||
if cat.Category == "" { | ||
continue | ||
} | ||
contents := prettyhelp.MarkersDetails(false, cat.Category, cat.Markers) | ||
if err := contents.WriteTo(os.Stderr); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
/* | ||
Copyright 2023 The Kubernetes Authors All rights reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
package metric | ||
|
||
import ( | ||
"fmt" | ||
"sort" | ||
|
||
"k8s.io/klog/v2" | ||
"sigs.k8s.io/controller-tools/pkg/crd" | ||
"sigs.k8s.io/controller-tools/pkg/genall" | ||
"sigs.k8s.io/controller-tools/pkg/loader" | ||
"sigs.k8s.io/controller-tools/pkg/markers" | ||
|
||
"k8s.io/kube-state-metrics/v2/pkg/customresourcestate" | ||
) | ||
|
||
type Generator struct{} | ||
|
||
func (Generator) CheckFilter() loader.NodeFilter { | ||
// Re-use controller-tools filter to filter out unrelated nodes that aren't used | ||
// in CRD generation, like interfaces and struct fields without JSON tag. | ||
return crd.Generator{}.CheckFilter() | ||
} | ||
|
||
func (g Generator) Generate(ctx *genall.GenerationContext) error { | ||
// Create the parser which is specific to the metric generator. | ||
parser := newParser( | ||
&crd.Parser{ | ||
Collector: ctx.Collector, | ||
Checker: ctx.Checker, | ||
}, | ||
) | ||
|
||
// Loop over all passed packages. | ||
for _, root := range ctx.Roots { | ||
// skip packages which don't import metav1 because they can't define a CRD without meta v1. | ||
metav1 := root.Imports()["k8s.io/apimachinery/pkg/apis/meta/v1"] | ||
if metav1 == nil { | ||
continue | ||
} | ||
|
||
// parse the given package to feed crd.FindKubeKinds to find CRD objects. | ||
parser.NeedPackage(root) | ||
kubeKinds := crd.FindKubeKinds(parser.Parser, metav1) | ||
if len(kubeKinds) == 0 { | ||
klog.Fatalf("no objects in the roots") | ||
} | ||
|
||
for _, gv := range kubeKinds { | ||
// Create customresourcestate.Resource for each CRD which contains all metric | ||
// definitions for the CRD. | ||
parser.NeedResourceFor(gv) | ||
} | ||
} | ||
|
||
// Build customresourcestate configuration file from generated data. | ||
metrics := customresourcestate.Metrics{ | ||
Spec: customresourcestate.MetricsSpec{ | ||
Resources: []customresourcestate.Resource{}, | ||
}, | ||
} | ||
|
||
// Sort the resources to get a deterministic output. | ||
|
||
for _, resource := range parser.CustomResourceStates { | ||
if len(resource.Metrics) > 0 { | ||
// sort the metrics | ||
sort.Slice(resource.Metrics, func(i, j int) bool { | ||
return resource.Metrics[i].Name < resource.Metrics[j].Name | ||
}) | ||
|
||
metrics.Spec.Resources = append(metrics.Spec.Resources, resource) | ||
} | ||
} | ||
|
||
sort.Slice(metrics.Spec.Resources, func(i, j int) bool { | ||
if metrics.Spec.Resources[i].MetricNamePrefix == nil && metrics.Spec.Resources[j].MetricNamePrefix == nil { | ||
a := metrics.Spec.Resources[i].GroupVersionKind.Group + "/" + metrics.Spec.Resources[i].GroupVersionKind.Version + "/" + metrics.Spec.Resources[i].GroupVersionKind.Kind | ||
b := metrics.Spec.Resources[j].GroupVersionKind.Group + "/" + metrics.Spec.Resources[j].GroupVersionKind.Version + "/" + metrics.Spec.Resources[j].GroupVersionKind.Kind | ||
return a < b | ||
} | ||
|
||
// Either a or b will not be the empty string, so we can compare them. | ||
var a, b string | ||
if metrics.Spec.Resources[i].MetricNamePrefix == nil { | ||
a = *metrics.Spec.Resources[i].MetricNamePrefix | ||
} | ||
if metrics.Spec.Resources[j].MetricNamePrefix != nil { | ||
b = *metrics.Spec.Resources[j].MetricNamePrefix | ||
} | ||
return a < b | ||
}) | ||
|
||
// Write the rendered yaml to the context which will result in stdout. | ||
filePath := "metrics.yaml" | ||
if err := ctx.WriteYAML(filePath, "", []interface{}{metrics}, genall.WithTransform(addCustomResourceStateKind)); err != nil { | ||
return fmt.Errorf("WriteYAML to %s: %w", filePath, err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// addCustomResourceStateKind adds the correct kind because we don't have a correct | ||
// kubernetes-style object as configuration definition. | ||
func addCustomResourceStateKind(obj map[string]interface{}) error { | ||
obj["kind"] = "CustomResourceStateMetrics" | ||
return nil | ||
} | ||
|
||
func (g Generator) RegisterMarkers(into *markers.Registry) error { | ||
for _, m := range markerDefinitions { | ||
if err := m.Register(into); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.