diff --git a/pkg/resource-handler/controller/metadata/doc.go b/pkg/resource-handler/controller/metadata/doc.go new file mode 100644 index 00000000..ce51dddf --- /dev/null +++ b/pkg/resource-handler/controller/metadata/doc.go @@ -0,0 +1,6 @@ +// Package metadata provides utilities for building Kubernetes resource metadata +// such as labels, annotations, and owner references used across all Multigres components. +// +// This package contains generic, reusable functions that are component-agnostic. +// Component-specific logic should be provided by callers through function parameters. +package metadata diff --git a/pkg/resource-handler/controller/metadata/labels.go b/pkg/resource-handler/controller/metadata/labels.go new file mode 100644 index 00000000..74c7cb5b --- /dev/null +++ b/pkg/resource-handler/controller/metadata/labels.go @@ -0,0 +1,106 @@ +package metadata + +import "maps" + +// Standard Kubernetes label keys following kubernetes.io conventions. +// +// See: https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/ +const ( + // LabelAppName is the standard label key for the application name. + LabelAppName = "app.kubernetes.io/name" + + // LabelAppInstance is the standard label key for the unique instance name. + LabelAppInstance = "app.kubernetes.io/instance" + + // LabelAppVersion is the standard label key for the application version. + LabelAppVersion = "app.kubernetes.io/version" + + // LabelAppComponent is the standard label key for the component within the + // application. + LabelAppComponent = "app.kubernetes.io/component" + + // LabelAppPartOf is the standard label key for the name of a higher level + // application this one is part of. + LabelAppPartOf = "app.kubernetes.io/part-of" + + // LabelAppManagedBy is the standard label key for the tool managing the + // resource. + LabelAppManagedBy = "app.kubernetes.io/managed-by" +) + +const ( + // AppNameMultigres is the fixed application name for all Multigres resources. + AppNameMultigres = "multigres" + + // ManagedByMultigres identifies the operator managing these resources. + ManagedByMultigres = "multigres-operator" +) + +const ( + // LabelMultigresCell identifies which cell a resource belongs to. + LabelMultigresCell = "multigres.com/cell" + + // DefaultCellName is the default cell name when none is specified. + DefaultCellName = "multigres-global-topo" +) + +// BuildStandardLabels builds the standard Kubernetes labels for a Multigres +// component. These labels are applied to all resources managed by the operator. +// +// Parameters: +// - resourceName: The name of the custom resource instance (e.g., "my-etcd-cluster") +// - componentName: The component type (e.g., "etcd", "gateway", "orch", "pooler") +// - cellName: Optional cell name. If empty, no cell label is added. +// +// Standard labels include: +// - app.kubernetes.io/name: "multigres" +// - app.kubernetes.io/instance: +// - app.kubernetes.io/component: +// - app.kubernetes.io/part-of: "multigres" +// - app.kubernetes.io/managed-by: "multigres-operator" +// - multigres.com/cell: (uses "multigres-global-topo" if empty) +// +// Example usage: +// +// labels := BuildStandardLabels("my-etcd", "etcd", "cell-1") +// // Returns: { +// // "app.kubernetes.io/name": "multigres", +// // "app.kubernetes.io/instance": "my-etcd", +// // "app.kubernetes.io/component": "etcd", +// // "app.kubernetes.io/part-of": "multigres", +// // "app.kubernetes.io/managed-by": "multigres-operator", +// // "multigres.com/cell": "cell-1" +// // } +func BuildStandardLabels(resourceName, componentName, cellName string) map[string]string { + // Use default cell name if not provided + if cellName == "" { + cellName = DefaultCellName + } + + labels := map[string]string{ + LabelAppName: AppNameMultigres, + LabelAppInstance: resourceName, + LabelAppComponent: componentName, + LabelAppPartOf: AppNameMultigres, + LabelAppManagedBy: ManagedByMultigres, + LabelMultigresCell: cellName, + } + + return labels +} + +// MergeLabels merges custom labels with standard labels. +// +// Note that standard labels take precedence over custom labels to prevent users +// from overriding critical operator-managed labels. +func MergeLabels(standardLabels, customLabels map[string]string) map[string]string { + merged := make(map[string]string) + + // Copy custom labels first (if provided) + maps.Copy(merged, customLabels) + + // Copy standard labels (overwriting any duplicates from custom) + maps.Copy(merged, standardLabels) + + return merged +} diff --git a/pkg/resource-handler/controller/metadata/labels_test.go b/pkg/resource-handler/controller/metadata/labels_test.go new file mode 100644 index 00000000..d0b2fa00 --- /dev/null +++ b/pkg/resource-handler/controller/metadata/labels_test.go @@ -0,0 +1,333 @@ +package metadata_test + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/numtide/multigres-operator/pkg/resource-handler/controller/metadata" +) + +func TestBuildStandardLabels(t *testing.T) { + tests := map[string]struct { + resourceName string + componentName string + cellName string + want map[string]string + }{ + "basic case with all parameters": { + resourceName: "my-etcd-cluster", + componentName: "etcd", + cellName: "cell-1", + want: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/instance": "my-etcd-cluster", + "app.kubernetes.io/component": "etcd", + "app.kubernetes.io/part-of": "multigres", + "app.kubernetes.io/managed-by": "multigres-operator", + "multigres.com/cell": "cell-1", + }, + }, + "empty cellName uses default": { + resourceName: "my-gateway", + componentName: "gateway", + cellName: "", + want: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/instance": "my-gateway", + "app.kubernetes.io/component": "gateway", + "app.kubernetes.io/part-of": "multigres", + "app.kubernetes.io/managed-by": "multigres-operator", + "multigres.com/cell": "multigres-global-topo", + }, + }, + "with cellName should add cell label": { + resourceName: "my-orch", + componentName: "orch", + cellName: "cell-alpha", + want: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/instance": "my-orch", + "app.kubernetes.io/component": "orch", + "app.kubernetes.io/part-of": "multigres", + "app.kubernetes.io/managed-by": "multigres-operator", + "multigres.com/cell": "cell-alpha", + }, + }, + "empty resourceName": { + resourceName: "", + componentName: "pooler", + cellName: "cell-2", + want: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/instance": "", + "app.kubernetes.io/component": "pooler", + "app.kubernetes.io/part-of": "multigres", + "app.kubernetes.io/managed-by": "multigres-operator", + "multigres.com/cell": "cell-2", + }, + }, + "empty componentName": { + resourceName: "my-resource", + componentName: "", + cellName: "cell-3", + want: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/instance": "my-resource", + "app.kubernetes.io/component": "", + "app.kubernetes.io/part-of": "multigres", + "app.kubernetes.io/managed-by": "multigres-operator", + "multigres.com/cell": "cell-3", + }, + }, + "all empty strings - uses default cell": { + resourceName: "", + componentName: "", + cellName: "", + want: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/instance": "", + "app.kubernetes.io/component": "", + "app.kubernetes.io/part-of": "multigres", + "app.kubernetes.io/managed-by": "multigres-operator", + "multigres.com/cell": "multigres-global-topo", + }, + }, + "special characters in names": { + resourceName: "my-resource-123", + componentName: "etcd-v3", + cellName: "cell-prod-1", + want: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/instance": "my-resource-123", + "app.kubernetes.io/component": "etcd-v3", + "app.kubernetes.io/part-of": "multigres", + "app.kubernetes.io/managed-by": "multigres-operator", + "multigres.com/cell": "cell-prod-1", + }, + }, + "long names": { + resourceName: "very-long-resource-name-with-many-segments", + componentName: "gateway-proxy-component", + cellName: "cell-production-region-1", + want: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/instance": "very-long-resource-name-with-many-segments", + "app.kubernetes.io/component": "gateway-proxy-component", + "app.kubernetes.io/part-of": "multigres", + "app.kubernetes.io/managed-by": "multigres-operator", + "multigres.com/cell": "cell-production-region-1", + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + got := metadata.BuildStandardLabels(tc.resourceName, tc.componentName, tc.cellName) + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("BuildStandardLabels() mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestMergeLabels(t *testing.T) { + tests := map[string]struct { + standardLabels map[string]string + customLabels map[string]string + want map[string]string + }{ + "both maps populated - standard labels should win on conflicts": { + standardLabels: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/instance": "my-resource", + "app.kubernetes.io/component": "etcd", + "app.kubernetes.io/managed-by": "multigres-operator", + }, + customLabels: map[string]string{ + "app.kubernetes.io/name": "user-app", // conflict: standard should win + "custom-label-1": "value1", + "custom-label-2": "value2", + }, + want: map[string]string{ + "app.kubernetes.io/name": "multigres", // Standard wins + "app.kubernetes.io/instance": "my-resource", + "app.kubernetes.io/component": "etcd", + "app.kubernetes.io/managed-by": "multigres-operator", + "custom-label-1": "value1", + "custom-label-2": "value2", + }, + }, + "standardLabels nil, customLabels populated": { + standardLabels: nil, + customLabels: map[string]string{ + "custom-label-1": "value1", + "custom-label-2": "value2", + }, + want: map[string]string{ + "custom-label-1": "value1", + "custom-label-2": "value2", + }, + }, + "customLabels nil, standardLabels populated": { + standardLabels: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/instance": "my-resource", + "app.kubernetes.io/component": "gateway", + }, + customLabels: nil, + want: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/instance": "my-resource", + "app.kubernetes.io/component": "gateway", + }, + }, + "both maps nil": { + standardLabels: nil, + customLabels: nil, + want: map[string]string{}, + }, + "overlapping keys - standard should override custom": { + standardLabels: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/instance": "resource-1", + "app.kubernetes.io/component": "orch", + }, + customLabels: map[string]string{ + "app.kubernetes.io/instance": "user-defined-name", // conflict: standard should win + "app.kubernetes.io/component": "user-component", // conflict: standard should win + "user-label": "user-value", // no conflict: should be preserved + }, + want: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/instance": "resource-1", // Standard wins + "app.kubernetes.io/component": "orch", // Standard wins + "user-label": "user-value", // Custom preserved + }, + }, + "custom labels with keys not in standard - should be preserved": { + standardLabels: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/managed-by": "multigres-operator", + }, + customLabels: map[string]string{ + "env": "production", + "team": "platform", + "version": "v1.2.3", + "app.custom.io/x": "custom-value", + }, + want: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/managed-by": "multigres-operator", + "app.custom.io/x": "custom-value", + "env": "production", + "team": "platform", + "version": "v1.2.3", + }, + }, + "empty standard labels map": { + standardLabels: map[string]string{}, + customLabels: map[string]string{ + "custom-label": "value", + }, + want: map[string]string{ + "custom-label": "value", + }, + }, + "empty custom labels map": { + standardLabels: map[string]string{ + "app.kubernetes.io/name": "multigres", + }, + customLabels: map[string]string{}, + want: map[string]string{ + "app.kubernetes.io/name": "multigres", + }, + }, + "both maps empty": { + standardLabels: map[string]string{}, + customLabels: map[string]string{}, + want: map[string]string{}, + }, + "all standard labels can override custom labels": { + standardLabels: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/instance": "standard-instance", + "app.kubernetes.io/component": "standard-component", + "app.kubernetes.io/managed-by": "multigres-operator", + "multigres.com/cell": "standard-cell", + }, + customLabels: map[string]string{ + "app.kubernetes.io/name": "custom-app", // conflict: standard wins + "app.kubernetes.io/instance": "custom-instance", // conflict: standard wins + "app.kubernetes.io/component": "custom-component", // conflict: standard wins + "app.kubernetes.io/managed-by": "custom-operator", // conflict: standard wins + "multigres.com/cell": "custom-cell", // conflict: standard wins + }, + want: map[string]string{ + "app.kubernetes.io/name": "multigres", // All standard values win + "app.kubernetes.io/instance": "standard-instance", + "app.kubernetes.io/component": "standard-component", + "app.kubernetes.io/managed-by": "multigres-operator", + "multigres.com/cell": "standard-cell", + }, + }, + "part-of label conflict - standard wins": { + standardLabels: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/part-of": "multigres", + }, + customLabels: map[string]string{ + "app.kubernetes.io/part-of": "custom-app", // conflict: standard should win + "custom-label": "value", + }, + want: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/part-of": "multigres", // Standard wins + "custom-label": "value", + }, + }, + "complex scenario with many labels": { + standardLabels: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/instance": "my-cluster", + "app.kubernetes.io/component": "pooler", + "app.kubernetes.io/part-of": "multigres", + "app.kubernetes.io/managed-by": "multigres-operator", + }, + customLabels: map[string]string{ + "app.kubernetes.io/component": "user-override", // conflict: standard should win + "app.kubernetes.io/part-of": "custom-part", // conflict: standard should win + "app.custom.io/name": "custom-app", + "kubernetes.io/name": "k8s-name", + "monitoring": "enabled", + "backup": "true", + "tier": "critical", + "owner": "team-database", + "cost-center": "engineering", + }, + want: map[string]string{ + "app.kubernetes.io/name": "multigres", + "app.kubernetes.io/instance": "my-cluster", + "app.kubernetes.io/component": "pooler", // Standard wins + "app.kubernetes.io/part-of": "multigres", // Standard wins + "app.kubernetes.io/managed-by": "multigres-operator", + "app.custom.io/name": "custom-app", + "kubernetes.io/name": "k8s-name", + "monitoring": "enabled", + "backup": "true", + "tier": "critical", + "owner": "team-database", + "cost-center": "engineering", + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + got := metadata.MergeLabels(tc.standardLabels, tc.customLabels) + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("MergeLabels() mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/pkg/resource-handler/go.mod b/pkg/resource-handler/go.mod index 370abd64..012c27ad 100644 --- a/pkg/resource-handler/go.mod +++ b/pkg/resource-handler/go.mod @@ -1,3 +1,29 @@ module github.com/numtide/multigres-operator/pkg/resource-handler go 1.25.0 + +require ( + github.com/google/go-cmp v0.7.0 + k8s.io/api v0.34.1 +) + +require ( + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/x448/float16 v0.8.4 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/text v0.23.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + k8s.io/apimachinery v0.34.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect +) diff --git a/pkg/resource-handler/go.sum b/pkg/resource-handler/go.sum new file mode 100644 index 00000000..df4088f6 --- /dev/null +++ b/pkg/resource-handler/go.sum @@ -0,0 +1,95 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=