diff --git a/.gitignore b/.gitignore index 41eb2bd..d6e502f 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ go.work *~ *.html + +# VS Code Go debug binaries +bin/debug_bin* diff --git a/Dockerfile b/Dockerfile index 4bedff8..2599f8c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,5 +29,7 @@ RUN apk add --no-cache docker-cli kind WORKDIR / COPY --from=builder /workspace/manager . USER 65532:65532 +# Set the default command to execute when running the container +ENV KIND_IN_CONTAINER=true ENTRYPOINT ["/manager"] diff --git a/PROJECT b/PROJECT index 96cfbe6..0a9a179 100644 --- a/PROJECT +++ b/PROJECT @@ -16,12 +16,4 @@ resources: kind: Cluster path: github.com/openmcp-project/cluster-provider-kind/api/v1alpha1 version: v1alpha1 -- api: - crdVersion: v1 - namespaced: true - controller: true - domain: kind.clusters.openmcp.cloud - kind: AccessRequest - path: github.com/openmcp-project/cluster-provider-kind/api/v1alpha1 - version: v1alpha1 version: "3" diff --git a/api/v1alpha1/accessrequest_types.go b/api/v1alpha1/accessrequest_types.go deleted file mode 100644 index 9b4dff1..0000000 --- a/api/v1alpha1/accessrequest_types.go +++ /dev/null @@ -1,72 +0,0 @@ -/* -Copyright 2025. - -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 v1alpha1 - -import ( - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// AccessRequestSpec defines the desired state of AccessRequest. -type AccessRequestSpec struct { - ClusterRef corev1.LocalObjectReference `json:"clusterRef,omitempty"` - Rules []rbacv1.PolicyRule `json:"rules"` -} - -// AccessRequestStatus defines the observed state of AccessRequest. -type AccessRequestStatus struct { - Kubeconfig *Kubeconfig `json:"kubeconfig,omitempty"` -} - -// Kubeconfig contains the information needed to access a cluster. -type Kubeconfig struct { - SecretRef corev1.SecretReference `json:"secretRef,omitempty"` - ExpiresAt metav1.Time `json:"expiresAt,omitempty"` - ServiceAccount ServiceAccountRef `json:"serviceAccount,omitempty"` -} - -// ServiceAccountRef contains the information needed to access a service account. -type ServiceAccountRef struct { - Name string `json:"name"` - Namespace string `json:"namespace"` -} - -// +kubebuilder:object:root=true -// +kubebuilder:subresource:status - -// AccessRequest is the Schema for the accessrequests API. -type AccessRequest struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec AccessRequestSpec `json:"spec,omitempty"` - Status AccessRequestStatus `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// AccessRequestList contains a list of AccessRequest. -type AccessRequestList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []AccessRequest `json:"items"` -} - -func init() { - SchemeBuilder.Register(&AccessRequest{}, &AccessRequestList{}) -} diff --git a/api/v1alpha1/cluster_types.go b/api/v1alpha1/cluster_types.go index 21ff81b..32d4615 100644 --- a/api/v1alpha1/cluster_types.go +++ b/api/v1alpha1/cluster_types.go @@ -18,6 +18,7 @@ package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" ) // Phase is a custom type representing the phase of a cluster. @@ -48,10 +49,32 @@ type ClusterSpec struct{} // ClusterStatus defines the observed state of Cluster. type ClusterStatus struct { + // Reason is expected to contain a CamelCased string that provides further information in a machine-readable format. + // +optional + Reason string `json:"reason,omitempty"` + + // Message contains further details in a human-readable format. + // +optional + Message string `json:"message,omitempty"` + + // Conditions is a list of conditions that apply to the cluster. + // +optional Conditions []metav1.Condition `json:"conditions,omitempty"` + // ObservedGeneration is the generation of this resource that was last reconciled by the controller. + ObservedGeneration int64 `json:"observedGeneration"` + // +kubebuilder:default=Unknown Phase Phase `json:"phase"` + + // APIServer is the API server endpoint of the cluster. + // +optional + APIServer string `json:"apiServer,omitempty"` + + // ProviderStatus is the provider-specific status of the cluster. + // x-kubernetes-preserve-unknown-fields: true + // +optional + ProviderStatus *runtime.RawExtension `json:"providerStatus,omitempty"` } // Cluster is the Schema for the clusters API. @@ -79,3 +102,12 @@ type ClusterList struct { func init() { SchemeBuilder.Register(&Cluster{}, &ClusterList{}) } + +const ( + // StatusPhaseReady indicates that the resource is ready. All conditions are met and are in status "True". + StatusPhaseReady = "Ready" + // StatusPhaseProgressing indicates that the resource is not ready and being created or updated. At least one condition is not met and is in status "False". + StatusPhaseProgressing = "Progressing" + // StatusPhaseTerminating indicates that the resource is not ready and in deletion. At least one condition is not met and is in status "False". + StatusPhaseTerminating = "Terminating" +) diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 1a9214a..cdef32f 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -5,113 +5,10 @@ package v1alpha1 import ( - v1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AccessRequest) DeepCopyInto(out *AccessRequest) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessRequest. -func (in *AccessRequest) DeepCopy() *AccessRequest { - if in == nil { - return nil - } - out := new(AccessRequest) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *AccessRequest) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AccessRequestList) DeepCopyInto(out *AccessRequestList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]AccessRequest, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessRequestList. -func (in *AccessRequestList) DeepCopy() *AccessRequestList { - if in == nil { - return nil - } - out := new(AccessRequestList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *AccessRequestList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AccessRequestSpec) DeepCopyInto(out *AccessRequestSpec) { - *out = *in - out.ClusterRef = in.ClusterRef - if in.Rules != nil { - in, out := &in.Rules, &out.Rules - *out = make([]v1.PolicyRule, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessRequestSpec. -func (in *AccessRequestSpec) DeepCopy() *AccessRequestSpec { - if in == nil { - return nil - } - out := new(AccessRequestSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AccessRequestStatus) DeepCopyInto(out *AccessRequestStatus) { - *out = *in - if in.Kubeconfig != nil { - in, out := &in.Kubeconfig, &out.Kubeconfig - *out = new(Kubeconfig) - (*in).DeepCopyInto(*out) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessRequestStatus. -func (in *AccessRequestStatus) DeepCopy() *AccessRequestStatus { - if in == nil { - return nil - } - out := new(AccessRequestStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Cluster) DeepCopyInto(out *Cluster) { *out = *in @@ -191,11 +88,16 @@ func (in *ClusterStatus) DeepCopyInto(out *ClusterStatus) { *out = *in if in.Conditions != nil { in, out := &in.Conditions, &out.Conditions - *out = make([]metav1.Condition, len(*in)) + *out = make([]v1.Condition, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.ProviderStatus != nil { + in, out := &in.ProviderStatus, &out.ProviderStatus + *out = new(runtime.RawExtension) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterStatus. @@ -207,36 +109,3 @@ func (in *ClusterStatus) DeepCopy() *ClusterStatus { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Kubeconfig) DeepCopyInto(out *Kubeconfig) { - *out = *in - out.SecretRef = in.SecretRef - in.ExpiresAt.DeepCopyInto(&out.ExpiresAt) - out.ServiceAccount = in.ServiceAccount -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Kubeconfig. -func (in *Kubeconfig) DeepCopy() *Kubeconfig { - if in == nil { - return nil - } - out := new(Kubeconfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ServiceAccountRef) DeepCopyInto(out *ServiceAccountRef) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountRef. -func (in *ServiceAccountRef) DeepCopy() *ServiceAccountRef { - if in == nil { - return nil - } - out := new(ServiceAccountRef) - in.DeepCopyInto(out) - return out -} diff --git a/cmd/cluster-provider-kind/main.go b/cmd/cluster-provider-kind/main.go index 8bd3219..aacfacd 100644 --- a/cmd/cluster-provider-kind/main.go +++ b/cmd/cluster-provider-kind/main.go @@ -38,6 +38,8 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" + openv1alpha1 "github.com/openmcp-project/openmcp-operator/api/clusters/v1alpha1" + kindclustersopenmcpcloudv1alpha1 "github.com/openmcp-project/cluster-provider-kind/api/v1alpha1" "github.com/openmcp-project/cluster-provider-kind/internal/controller" "github.com/openmcp-project/cluster-provider-kind/pkg/kind" @@ -54,6 +56,8 @@ func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(kindclustersopenmcpcloudv1alpha1.AddToScheme(scheme)) + utilruntime.Must(openv1alpha1.AddToScheme(scheme)) + // +kubebuilder:scaffold:scheme } @@ -181,7 +185,7 @@ func main() { }) } - kindProvider := kind.NewDockerProvider() + kindProvider := kind.NewKindProvider() mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, diff --git a/config/crd/bases/kind.clusters.openmcp.cloud_accessrequests.yaml b/config/crd/bases/kind.clusters.openmcp.cloud_accessrequests.yaml deleted file mode 100644 index d0e592a..0000000 --- a/config/crd/bases/kind.clusters.openmcp.cloud_accessrequests.yaml +++ /dev/null @@ -1,153 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.18.0 - name: accessrequests.kind.clusters.openmcp.cloud -spec: - group: kind.clusters.openmcp.cloud - names: - kind: AccessRequest - listKind: AccessRequestList - plural: accessrequests - singular: accessrequest - scope: Namespaced - versions: - - name: v1alpha1 - schema: - openAPIV3Schema: - description: AccessRequest is the Schema for the accessrequests API. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: AccessRequestSpec defines the desired state of AccessRequest. - properties: - clusterRef: - description: |- - LocalObjectReference contains enough information to let you locate the - referenced object inside the same namespace. - properties: - name: - default: "" - description: |- - Name of the referent. - This field is effectively required, but due to backwards compatibility is - allowed to be empty. Instances of this type with an empty value here are - almost certainly wrong. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - type: string - type: object - x-kubernetes-map-type: atomic - rules: - items: - description: |- - PolicyRule holds information that describes a policy rule, but does not contain information - about who the rule applies to or which namespace the rule applies to. - properties: - apiGroups: - description: |- - APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of - the enumerated resources in any API group will be allowed. "" represents the core API group and "*" represents all API groups. - items: - type: string - type: array - x-kubernetes-list-type: atomic - nonResourceURLs: - description: |- - NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full, final step in the path - Since non-resource URLs are not namespaced, this field is only applicable for ClusterRoles referenced from a ClusterRoleBinding. - Rules can either apply to API resources (such as "pods" or "secrets") or non-resource URL paths (such as "/api"), but not both. - items: - type: string - type: array - x-kubernetes-list-type: atomic - resourceNames: - description: ResourceNames is an optional white list of names - that the rule applies to. An empty set means that everything - is allowed. - items: - type: string - type: array - x-kubernetes-list-type: atomic - resources: - description: Resources is a list of resources this rule applies - to. '*' represents all resources. - items: - type: string - type: array - x-kubernetes-list-type: atomic - verbs: - description: Verbs is a list of Verbs that apply to ALL the - ResourceKinds contained in this rule. '*' represents all verbs. - items: - type: string - type: array - x-kubernetes-list-type: atomic - required: - - verbs - type: object - type: array - required: - - rules - type: object - status: - description: AccessRequestStatus defines the observed state of AccessRequest. - properties: - kubeconfig: - description: Kubeconfig contains the information needed to access - a cluster. - properties: - expiresAt: - format: date-time - type: string - secretRef: - description: |- - SecretReference represents a Secret Reference. It has enough information to retrieve secret - in any namespace - properties: - name: - description: name is unique within a namespace to reference - a secret resource. - type: string - namespace: - description: namespace defines the space within which the - secret name must be unique. - type: string - type: object - x-kubernetes-map-type: atomic - serviceAccount: - description: ServiceAccountRef contains the information needed - to access a service account. - properties: - name: - type: string - namespace: - type: string - required: - - name - - namespace - type: object - type: object - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/config/crd/bases/kind.clusters.openmcp.cloud_clusters.yaml b/config/crd/bases/kind.clusters.openmcp.cloud_clusters.yaml index efd7b14..ea37ca0 100644 --- a/config/crd/bases/kind.clusters.openmcp.cloud_clusters.yaml +++ b/config/crd/bases/kind.clusters.openmcp.cloud_clusters.yaml @@ -49,7 +49,12 @@ spec: status: description: ClusterStatus defines the observed state of Cluster. properties: + apiServer: + description: APIServer is the API server endpoint of the cluster. + type: string conditions: + description: Conditions is a list of conditions that apply to the + cluster. items: description: Condition contains details for one aspect of the current state of this API Resource. @@ -105,11 +110,31 @@ spec: - type type: object type: array + message: + description: Message contains further details in a human-readable + format. + type: string + observedGeneration: + description: ObservedGeneration is the generation of this resource + that was last reconciled by the controller. + format: int64 + type: integer phase: default: Unknown description: Phase is a custom type representing the phase of a cluster. type: string + providerStatus: + description: |- + ProviderStatus is the provider-specific status of the cluster. + x-kubernetes-preserve-unknown-fields: true + type: object + x-kubernetes-preserve-unknown-fields: true + reason: + description: Reason is expected to contain a CamelCased string that + provides further information in a machine-readable format. + type: string required: + - observedGeneration - phase type: object type: object diff --git a/config/samples/v1alpha1_accessrequest.yaml b/config/samples/v1alpha1_accessrequest.yaml index baf01e9..654f54c 100644 --- a/config/samples/v1alpha1_accessrequest.yaml +++ b/config/samples/v1alpha1_accessrequest.yaml @@ -1,4 +1,4 @@ -apiVersion: kind.clusters.openmcp.cloud/v1alpha1 +apiVersion: clusters.openmcp.cloud/v1alpha1 kind: AccessRequest metadata: name: accessrequest-sample diff --git a/go.mod b/go.mod index 86375f0..f32ac6c 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,16 @@ module github.com/openmcp-project/cluster-provider-kind -go 1.23.0 +go 1.24.2 godebug default=go1.23 require ( - github.com/onsi/ginkgo/v2 v2.23.4 - github.com/onsi/gomega v1.36.3 + github.com/openmcp-project/openmcp-operator/api v0.8.3 github.com/stretchr/testify v1.10.0 - k8s.io/api v0.32.1 - k8s.io/apimachinery v0.32.1 - k8s.io/client-go v0.32.1 - sigs.k8s.io/controller-runtime v0.20.2 + k8s.io/api v0.33.1 + k8s.io/apimachinery v0.33.1 + k8s.io/client-go v0.33.1 + sigs.k8s.io/controller-runtime v0.21.0 sigs.k8s.io/kind v0.29.0 sigs.k8s.io/kustomize/api v0.19.0 sigs.k8s.io/kustomize/kyaml v0.19.0 @@ -19,39 +18,34 @@ require ( require ( al.essio.dev/pkg/shellescape v1.5.1 // indirect - cel.dev/expr v0.18.0 // indirect + cel.dev/expr v0.19.1 // indirect github.com/BurntSushi/toml v1.4.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect - github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-errors/errors v1.5.1 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.1 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/cel-go v0.22.0 // indirect + github.com/google/cel-go v0.23.2 // indirect github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // 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 @@ -61,55 +55,56 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/onsi/ginkgo/v2 v2.23.4 // indirect + github.com/onsi/gomega v1.36.3 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect - go.uber.org/automaxprocs v1.6.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect + go.opentelemetry.io/otel v1.33.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 // indirect + go.opentelemetry.io/otel/metric v1.33.0 // indirect + go.opentelemetry.io/otel/sdk v1.33.0 // indirect + go.opentelemetry.io/otel/trace v1.33.0 // indirect + go.opentelemetry.io/proto/otlp v1.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.37.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.32.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect - golang.org/x/time v0.7.0 // indirect - golang.org/x/tools v0.31.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/oauth2 v0.27.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.10.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 // indirect - google.golang.org/grpc v1.65.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/grpc v1.68.1 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.32.1 // indirect - k8s.io/apiserver v0.32.1 // indirect - k8s.io/component-base v0.32.1 // indirect + k8s.io/apiextensions-apiserver v0.33.1 // indirect + k8s.io/apiserver v0.33.1 // indirect + k8s.io/component-base v0.33.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // 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/v4 v4.6.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index b52da31..0cf92a5 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,11 @@ al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho= al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= -cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo= -cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= +cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= @@ -21,23 +19,23 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= +github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= @@ -56,8 +54,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g= -github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= +github.com/google/cel-go v0.23.2 h1:UdEe3CvQh3Nv+E/j9r1Y//WO0K0cSyD7/y0bzyLIMI4= +github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo= github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -72,8 +70,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -82,10 +80,14 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr 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/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -103,6 +105,8 @@ github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= github.com/onsi/gomega v1.36.3 h1:hID7cr8t3Wp26+cYnfcjR6HpJ00fdogN6dqZ1t6IylU= github.com/onsi/gomega v1.36.3/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/openmcp-project/openmcp-operator/api v0.8.3 h1:s1c9kwvHUAkHZfSybb83Biw8qyia9pF4r2zxbfSM3qI= +github.com/openmcp-project/openmcp-operator/api v0.8.3/go.mod h1:AflvCe/S41tO3x2rq4p+JnWxqVNtuwMn4LDPEVE00LE= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -110,25 +114,24 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= -github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -149,22 +152,24 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= +go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= +go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA= +go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= +go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= +go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= +go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= +go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= +go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= +go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg= +go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -184,29 +189,29 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn 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.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 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/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= 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/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 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= @@ -219,12 +224,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 h1:YcyjlL1PRr2Q17/I0dPk2JmYS5CDXfcdb2Z3YRioEbw= -google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 h1:2035KHhUv+EpyB+hWgJnaWKJOdX1E95w2S8Rr4uWKTs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= +google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -237,30 +242,30 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc= -k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k= -k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw= -k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto= -k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs= -k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/apiserver v0.32.1 h1:oo0OozRos66WFq87Zc5tclUX2r0mymoVHRq8JmR7Aak= -k8s.io/apiserver v0.32.1/go.mod h1:UcB9tWjBY7aryeI5zAgzVJB/6k7E97bkr1RgqDz0jPw= -k8s.io/client-go v0.32.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU= -k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg= -k8s.io/component-base v0.32.1 h1:/5IfJ0dHIKBWysGV0yKTFfacZ5yNV1sulPh3ilJjRZk= -k8s.io/component-base v0.32.1/go.mod h1:j1iMMHi/sqAHeG5z+O9BFNCF698a1u0186zkjMZQ28w= +k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= +k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= +k8s.io/apiextensions-apiserver v0.33.1 h1:N7ccbSlRN6I2QBcXevB73PixX2dQNIW0ZRuguEE91zI= +k8s.io/apiextensions-apiserver v0.33.1/go.mod h1:uNQ52z1A1Gu75QSa+pFK5bcXc4hq7lpOXbweZgi4dqA= +k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= +k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= +k8s.io/apiserver v0.33.1 h1:yLgLUPDVC6tHbNcw5uE9mo1T6ELhJj7B0geifra3Qdo= +k8s.io/apiserver v0.33.1/go.mod h1:VMbE4ArWYLO01omz+k8hFjAdYfc3GVAYPrhP2tTKccs= +k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= +k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= +k8s.io/component-base v0.33.1 h1:EoJ0xA+wr77T+G8p6T3l4efT2oNwbqBVKR71E0tBIaI= +k8s.io/component-base v0.33.1/go.mod h1:guT/w/6piyPfTgq7gfvgetyXMIh10zuXA6cRRm3rDuY= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.20.2 h1:/439OZVxoEc02psi1h4QO3bHzTgu49bb347Xp4gW1pc= -sigs.k8s.io/controller-runtime v0.20.2/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 h1:jgJW5IePPXLGB8e/1wvd0Ich9QE97RvvF3a8J3fP/Lg= +k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= +sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= +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/kind v0.29.0 h1:3TpCsyh908IkXXpcSnsMjWdwdWjIl7o9IMZImZCWFnI= sigs.k8s.io/kind v0.29.0/go.mod h1:ldWQisw2NYyM6k64o/tkZng/1qQW7OlzcN5a8geJX3o= sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ= @@ -270,7 +275,7 @@ sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/r sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 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/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= -sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/controller/accessrequest_controller.go b/internal/controller/accessrequest_controller.go index 81972db..dac4bff 100644 --- a/internal/controller/accessrequest_controller.go +++ b/internal/controller/accessrequest_controller.go @@ -30,7 +30,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" logf "sigs.k8s.io/controller-runtime/pkg/log" + openv1alpha1 "github.com/openmcp-project/openmcp-operator/api/clusters/v1alpha1" + "github.com/openmcp-project/cluster-provider-kind/api/v1alpha1" + "github.com/openmcp-project/cluster-provider-kind/pkg/kind" ) @@ -54,7 +57,7 @@ type AccessRequestReconciler struct { func (r *AccessRequestReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = logf.FromContext(ctx) - ar := &v1alpha1.AccessRequest{} + ar := &openv1alpha1.AccessRequest{} if err := r.Get(ctx, req.NamespacedName, ar); err != nil { if apierrors.IsNotFound(err) { return ctrl.Result{}, nil @@ -88,6 +91,9 @@ func (r *AccessRequestReconciler) Reconcile(ctx context.Context, req ctrl.Reques } secret.Data["kubeconfig"] = []byte(kubeconfigStr) return controllerutil.SetOwnerReference(ar, secret, r.Scheme) + + // TODO: write kubeconfig to secret and reference secret in status of AccessRequest resource + // ignore clusterrequest ref }) return ctrl.Result{}, err } @@ -95,7 +101,7 @@ func (r *AccessRequestReconciler) Reconcile(ctx context.Context, req ctrl.Reques // SetupWithManager sets up the controller with the Manager. func (r *AccessRequestReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&v1alpha1.AccessRequest{}). + For(&openv1alpha1.AccessRequest{}). Named("accessrequest"). Complete(r) } diff --git a/internal/controller/cluster_controller.go b/internal/controller/cluster_controller.go index 08cb4e8..f09643f 100644 --- a/internal/controller/cluster_controller.go +++ b/internal/controller/cluster_controller.go @@ -31,6 +31,7 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "github.com/openmcp-project/cluster-provider-kind/api/v1alpha1" + "github.com/openmcp-project/cluster-provider-kind/pkg/kind" "github.com/openmcp-project/cluster-provider-kind/pkg/metallb" "github.com/openmcp-project/cluster-provider-kind/pkg/smartrequeue" @@ -83,7 +84,7 @@ func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct func (r *ClusterReconciler) handleDelete(ctx context.Context, cluster *v1alpha1.Cluster) (ctrl.Result, error) { requeue := smartrequeue.FromContext(ctx) - cluster.Status.Phase = v1alpha1.Terminating + cluster.Status.Phase = v1alpha1.StatusPhaseTerminating if !controllerutil.ContainsFinalizer(cluster, Finalizer) { // Nothing to do @@ -114,7 +115,7 @@ func (r *ClusterReconciler) handleDelete(ctx context.Context, cluster *v1alpha1. //nolint:gocyclo func (r *ClusterReconciler) handleCreateOrUpdate(ctx context.Context, cluster *v1alpha1.Cluster) (ctrl.Result, error) { requeue := smartrequeue.FromContext(ctx) - cluster.Status.Phase = v1alpha1.Progressing + cluster.Status.Phase = v1alpha1.StatusPhaseProgressing if controllerutil.AddFinalizer(cluster, Finalizer) { if err := r.Update(ctx, cluster); err != nil { @@ -141,7 +142,7 @@ func (r *ClusterReconciler) handleCreateOrUpdate(ctx context.Context, cluster *v return requeue.Progressing() } meta.SetStatusCondition(&cluster.Status.Conditions, metav1.Condition{ - Type: string(v1alpha1.KindReady), + Type: string("KindReady"), Status: metav1.ConditionTrue, Reason: "ClusterExists", }) @@ -178,7 +179,7 @@ func (r *ClusterReconciler) handleCreateOrUpdate(ctx context.Context, cluster *v return requeue.Progressing() } meta.SetStatusCondition(&cluster.Status.Conditions, metav1.Condition{ - Type: string(v1alpha1.MetalLBReady), + Type: "MetalLBReady", Status: metav1.ConditionTrue, Reason: "AllPodsReady", }) @@ -187,9 +188,9 @@ func (r *ClusterReconciler) handleCreateOrUpdate(ctx context.Context, cluster *v return requeue.Error(err) } - cluster.Status.Phase = v1alpha1.Ready + cluster.Status.Phase = v1alpha1.StatusPhaseReady meta.SetStatusCondition(&cluster.Status.Conditions, metav1.Condition{ - Type: string(v1alpha1.Ready), + Type: string(v1alpha1.StatusPhaseReady), Status: metav1.ConditionTrue, Reason: "ClusterAndMetalLBReady", }) diff --git a/pkg/kind/provider.go b/pkg/kind/provider.go index fc4313b..17b1a72 100644 --- a/pkg/kind/provider.go +++ b/pkg/kind/provider.go @@ -12,10 +12,19 @@ import ( "sigs.k8s.io/kind/pkg/cluster" ) +// Provider defines the interface for managing Kubernetes clusters using kind. +// It provides methods to create, delete, check existence of clusters, and retrieve kubeconfig. type Provider interface { + // CreateCluster creates a new Kubernetes cluster with the given name. CreateCluster(name string) error + + // DeleteCluster deletes the Kubernetes cluster with the given name. DeleteCluster(name string) error + + // ClusterExists checks if a Kubernetes cluster with the given name exists. ClusterExists(name string) (bool, error) + + // KubeConfig retrieves the kubeconfig for the specified cluster name. KubeConfig(name string) (string, error) } @@ -23,22 +32,24 @@ var ( kubeconfigPath = path.Join(os.TempDir(), "cluster-provider-kind.kubeconfig") ) -func NewDockerProvider() Provider { - return &provider{ +// KindProvider returns a new instance of the kind provider for managing Kubernetes clusters. +// It uses the default Docker-based kind provider configuration. +func NewKindProvider() Provider { + return &kindProvider{ internal: cluster.NewProvider( cluster.ProviderWithDocker(), ), } } -var _ Provider = &provider{} +var _ Provider = &kindProvider{} -type provider struct { +type kindProvider struct { internal *cluster.Provider } // ClusterExists implements Provider. -func (p *provider) ClusterExists(name string) (bool, error) { +func (p *kindProvider) ClusterExists(name string) (bool, error) { clusters, err := p.internal.List() if err != nil { return false, err @@ -48,7 +59,7 @@ func (p *provider) ClusterExists(name string) (bool, error) { } // CreateCluster implements Provider. -func (p *provider) CreateCluster(name string) error { +func (p *kindProvider) CreateCluster(name string) error { options := []cluster.CreateOption{ cluster.CreateWithWaitForReady(1 * time.Minute), cluster.CreateWithKubeconfigPath(kubeconfigPath), @@ -57,12 +68,13 @@ func (p *provider) CreateCluster(name string) error { } // DeleteCluster implements Provider. -func (p *provider) DeleteCluster(name string) error { +func (p *kindProvider) DeleteCluster(name string) error { return p.internal.Delete(name, kubeconfigPath) } -func (p *provider) KubeConfig(name string) (string, error) { - kubeconfigStr, err := p.internal.KubeConfig(name, true) +// KubeConfig implements Provider. +func (p *kindProvider) KubeConfig(name string) (string, error) { + kubeconfigStr, err := p.internal.KubeConfig(name, runsInContainer()) if err != nil { return "", err } @@ -77,6 +89,11 @@ func (p *provider) KubeConfig(name string) (string, error) { return strings.ReplaceAll(kubeconfigStr, "https://"+containerName, "https://"+containerIP.String()), nil } -func (p *provider) controlPlaneContainer(name string) string { +// runsInContainer returns true if the KIND_IN_CONTAINER environment variable is set to "true". +func runsInContainer() bool { + return os.Getenv("KIND_IN_CONTAINER") == "true" +} + +func (p *kindProvider) controlPlaneContainer(name string) string { return fmt.Sprintf("%s-control-plane", name) } diff --git a/pkg/smartrequeue/context.go b/pkg/smartrequeue/context.go index a558e91..1f0bc2d 100644 --- a/pkg/smartrequeue/context.go +++ b/pkg/smartrequeue/context.go @@ -2,16 +2,21 @@ package smartrequeue import "context" +// contextKey is a type used as a key for storing and retrieving the Entry from the context. type contextKey struct{} +// NewContext creates a new context with the given Entry. +// This is a utility function for passing Entry instances through context. func NewContext(ctx context.Context, entry *Entry) context.Context { return context.WithValue(ctx, contextKey{}, entry) } +// FromContext retrieves the Entry from the context, if it exists. +// Returns nil if no Entry is found in the context. func FromContext(ctx context.Context) *Entry { - s, ok := ctx.Value(contextKey{}).(*Entry) + entry, ok := ctx.Value(contextKey{}).(*Entry) if !ok { return nil } - return s + return entry } diff --git a/pkg/smartrequeue/context_test.go b/pkg/smartrequeue/context_test.go new file mode 100644 index 0000000..9324fd4 --- /dev/null +++ b/pkg/smartrequeue/context_test.go @@ -0,0 +1,33 @@ +package smartrequeue + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestNewContext(t *testing.T) { + store := NewStore(time.Second, time.Minute, 2) + entry := newEntry(store) + ctx := NewContext(context.Background(), entry) + + // Test that we get the entry back using FromContext + got := FromContext(ctx) + assert.Equal(t, entry, got, "Expected entry to be the same as the one set in context") +} + +func TestFromContext(t *testing.T) { + store := NewStore(time.Second, time.Minute, 2) + entry := newEntry(store) + ctx := NewContext(context.Background(), entry) + + // Retrieve entry from context + got := FromContext(ctx) + assert.Equal(t, entry, got, "Expected entry to be the same as the one set in context") + + // Test empty context + got = FromContext(context.Background()) + assert.Nil(t, got, "Expected nil when no entry is set in context") +} diff --git a/pkg/smartrequeue/entry.go b/pkg/smartrequeue/entry.go new file mode 100644 index 0000000..2d5d292 --- /dev/null +++ b/pkg/smartrequeue/entry.go @@ -0,0 +1,67 @@ +package smartrequeue + +import ( + "time" + + ctrl "sigs.k8s.io/controller-runtime" +) + +// Entry is used to manage the requeue logic for a specific object. +// It holds the next duration to requeue and the store it belongs to. +type Entry struct { + store *Store + nextDuration time.Duration +} + +func newEntry(s *Store) *Entry { + return &Entry{ + store: s, + nextDuration: s.minInterval, + } +} + +// Error resets the duration to the minInterval and returns an empty Result and the error +// so that the controller-runtime can handle the exponential backoff for errors. +func (e *Entry) Error(err error) (ctrl.Result, error) { + e.nextDuration = e.store.minInterval + return ctrl.Result{}, err +} + +// Stable returns a Result and increments the interval for the next iteration. +// Used when the external resource is stable (healthy or unhealthy). +func (e *Entry) Stable() (ctrl.Result, error) { + // Save current duration for result + current := e.nextDuration + + // Schedule calculation of next duration + defer e.setNext() + + return ctrl.Result{RequeueAfter: current}, nil +} + +// Progressing resets the duration to the minInterval and returns a Result with that interval. +// Used when the external resource is still doing something (creating, deleting, updating, etc.) +func (e *Entry) Progressing() (ctrl.Result, error) { + e.nextDuration = e.store.minInterval + defer e.setNext() + return ctrl.Result{RequeueAfter: e.nextDuration}, nil +} + +// Never deletes the entry from the store and returns an empty Result. +func (e *Entry) Never() (ctrl.Result, error) { + e.store.deleteEntry(e) + return ctrl.Result{}, nil +} + +// setNext updates the next requeue duration using exponential backoff. +// It multiplies the current duration by the store's multiplier and ensures +// the result doesn't exceed the configured maximum interval. +func (e *Entry) setNext() { + newDuration := time.Duration(float32(e.nextDuration) * e.store.multiplier) + + if newDuration > e.store.maxInterval { + newDuration = e.store.maxInterval + } + + e.nextDuration = newDuration +} diff --git a/pkg/smartrequeue/entry_test.go b/pkg/smartrequeue/entry_test.go new file mode 100644 index 0000000..fa93538 --- /dev/null +++ b/pkg/smartrequeue/entry_test.go @@ -0,0 +1,119 @@ +package smartrequeue + +import ( + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + ctrl "sigs.k8s.io/controller-runtime" +) + +// Helper function to get requeue duration from Result +func getRequeueAfter(res ctrl.Result, _ error) time.Duration { + return res.RequeueAfter.Round(time.Second) +} + +func TestEntry_Stable(t *testing.T) { + // Setup + store := NewStore(time.Second, time.Minute, 2) + entry := newEntry(store) + + // Test the exponential backoff behavior + t.Run("exponential backoff sequence", func(t *testing.T) { + expectedDurations := []time.Duration{ + 1 * time.Second, + 2 * time.Second, + 4 * time.Second, + 8 * time.Second, + 16 * time.Second, + 32 * time.Second, + 60 * time.Second, // Capped at maxInterval + 60 * time.Second, // Still capped + } + + for i, expected := range expectedDurations { + result, err := entry.Stable() + require.NoError(t, err) + assert.Equal(t, expected, getRequeueAfter(result, err), "Iteration %d should have correct duration", i) + } + }) +} + +func TestEntry_Progressing(t *testing.T) { + // Setup + minInterval := time.Second + maxInterval := time.Minute + store := NewStore(minInterval, maxInterval, 2) + entry := newEntry(store) + + // Ensure state is not at minimum + _, _ = entry.Stable() + _, _ = entry.Stable() + + // Test progressing resets duration to minimum + t.Run("resets to minimum interval", func(t *testing.T) { + result, err := entry.Progressing() + require.NoError(t, err) + assert.Equal(t, minInterval, getRequeueAfter(result, err)) + + // Second call should also return min interval with small increment + result, err = entry.Progressing() + require.NoError(t, err) + assert.Equal(t, minInterval, getRequeueAfter(result, err)) + }) + + // After progressing, Stable should restart exponential backoff + t.Run("stable continues from minimum", func(t *testing.T) { + result, err := entry.Stable() + require.NoError(t, err) + assert.Equal(t, 2*time.Second, getRequeueAfter(result, err)) + + result, err = entry.Stable() + require.NoError(t, err) + assert.Equal(t, 4*time.Second, getRequeueAfter(result, err)) + }) +} + +func TestEntry_Error(t *testing.T) { + // Setup + store := NewStore(time.Second, time.Minute, 2) + entry := newEntry(store) + testErr := errors.New("test error") + + // Ensure state is not at minimum + _, _ = entry.Stable() + _, _ = entry.Stable() + + // Test error handling + t.Run("returns error and resets backoff", func(t *testing.T) { + result, err := entry.Error(testErr) + assert.Equal(t, testErr, err, "Should return the passed error") + assert.Equal(t, 0*time.Second, getRequeueAfter(result, err), "Should have zero requeue time") + }) + + // After error, stable should continue from minimum + t.Run("stable continues properly after error", func(t *testing.T) { + result, err := entry.Stable() + require.NoError(t, err) + assert.Equal(t, time.Second, getRequeueAfter(result, err)) + + result, err = entry.Stable() + require.NoError(t, err) + assert.Equal(t, 2*time.Second, getRequeueAfter(result, err)) + }) +} + +func TestEntry_Never(t *testing.T) { + // Setup + store := NewStore(time.Second, time.Minute, 2) + entry := newEntry(store) + + // Test Never behavior + t.Run("returns empty result", func(t *testing.T) { + result, err := entry.Never() + require.NoError(t, err) + assert.Equal(t, time.Duration(0), getRequeueAfter(result, err)) + }) +} diff --git a/pkg/smartrequeue/example_test.go b/pkg/smartrequeue/example_test.go new file mode 100644 index 0000000..3261c59 --- /dev/null +++ b/pkg/smartrequeue/example_test.go @@ -0,0 +1,44 @@ +package smartrequeue_test + +import ( + "fmt" + "time" + + "github.com/openmcp-project/cluster-provider-kind/pkg/smartrequeue" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// This example shows how to use the SmartRequeue package in a Kubernetes controller. +func Example_controllerUsage() { + // Create a store with min and max requeue intervals + store := smartrequeue.NewStore(5*time.Second, 10*time.Minute, 2.0) + + // In your controller's Reconcile function: + reconcileFunction := func(_ ctrl.Request) (ctrl.Result, error) { + // Create a dummy object representing what you'd get from the client + var obj client.Object // In real code: Get this from the client + + // Get the Entry for this specific object + entry := store.For(obj) + + // Determine the state of the external resource... + inProgress := false // This would be determined by your logic + errOccurred := false // This would be determined by your logic + + if errOccurred { + // Handle error case + err := fmt.Errorf("something went wrong") + return entry.Error(err) + } else if inProgress { + // Resource is changing - check back soon + return entry.Progressing() + } else { + // Resource is stable - gradually back off + return entry.Stable() + } + } + + // Call the reconcile function + _, _ = reconcileFunction(ctrl.Request{}) +} diff --git a/pkg/smartrequeue/store.go b/pkg/smartrequeue/store.go index d722bee..13ce197 100644 --- a/pkg/smartrequeue/store.go +++ b/pkg/smartrequeue/store.go @@ -5,117 +5,114 @@ import ( "sync" "time" - ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" ) +// Store is used to manage requeue entries for different objects. +// It holds a map of entries indexed by a key that uniquely identifies the object. +type Store struct { + minInterval time.Duration + maxInterval time.Duration + multiplier float32 + objects map[key]*Entry + mu sync.RWMutex // Using RWMutex for better read concurrency +} + +// NewStore creates a new Store with the specified minimum and maximum intervals +// and a multiplier for the exponential backoff logic. func NewStore(minInterval, maxInterval time.Duration, multiplier float32) *Store { + if minInterval <= 0 { + minInterval = 1 * time.Second // Safe default + } + + if maxInterval < minInterval { + maxInterval = minInterval * 60 // Safe default: 1 minute or 60x min + } + + if multiplier <= 1.0 { + multiplier = 2.0 // Safe default: double each time + } + return &Store{ minInterval: minInterval, maxInterval: maxInterval, multiplier: multiplier, - objects: map[key]*Entry{}, + objects: make(map[key]*Entry), } } -type Store struct { - minInterval time.Duration - maxInterval time.Duration - multiplier float32 - objects map[key]*Entry - objectsLock sync.Mutex -} - +// For gets or creates an Entry for the specified object. func (s *Store) For(obj client.Object) *Entry { - s.objectsLock.Lock() - defer s.objectsLock.Unlock() + key := keyFromObject(obj) + + // Try read lock first for better concurrency + s.mu.RLock() + entry, exists := s.objects[key] + s.mu.RUnlock() + + if exists { + return entry + } - objKey := keyFromObject(obj) - entry, ok := s.objects[objKey] + // Need to create a new entry + s.mu.Lock() + defer s.mu.Unlock() - if !ok { - entry = newEntry(s) - s.objects[objKey] = entry + // Check again in case another goroutine created it while we were waiting + entry, exists = s.objects[key] + if !exists { + entry = &Entry{ + store: s, + nextDuration: s.minInterval, + } + s.objects[key] = entry } return entry } +// Clear removes all entries from the store (mainly useful for testing). +func (s *Store) Clear() { + s.mu.Lock() + defer s.mu.Unlock() + + s.objects = make(map[key]*Entry) +} + +// deleteEntry removes an entry from the store. func (s *Store) deleteEntry(toDelete *Entry) { - s.objectsLock.Lock() - defer s.objectsLock.Unlock() + s.mu.Lock() + defer s.mu.Unlock() - for i, entry := range s.objects { + for k, entry := range s.objects { if entry == toDelete { - delete(s.objects, i) + delete(s.objects, k) break } } } -func (s *Store) cap(next time.Duration) time.Duration { - if next > s.maxInterval { - return s.maxInterval +// keyFromObject generates a unique key for a client.Object. +func keyFromObject(obj client.Object) key { + kind := "" + if obj != nil { + kind = obj.GetObjectKind().GroupVersionKind().Kind + if kind == "" { + // Fallback if Kind is not set in GroupVersionKind + kind = reflect.TypeOf(obj).Elem().Name() + } } - return next -} -func keyFromObject(obj client.Object) key { return key{ - Kind: reflect.TypeOf(obj).Elem().Name(), + Kind: kind, Name: obj.GetName(), Namespace: obj.GetNamespace(), } } +// key uniquely identifies a Kubernetes object. type key struct { Kind string Name string Namespace string } - -func newEntry(s *Store) *Entry { - return &Entry{ - store: s, - nextDuration: s.minInterval, - } -} - -type Entry struct { - store *Store - nextDuration time.Duration -} - -// Error resets the duration to the minInterval and returns an empty Result and the error -// so that the controller-runtime can handle the exponential backoff for errors. -func (e *Entry) Error(err error) (ctrl.Result, error) { - e.nextDuration = e.store.minInterval - e.setNext() - return ctrl.Result{}, err -} - -// Stable returns a Result and increments the interval for the next iteration. -// Used when the external resource is stable (healthy or unhealthy). -func (e *Entry) Stable() (ctrl.Result, error) { - defer e.setNext() - return ctrl.Result{RequeueAfter: e.nextDuration}, nil -} - -// Progressing resets the duration to the minInterval and returns a Result with that interval. -// Used when the external resource is still doing something (creating, deleting, updating, etc.) -func (e *Entry) Progressing() (ctrl.Result, error) { - e.nextDuration = e.store.minInterval - defer e.setNext() - return ctrl.Result{RequeueAfter: e.nextDuration}, nil -} - -// Never deletes the entry from the store and returns an empty Result. -func (e *Entry) Never() (ctrl.Result, error) { - e.store.deleteEntry(e) - return ctrl.Result{}, nil -} - -func (e *Entry) setNext() { - e.nextDuration = time.Duration(float32(e.nextDuration) * e.store.multiplier) - e.nextDuration = e.store.cap(e.nextDuration) -} diff --git a/pkg/smartrequeue/store_test.go b/pkg/smartrequeue/store_test.go index dc6a246..7b347ea 100644 --- a/pkg/smartrequeue/store_test.go +++ b/pkg/smartrequeue/store_test.go @@ -1,36 +1,185 @@ package smartrequeue import ( + "fmt" + "sync" "testing" "time" + "github.com/openmcp-project/openmcp-operator/api/clusters/v1alpha1" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" ) -func Test_Entry(t *testing.T) { - entry := newEntry(NewStore(time.Second, time.Minute, 2)) +func TestFor(t *testing.T) { + tests := []struct { + name string + firstObj client.Object + secondObj client.Object + expectSame bool + description string + }{ + { + name: "same object returns same entry", + firstObj: &v1alpha1.Cluster{ + ObjectMeta: ctrl.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + }, + secondObj: &v1alpha1.Cluster{ + ObjectMeta: ctrl.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + }, + expectSame: true, + description: "Expected to get the same entry back", + }, + { + name: "different namespace returns different entry", + firstObj: &v1alpha1.Cluster{ + ObjectMeta: ctrl.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + }, + secondObj: &v1alpha1.Cluster{ + ObjectMeta: ctrl.ObjectMeta{ + Name: "test", + Namespace: "test2", + }, + }, + expectSame: false, + description: "Expected to get a different entry back", + }, + { + name: "different name returns different entry", + firstObj: &v1alpha1.Cluster{ + ObjectMeta: ctrl.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + }, + secondObj: &v1alpha1.Cluster{ + ObjectMeta: ctrl.ObjectMeta{ + Name: "test2", + Namespace: "test", + }, + }, + expectSame: false, + description: "Expected to get a different entry back", + }, + { + name: "different kind returns different entry", + firstObj: &v1alpha1.Cluster{ + ObjectMeta: ctrl.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + }, + secondObj: &v1alpha1.AccessRequest{ + ObjectMeta: ctrl.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + }, + expectSame: false, + description: "Expected to get a different entry back", + }, + } - assert.Equal(t, 1*time.Second, getRequeueAfter(entry.Stable())) - assert.Equal(t, 2*time.Second, getRequeueAfter(entry.Stable())) - assert.Equal(t, 4*time.Second, getRequeueAfter(entry.Stable())) - assert.Equal(t, 8*time.Second, getRequeueAfter(entry.Stable())) - assert.Equal(t, 16*time.Second, getRequeueAfter(entry.Stable())) - assert.Equal(t, 32*time.Second, getRequeueAfter(entry.Stable())) - assert.Equal(t, 60*time.Second, getRequeueAfter(entry.Stable())) - assert.Equal(t, 60*time.Second, getRequeueAfter(entry.Stable())) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + store := NewStore(time.Second, time.Minute, 2) + entry1 := store.For(tt.firstObj) - assert.Equal(t, 1*time.Second, getRequeueAfter(entry.Progressing())) - assert.Equal(t, 1*time.Second, getRequeueAfter(entry.Progressing())) + assert.NotNil(t, entry1, "Expected entry to be created") + result, err := entry1.Stable() + require.NoError(t, err) + assert.Equal(t, 1*time.Second, getRequeueAfter(result, err)) - assert.Equal(t, 2*time.Second, getRequeueAfter(entry.Stable())) - assert.Equal(t, 4*time.Second, getRequeueAfter(entry.Stable())) + entry2 := store.For(tt.secondObj) - assert.Equal(t, 0*time.Second, getRequeueAfter(entry.Error(assert.AnError))) - assert.Equal(t, 2*time.Second, getRequeueAfter(entry.Stable())) - assert.Equal(t, 4*time.Second, getRequeueAfter(entry.Stable())) + if tt.expectSame { + assert.Same(t, entry1, entry2, tt.description) + } else { + assert.NotSame(t, entry1, entry2, tt.description) + } + }) + } } -func getRequeueAfter(res ctrl.Result, _ error) time.Duration { - return res.RequeueAfter.Round(time.Second) +// TestClear ensures the Clear method removes all entries +func TestClear(t *testing.T) { + store := NewStore(time.Second, time.Minute, 2) + + // Add some entries + obj1 := &v1alpha1.Cluster{ + ObjectMeta: ctrl.ObjectMeta{ + Name: "test1", + Namespace: "test", + }, + } + + obj2 := &v1alpha1.Cluster{ + ObjectMeta: ctrl.ObjectMeta{ + Name: "test2", + Namespace: "test", + }, + } + + // Get entries to populate the store + entry1 := store.For(obj1) + entry2 := store.For(obj2) + + // Verify entries exist + assert.NotNil(t, entry1) + assert.NotNil(t, entry2) + + // Clear the store + store.Clear() + + // Get entries again - they should be new instances + entry1After := store.For(obj1) + entry2After := store.For(obj2) + + // Verify they're different instances + assert.NotSame(t, entry1, entry1After) + assert.NotSame(t, entry2, entry2After) +} + +// TestConcurrentAccess tests that the store handles concurrent access properly +func TestConcurrentAccess(t *testing.T) { + store := NewStore(time.Second, time.Minute, 2) + + // Create a series of objects + const numObjects = 100 + objects := make([]client.Object, numObjects) + + for i := 0; i < numObjects; i++ { + objects[i] = &v1alpha1.Cluster{ + ObjectMeta: ctrl.ObjectMeta{ + Name: fmt.Sprintf("test-%d", i), + Namespace: "test", + }, + } + } + + // Access concurrently + var wg sync.WaitGroup + wg.Add(numObjects) + + for i := 0; i < numObjects; i++ { + go func(idx int) { + defer wg.Done() + obj := objects[idx] + entry := store.For(obj) + _, _ = entry.Stable() // Just exercise the API + }(i) + } + + wg.Wait() } diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go deleted file mode 100644 index 40348cf..0000000 --- a/test/e2e/e2e_suite_test.go +++ /dev/null @@ -1,89 +0,0 @@ -/* -Copyright 2025. - -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 e2e - -import ( - "fmt" - "os" - "os/exec" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/openmcp-project/cluster-provider-kind/test/utils" -) - -var ( - // Optional Environment Variables: - // - CERT_MANAGER_INSTALL_SKIP=true: Skips CertManager installation during test setup. - // These variables are useful if CertManager is already installed, avoiding - // re-installation and conflicts. - skipCertManagerInstall = os.Getenv("CERT_MANAGER_INSTALL_SKIP") == "true" - // isCertManagerAlreadyInstalled will be set true when CertManager CRDs be found on the cluster - isCertManagerAlreadyInstalled = false - - // projectImage is the name of the image which will be build and loaded - // with the code source changes to be tested. - projectImage = "example.com/cluster-provider-kind:v0.0.1" -) - -// TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated, -// temporary environment to validate project changes with the the purposed to be used in CI jobs. -// The default setup requires Kind, builds/loads the Manager Docker image locally, and installs -// CertManager. -func TestE2E(t *testing.T) { - RegisterFailHandler(Fail) - _, _ = fmt.Fprintf(GinkgoWriter, "Starting cluster-provider-kind integration test suite\n") - RunSpecs(t, "e2e suite") -} - -var _ = BeforeSuite(func() { - By("building the manager(Operator) image") - cmd := exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", projectImage)) - _, err := utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to build the manager(Operator) image") - - // TODO(user): If you want to change the e2e test vendor from Kind, ensure the image is - // built and available before running the tests. Also, remove the following block. - By("loading the manager(Operator) image on Kind") - err = utils.LoadImageToKindClusterWithName(projectImage) - ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to load the manager(Operator) image into Kind") - - // The tests-e2e are intended to run on a temporary cluster that is created and destroyed for testing. - // To prevent errors when tests run in environments with CertManager already installed, - // we check for its presence before execution. - // Setup CertManager before the suite if not skipped and if not already installed - if !skipCertManagerInstall { - By("checking if cert manager is installed already") - isCertManagerAlreadyInstalled = utils.IsCertManagerCRDsInstalled() - if !isCertManagerAlreadyInstalled { - _, _ = fmt.Fprintf(GinkgoWriter, "Installing CertManager...\n") - Expect(utils.InstallCertManager()).To(Succeed(), "Failed to install CertManager") - } else { - _, _ = fmt.Fprintf(GinkgoWriter, "WARNING: CertManager is already installed. Skipping installation...\n") - } - } -}) - -var _ = AfterSuite(func() { - // Teardown CertManager after the suite if not skipped and if it was not already installed - if !skipCertManagerInstall && !isCertManagerAlreadyInstalled { - _, _ = fmt.Fprintf(GinkgoWriter, "Uninstalling CertManager...\n") - utils.UninstallCertManager() - } -}) diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go deleted file mode 100644 index cb384f0..0000000 --- a/test/e2e/e2e_test.go +++ /dev/null @@ -1,329 +0,0 @@ -/* -Copyright 2025. - -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 e2e - -import ( - "encoding/json" - "fmt" - "os" - "os/exec" - "path/filepath" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/openmcp-project/cluster-provider-kind/test/utils" -) - -// namespace where the project is deployed in -const namespace = "cluster-provider-kind-system" - -// serviceAccountName created for the project -const serviceAccountName = "cluster-provider-kind-controller-manager" - -// metricsServiceName is the name of the metrics service of the project -const metricsServiceName = "cluster-provider-kind-controller-manager-metrics-service" - -// metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data -const metricsRoleBindingName = "cluster-provider-kind-metrics-binding" - -var _ = Describe("Manager", Ordered, func() { - var controllerPodName string - - // Before running the tests, set up the environment by creating the namespace, - // enforce the restricted security policy to the namespace, installing CRDs, - // and deploying the controller. - BeforeAll(func() { - By("creating manager namespace") - cmd := exec.Command("kubectl", "create", "ns", namespace) - _, err := utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to create namespace") - - By("labeling the namespace to enforce the restricted security policy") - cmd = exec.Command("kubectl", "label", "--overwrite", "ns", namespace, - "pod-security.kubernetes.io/enforce=restricted") - _, err = utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to label namespace with restricted policy") - - By("installing CRDs") - cmd = exec.Command("make", "install") - _, err = utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to install CRDs") - - By("deploying the controller-manager") - cmd = exec.Command("make", "deploy", fmt.Sprintf("IMG=%s", projectImage)) - _, err = utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to deploy the controller-manager") - }) - - // After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs, - // and deleting the namespace. - AfterAll(func() { - By("cleaning up the curl pod for metrics") - cmd := exec.Command("kubectl", "delete", "pod", "curl-metrics", "-n", namespace) - _, _ = utils.Run(cmd) - - By("undeploying the controller-manager") - cmd = exec.Command("make", "undeploy") - _, _ = utils.Run(cmd) - - By("uninstalling CRDs") - cmd = exec.Command("make", "uninstall") - _, _ = utils.Run(cmd) - - By("removing manager namespace") - cmd = exec.Command("kubectl", "delete", "ns", namespace) - _, _ = utils.Run(cmd) - }) - - // After each test, check for failures and collect logs, events, - // and pod descriptions for debugging. - AfterEach(func() { - specReport := CurrentSpecReport() - if specReport.Failed() { - By("Fetching controller manager pod logs") - cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace) - controllerLogs, err := utils.Run(cmd) - if err == nil { - _, _ = fmt.Fprintf(GinkgoWriter, "Controller logs:\n %s", controllerLogs) - } else { - _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Controller logs: %s", err) - } - - By("Fetching Kubernetes events") - cmd = exec.Command("kubectl", "get", "events", "-n", namespace, "--sort-by=.lastTimestamp") - eventsOutput, err := utils.Run(cmd) - if err == nil { - _, _ = fmt.Fprintf(GinkgoWriter, "Kubernetes events:\n%s", eventsOutput) - } else { - _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Kubernetes events: %s", err) - } - - By("Fetching curl-metrics logs") - cmd = exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace) - metricsOutput, err := utils.Run(cmd) - if err == nil { - _, _ = fmt.Fprintf(GinkgoWriter, "Metrics logs:\n %s", metricsOutput) - } else { - _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get curl-metrics logs: %s", err) - } - - By("Fetching controller manager pod description") - cmd = exec.Command("kubectl", "describe", "pod", controllerPodName, "-n", namespace) - podDescription, err := utils.Run(cmd) - if err == nil { - fmt.Println("Pod description:\n", podDescription) - } else { - fmt.Println("Failed to describe controller pod") - } - } - }) - - SetDefaultEventuallyTimeout(2 * time.Minute) - SetDefaultEventuallyPollingInterval(time.Second) - - Context("Manager", func() { - It("should run successfully", func() { - By("validating that the controller-manager pod is running as expected") - verifyControllerUp := func(g Gomega) { - // Get the name of the controller-manager pod - cmd := exec.Command("kubectl", "get", - "pods", "-l", "control-plane=controller-manager", - "-o", "go-template={{ range .items }}"+ - "{{ if not .metadata.deletionTimestamp }}"+ - "{{ .metadata.name }}"+ - "{{ \"\\n\" }}{{ end }}{{ end }}", - "-n", namespace, - ) - - podOutput, err := utils.Run(cmd) - g.Expect(err).NotTo(HaveOccurred(), "Failed to retrieve controller-manager pod information") - podNames := utils.GetNonEmptyLines(podOutput) - g.Expect(podNames).To(HaveLen(1), "expected 1 controller pod running") - controllerPodName = podNames[0] - g.Expect(controllerPodName).To(ContainSubstring("controller-manager")) - - // Validate the pod's status - cmd = exec.Command("kubectl", "get", - "pods", controllerPodName, "-o", "jsonpath={.status.phase}", - "-n", namespace, - ) - output, err := utils.Run(cmd) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(output).To(Equal("Running"), "Incorrect controller-manager pod status") - } - Eventually(verifyControllerUp).Should(Succeed()) - }) - - It("should ensure the metrics endpoint is serving metrics", func() { - By("creating a ClusterRoleBinding for the service account to allow access to metrics") - cmd := exec.Command("kubectl", "create", "clusterrolebinding", metricsRoleBindingName, - "--clusterrole=cluster-provider-kind-metrics-reader", - fmt.Sprintf("--serviceaccount=%s:%s", namespace, serviceAccountName), - ) - _, err := utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to create ClusterRoleBinding") - - By("validating that the metrics service is available") - cmd = exec.Command("kubectl", "get", "service", metricsServiceName, "-n", namespace) - _, err = utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Metrics service should exist") - - By("getting the service account token") - token, err := serviceAccountToken() - Expect(err).NotTo(HaveOccurred()) - Expect(token).NotTo(BeEmpty()) - - By("waiting for the metrics endpoint to be ready") - verifyMetricsEndpointReady := func(g Gomega) { - cmd := exec.Command("kubectl", "get", "endpoints", metricsServiceName, "-n", namespace) - output, err := utils.Run(cmd) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(output).To(ContainSubstring("8443"), "Metrics endpoint is not ready") - } - Eventually(verifyMetricsEndpointReady).Should(Succeed()) - - By("verifying that the controller manager is serving the metrics server") - verifyMetricsServerStarted := func(g Gomega) { - cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace) - output, err := utils.Run(cmd) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(output).To(ContainSubstring("controller-runtime.metrics\tServing metrics server"), - "Metrics server not yet started") - } - Eventually(verifyMetricsServerStarted).Should(Succeed()) - - By("creating the curl-metrics pod to access the metrics endpoint") - cmd = exec.Command("kubectl", "run", "curl-metrics", "--restart=Never", - "--namespace", namespace, - "--image=curlimages/curl:latest", - "--overrides", - fmt.Sprintf(`{ - "spec": { - "containers": [{ - "name": "curl", - "image": "curlimages/curl:latest", - "command": ["/bin/sh", "-c"], - "args": ["curl -v -k -H 'Authorization: Bearer %s' https://%s.%s.svc.cluster.local:8443/metrics"], - "securityContext": { - "allowPrivilegeEscalation": false, - "capabilities": { - "drop": ["ALL"] - }, - "runAsNonRoot": true, - "runAsUser": 1000, - "seccompProfile": { - "type": "RuntimeDefault" - } - } - }], - "serviceAccount": "%s" - } - }`, token, metricsServiceName, namespace, serviceAccountName)) - _, err = utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to create curl-metrics pod") - - By("waiting for the curl-metrics pod to complete.") - verifyCurlUp := func(g Gomega) { - cmd := exec.Command("kubectl", "get", "pods", "curl-metrics", - "-o", "jsonpath={.status.phase}", - "-n", namespace) - output, err := utils.Run(cmd) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(output).To(Equal("Succeeded"), "curl pod in wrong status") - } - Eventually(verifyCurlUp, 5*time.Minute).Should(Succeed()) - - By("getting the metrics by checking curl-metrics logs") - metricsOutput := getMetricsOutput() - Expect(metricsOutput).To(ContainSubstring( - "controller_runtime_reconcile_total", - )) - }) - - // +kubebuilder:scaffold:e2e-webhooks-checks - - // TODO: Customize the e2e test suite with scenarios specific to your project. - // Consider applying sample/CR(s) and check their status and/or verifying - // the reconciliation by using the metrics, i.e.: - // metricsOutput := getMetricsOutput() - // Expect(metricsOutput).To(ContainSubstring( - // fmt.Sprintf(`controller_runtime_reconcile_total{controller="%s",result="success"} 1`, - // strings.ToLower(), - // )) - }) -}) - -// serviceAccountToken returns a token for the specified service account in the given namespace. -// It uses the Kubernetes TokenRequest API to generate a token by directly sending a request -// and parsing the resulting token from the API response. -func serviceAccountToken() (string, error) { - const tokenRequestRawString = `{ - "apiVersion": "authentication.k8s.io/v1", - "kind": "TokenRequest" - }` - - // Temporary file to store the token request - secretName := fmt.Sprintf("%s-token-request", serviceAccountName) - tokenRequestFile := filepath.Join("/tmp", secretName) - err := os.WriteFile(tokenRequestFile, []byte(tokenRequestRawString), os.FileMode(0o644)) - if err != nil { - return "", err - } - - var out string - verifyTokenCreation := func(g Gomega) { - // Execute kubectl command to create the token - cmd := exec.Command("kubectl", "create", "--raw", fmt.Sprintf( - "/api/v1/namespaces/%s/serviceaccounts/%s/token", - namespace, - serviceAccountName, - ), "-f", tokenRequestFile) - - output, err := cmd.CombinedOutput() - g.Expect(err).NotTo(HaveOccurred()) - - // Parse the JSON output to extract the token - var token tokenRequest - err = json.Unmarshal(output, &token) - g.Expect(err).NotTo(HaveOccurred()) - - out = token.Status.Token - } - Eventually(verifyTokenCreation).Should(Succeed()) - - return out, err -} - -// getMetricsOutput retrieves and returns the logs from the curl pod used to access the metrics endpoint. -func getMetricsOutput() string { - By("getting the curl-metrics logs") - cmd := exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace) - metricsOutput, err := utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to retrieve logs from curl pod") - Expect(metricsOutput).To(ContainSubstring("< HTTP/1.1 200 OK")) - return metricsOutput -} - -// tokenRequest is a simplified representation of the Kubernetes TokenRequest API response, -// containing only the token field that we need to extract. -type tokenRequest struct { - Status struct { - Token string `json:"token"` - } `json:"status"` -} diff --git a/test/utils/utils.go b/test/utils/utils.go deleted file mode 100644 index ea7c494..0000000 --- a/test/utils/utils.go +++ /dev/null @@ -1,251 +0,0 @@ -/* -Copyright 2025. - -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 utils - -import ( - "bufio" - "bytes" - "fmt" - "os" - "os/exec" - "strings" - - . "github.com/onsi/ginkgo/v2" //nolint:golint,revive,staticcheck -) - -const ( - prometheusOperatorVersion = "v0.77.1" - prometheusOperatorURL = "https://github.com/prometheus-operator/prometheus-operator/" + - "releases/download/%s/bundle.yaml" - - certmanagerVersion = "v1.16.3" - certmanagerURLTmpl = "https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml" -) - -func warnError(err error) { - _, _ = fmt.Fprintf(GinkgoWriter, "warning: %v\n", err) -} - -// Run executes the provided command within this context -func Run(cmd *exec.Cmd) (string, error) { - dir, _ := GetProjectDir() - cmd.Dir = dir - - if err := os.Chdir(cmd.Dir); err != nil { - _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %s\n", err) - } - - cmd.Env = append(os.Environ(), "GO111MODULE=on") - command := strings.Join(cmd.Args, " ") - _, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command) - output, err := cmd.CombinedOutput() - if err != nil { - return string(output), fmt.Errorf("%s failed with error: (%v) %s", command, err, string(output)) - } - - return string(output), nil -} - -// InstallPrometheusOperator installs the prometheus Operator to be used to export the enabled metrics. -func InstallPrometheusOperator() error { - url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) - cmd := exec.Command("kubectl", "create", "-f", url) - _, err := Run(cmd) - return err -} - -// UninstallPrometheusOperator uninstalls the prometheus -func UninstallPrometheusOperator() { - url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) - cmd := exec.Command("kubectl", "delete", "-f", url) - if _, err := Run(cmd); err != nil { - warnError(err) - } -} - -// IsPrometheusCRDsInstalled checks if any Prometheus CRDs are installed -// by verifying the existence of key CRDs related to Prometheus. -func IsPrometheusCRDsInstalled() bool { - // List of common Prometheus CRDs - prometheusCRDs := []string{ - "prometheuses.monitoring.coreos.com", - "prometheusrules.monitoring.coreos.com", - "prometheusagents.monitoring.coreos.com", - } - - cmd := exec.Command("kubectl", "get", "crds", "-o", "custom-columns=NAME:.metadata.name") - output, err := Run(cmd) - if err != nil { - return false - } - crdList := GetNonEmptyLines(output) - for _, crd := range prometheusCRDs { - for _, line := range crdList { - if strings.Contains(line, crd) { - return true - } - } - } - - return false -} - -// UninstallCertManager uninstalls the cert manager -func UninstallCertManager() { - url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion) - cmd := exec.Command("kubectl", "delete", "-f", url) - if _, err := Run(cmd); err != nil { - warnError(err) - } -} - -// InstallCertManager installs the cert manager bundle. -func InstallCertManager() error { - url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion) - cmd := exec.Command("kubectl", "apply", "-f", url) - if _, err := Run(cmd); err != nil { - return err - } - // Wait for cert-manager-webhook to be ready, which can take time if cert-manager - // was re-installed after uninstalling on a cluster. - cmd = exec.Command("kubectl", "wait", "deployment.apps/cert-manager-webhook", - "--for", "condition=Available", - "--namespace", "cert-manager", - "--timeout", "5m", - ) - - _, err := Run(cmd) - return err -} - -// IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed -// by verifying the existence of key CRDs related to Cert Manager. -func IsCertManagerCRDsInstalled() bool { - // List of common Cert Manager CRDs - certManagerCRDs := []string{ - "certificates.cert-manager.io", - "issuers.cert-manager.io", - "clusterissuers.cert-manager.io", - "certificaterequests.cert-manager.io", - "orders.acme.cert-manager.io", - "challenges.acme.cert-manager.io", - } - - // Execute the kubectl command to get all CRDs - cmd := exec.Command("kubectl", "get", "crds") - output, err := Run(cmd) - if err != nil { - return false - } - - // Check if any of the Cert Manager CRDs are present - crdList := GetNonEmptyLines(output) - for _, crd := range certManagerCRDs { - for _, line := range crdList { - if strings.Contains(line, crd) { - return true - } - } - } - - return false -} - -// LoadImageToKindClusterWithName loads a local docker image to the kind cluster -func LoadImageToKindClusterWithName(name string) error { - cluster := "kind" - if v, ok := os.LookupEnv("KIND_CLUSTER"); ok { - cluster = v - } - kindOptions := []string{"load", "docker-image", name, "--name", cluster} - cmd := exec.Command("kind", kindOptions...) - _, err := Run(cmd) - return err -} - -// GetNonEmptyLines converts given command output string into individual objects -// according to line breakers, and ignores the empty elements in it. -func GetNonEmptyLines(output string) []string { - var res []string - elements := strings.Split(output, "\n") - for _, element := range elements { - if element != "" { - res = append(res, element) - } - } - - return res -} - -// GetProjectDir will return the directory where the project is -func GetProjectDir() (string, error) { - wd, err := os.Getwd() - if err != nil { - return wd, err - } - wd = strings.ReplaceAll(wd, "/test/e2e", "") - return wd, nil -} - -// UncommentCode searches for target in the file and remove the comment prefix -// of the target content. The target content may span multiple lines. -func UncommentCode(filename, target, prefix string) error { - // false positive - // nolint:gosec - content, err := os.ReadFile(filename) - if err != nil { - return err - } - strContent := string(content) - - idx := strings.Index(strContent, target) - if idx < 0 { - return fmt.Errorf("unable to find the code %s to be uncomment", target) - } - - out := new(bytes.Buffer) - _, err = out.Write(content[:idx]) - if err != nil { - return err - } - - scanner := bufio.NewScanner(bytes.NewBufferString(target)) - if !scanner.Scan() { - return nil - } - for { - _, err := out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)) - if err != nil { - return err - } - // Avoid writing a newline in case the previous line was the last in target. - if !scanner.Scan() { - break - } - if _, err := out.WriteString("\n"); err != nil { - return err - } - } - - _, err = out.Write(content[idx+len(target):]) - if err != nil { - return err - } - // false positive - // nolint:gosec - return os.WriteFile(filename, out.Bytes(), 0644) -}