From 601a01177ed667f00dc235758369c6736778f0ed Mon Sep 17 00:00:00 2001 From: Christoph Barbian Date: Sun, 14 Jun 2026 14:40:49 +0200 Subject: [PATCH 1/3] add blueprints source --- Dockerfile | 2 +- api/v1alpha1/types.go | 164 +++++++++-- api/v1alpha1/util.go | 4 +- api/v1alpha1/zz_generated.deepcopy.go | 215 +++++++++++++-- crds/core.cs.sap.com_blueprints.yaml | 56 ++++ crds/core.cs.sap.com_blueprintversions.yaml | 68 +++++ crds/core.cs.sap.com_components.yaml | 36 ++- go.mod | 104 +++---- go.sum | 256 ++++++++++-------- internal/cache/component/cache.go | 221 +++++++++++++++ internal/controllers/blueprint/handlers.go | 54 ++++ internal/controllers/blueprint/reconciler.go | 121 +++++++++ internal/controllers/component/handlers.go | 181 +++++++++++++ .../controllers/component}/hooks.go | 11 +- internal/controllers/component/reconciler.go | 95 +++++++ internal/generator/factory.go | 83 ++++-- internal/generator/generator.go | 12 +- internal/httprepository/checker.go | 79 ++++++ .../httprepository/util/artifact.go | 0 internal/sources/flux/cache/cache.go | 163 ----------- .../sources/httprepository/checker/checker.go | 75 ----- .../versioned/fake/clientset_generated.go | 6 +- .../core.cs.sap.com/v1alpha1/blueprint.go | 57 ++++ .../v1alpha1/blueprintversion.go | 57 ++++ .../v1alpha1/core.cs.sap.com_client.go | 10 + .../v1alpha1/fake/fake_blueprint.go | 39 +++ .../v1alpha1/fake/fake_blueprintversion.go | 41 +++ .../fake/fake_core.cs.sap.com_client.go | 8 + .../v1alpha1/generated_expansion.go | 4 + .../core.cs.sap.com/v1alpha1/blueprint.go | 105 +++++++ .../v1alpha1/blueprintversion.go | 105 +++++++ .../core.cs.sap.com/v1alpha1/component.go | 48 ++-- .../core.cs.sap.com/v1alpha1/interface.go | 14 + .../informers/externalversions/factory.go | 110 ++++++-- .../informers/externalversions/generic.go | 4 + .../internalinterfaces/factory_interfaces.go | 19 ++ .../core.cs.sap.com/v1alpha1/blueprint.go | 59 ++++ .../v1alpha1/blueprintversion.go | 59 ++++ .../v1alpha1/expansion_generated.go | 16 ++ pkg/{operator/util.go => meta/constants.go} | 10 +- .../flux/types/source.go => pkg/meta/types.go | 4 +- pkg/operator/cache.go | 104 ------- pkg/operator/operator.go | 74 ++--- 43 files changed, 2257 insertions(+), 696 deletions(-) create mode 100644 crds/core.cs.sap.com_blueprints.yaml create mode 100644 crds/core.cs.sap.com_blueprintversions.yaml create mode 100644 internal/cache/component/cache.go create mode 100644 internal/controllers/blueprint/handlers.go create mode 100644 internal/controllers/blueprint/reconciler.go create mode 100644 internal/controllers/component/handlers.go rename {pkg/operator => internal/controllers/component}/hooks.go (94%) create mode 100644 internal/controllers/component/reconciler.go create mode 100644 internal/httprepository/checker.go rename internal/{sources => }/httprepository/util/artifact.go (100%) delete mode 100644 internal/sources/flux/cache/cache.go delete mode 100644 internal/sources/httprepository/checker/checker.go create mode 100644 pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/blueprint.go create mode 100644 pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/blueprintversion.go create mode 100644 pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/fake/fake_blueprint.go create mode 100644 pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/fake/fake_blueprintversion.go create mode 100644 pkg/client/informers/externalversions/core.cs.sap.com/v1alpha1/blueprint.go create mode 100644 pkg/client/informers/externalversions/core.cs.sap.com/v1alpha1/blueprintversion.go create mode 100644 pkg/client/listers/core.cs.sap.com/v1alpha1/blueprint.go create mode 100644 pkg/client/listers/core.cs.sap.com/v1alpha1/blueprintversion.go rename pkg/{operator/util.go => meta/constants.go} (52%) rename internal/sources/flux/types/source.go => pkg/meta/types.go (88%) delete mode 100644 pkg/operator/cache.go diff --git a/Dockerfile b/Dockerfile index 30712787..a05a4ff2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM --platform=$BUILDPLATFORM golang:1.26.2 AS builder +FROM --platform=$BUILDPLATFORM golang:1.26.4 AS builder ARG TARGETOS ARG TARGETARCH diff --git a/api/v1alpha1/types.go b/api/v1alpha1/types.go index aa52a4c3..f01462b6 100644 --- a/api/v1alpha1/types.go +++ b/api/v1alpha1/types.go @@ -14,7 +14,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime" apitypes "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" @@ -26,9 +26,9 @@ import ( "github.com/sap/component-operator-runtime/pkg/manifests" componentoperatorruntimetypes "github.com/sap/component-operator-runtime/pkg/types" + httprepositoryutil "github.com/sap/component-operator/internal/httprepository/util" "github.com/sap/component-operator/internal/object" - flux "github.com/sap/component-operator/internal/sources/flux/types" - httprepository "github.com/sap/component-operator/internal/sources/httprepository/util" + "github.com/sap/component-operator/pkg/meta" ) // ComponentSpec defines the desired state of Component. @@ -56,20 +56,21 @@ type ComponentSpec struct { Dependencies []Dependency `json:"dependencies,omitempty"` } -// +kubebuilder:validation:XValidation:rule="has(self.httpRepository) && !has(self.fluxGitRepository) && !has(self.fluxOciRepository) && !has(self.fluxBucket) && !has(self.fluxHelmChart) || !has(self.httpRepository) && has(self.fluxGitRepository) && !has(self.fluxOciRepository) && !has(self.fluxBucket) && !has(self.fluxHelmChart) || !has(self.httpRepository) && !has(self.fluxGitRepository) && has(self.fluxOciRepository) && !has(self.fluxBucket) && !has(self.fluxHelmChart) || !has(self.httpRepository) && !has(self.fluxGitRepository) && !has(self.fluxOciRepository) && has(self.fluxBucket) && !has(self.fluxHelmChart) || !has(self.httpRepository) && !has(self.fluxGitRepository) && !has(self.fluxOciRepository) && !has(self.fluxBucket) && has(self.fluxHelmChart)",message="Exactly one of 'httpRepository' or 'fluxGitRepository' or 'fluxOciRepository' or 'fluxBucket' or 'fluxHelmChart' must be provided" +// +kubebuilder:validation:XValidation:rule="has(self.blueprint) && !has(self.httpRepository) && !has(self.fluxGitRepository) && !has(self.fluxOciRepository) && !has(self.fluxBucket) && !has(self.fluxHelmChart) || !has(self.blueprint) && has(self.httpRepository) && !has(self.fluxGitRepository) && !has(self.fluxOciRepository) && !has(self.fluxBucket) && !has(self.fluxHelmChart) || !has(self.blueprint) && !has(self.httpRepository) && has(self.fluxGitRepository) && !has(self.fluxOciRepository) && !has(self.fluxBucket) && !has(self.fluxHelmChart) || !has(self.blueprint) && !has(self.httpRepository) && !has(self.fluxGitRepository) && has(self.fluxOciRepository) && !has(self.fluxBucket) && !has(self.fluxHelmChart) || !has(self.blueprint) && !has(self.httpRepository) && !has(self.fluxGitRepository) && !has(self.fluxOciRepository) && has(self.fluxBucket) && !has(self.fluxHelmChart) || !has(self.blueprint) && !has(self.httpRepository) && !has(self.fluxGitRepository) && !has(self.fluxOciRepository) && !has(self.fluxBucket) && has(self.fluxHelmChart)",message="Exactly one of 'blueprint' or 'httpRepository' or 'fluxGitRepository' or 'fluxOciRepository' or 'fluxBucket' or 'fluxHelmChart' must be provided" // SourceReference models the source of the templates used to render the dependent resources. // Exactly one of the options must be provided. Before accessing the Artifact() method, // the SourceReference must be loaded by calling Load(). type SourceReference struct { - HttpRepository *HttpRepository `json:"httpRepository,omitempty"` - FluxGitRepository *FluxGitRepository `json:"fluxGitRepository,omitempty"` - FluxOciRepository *FluxOciRepository `json:"fluxOciRepository,omitempty"` - FluxBucket *FluxBucket `json:"fluxBucket,omitempty"` - FluxHelmChart *FluxHelmChart `json:"fluxHelmChart,omitempty"` - artifact Artifact `json:"-"` - digest string `json:"-"` - loaded bool `json:"-"` + Blueprint *BlueprintReference `json:"blueprint,omitempty"` + HttpRepository *HttpRepository `json:"httpRepository,omitempty"` + FluxGitRepository *FluxGitRepositoryReference `json:"fluxGitRepository,omitempty"` + FluxOciRepository *FluxOciRepositoryReference `json:"fluxOciRepository,omitempty"` + FluxBucket *FluxBucketReference `json:"fluxBucket,omitempty"` + FluxHelmChart *FluxHelmChartReference `json:"fluxHelmChart,omitempty"` + artifact Artifact `json:"-"` + digest string `json:"-"` + loaded bool `json:"-"` } var _ component.Reference[*Component] = &SourceReference{} @@ -98,8 +99,43 @@ func (r *SourceReference) Load(ctx context.Context, clnt client.Client, componen var digestData []any switch { + case sourceRef.Blueprint != nil: + blueprint := &Blueprint{} + if err := clnt.Get(ctx, apitypes.NamespacedName(sourceRef.Blueprint.WithDefaultNamespace(component.Namespace)), blueprint); err != nil { + if apierrors.IsNotFound(err) { + return componentoperatorruntimetypes.NewRetriableError(err, new(10*time.Second)) + } + return err + } + + blueprintDigest := blueprint.GetDigest() + blueprintRevision := blueprint.GetRevision() + blueprintVersion := &BlueprintVersion{ + TypeMeta: metav1.TypeMeta{ + Kind: KindBlueprintVersion, + APIVersion: GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%s--%s", blueprint.Name, blueprintDigest), + Namespace: blueprint.Namespace, + }, + Spec: BlueprintVersionSpec{ + Blueprint: blueprint.Name, + Digest: blueprintDigest, + Revision: blueprintRevision, + BlueprintSpec: blueprint.Spec, + }, + } + if err := clnt.Patch(ctx, blueprintVersion, client.Apply, client.FieldOwner(meta.Name), client.ForceOwnership); err != nil { + return err + } + + sourceRefArtifact.Url = fmt.Sprintf("blueprint://%s/%s/%s", blueprint.Namespace, blueprint.Name, blueprintDigest) + sourceRefArtifact.Digest = blueprintDigest + sourceRefArtifact.Revision = blueprintRevision + digestData = []any{sourceRefArtifact.Url, sourceRefArtifact.Digest, sourceRefArtifact.Revision} case sourceRef.HttpRepository != nil: - url, digest, revision, err := httprepository.GetArtifact(sourceRef.HttpRepository.Url, sourceRef.HttpRepository.DigestHeader, sourceRef.HttpRepository.RevisionHeader) + url, digest, revision, err := httprepositoryutil.GetArtifact(sourceRef.HttpRepository.Url, sourceRef.HttpRepository.DigestHeader, sourceRef.HttpRepository.RevisionHeader) if err != nil { return err } @@ -110,7 +146,7 @@ func (r *SourceReference) Load(ctx context.Context, clnt client.Client, componen digestData = []any{sourceRefArtifact.Url, sourceRefArtifact.Digest, sourceRefArtifact.Revision} case sourceRef.FluxGitRepository != nil, sourceRef.FluxOciRepository != nil, sourceRef.FluxBucket != nil, sourceRef.FluxHelmChart != nil: var sourceName NamespacedName - var source flux.Source + var source meta.FluxSource switch { case sourceRef.FluxGitRepository != nil: @@ -131,27 +167,27 @@ func (r *SourceReference) Load(ctx context.Context, clnt client.Client, componen if err := clnt.Get(ctx, apitypes.NamespacedName(sourceName), source); err != nil { if apimeta.IsNoMatchError(err) || apierrors.IsNotFound(err) { - return componentoperatorruntimetypes.NewRetriableError(err, ref(10*time.Second)) + return componentoperatorruntimetypes.NewRetriableError(err, new(10*time.Second)) } return err } if !object.IsReady(source) { - return componentoperatorruntimetypes.NewRetriableError(fmt.Errorf("source not ready"), ref(10*time.Second)) + return componentoperatorruntimetypes.NewRetriableError(fmt.Errorf("source not ready"), new(10*time.Second)) } artifact := source.GetArtifact() if artifact == nil { - return componentoperatorruntimetypes.NewRetriableError(fmt.Errorf("missing artifact on ready source"), ref(10*time.Second)) + return componentoperatorruntimetypes.NewRetriableError(fmt.Errorf("missing artifact on ready source"), new(10*time.Second)) } if artifact.URL == "" { - return componentoperatorruntimetypes.NewRetriableError(fmt.Errorf("source not ready (missing URL)"), ref(10*time.Second)) + return componentoperatorruntimetypes.NewRetriableError(fmt.Errorf("source not ready (missing URL)"), new(10*time.Second)) } if artifact.Digest == "" { - return componentoperatorruntimetypes.NewRetriableError(fmt.Errorf("source not ready (missing digest)"), ref(10*time.Second)) + return componentoperatorruntimetypes.NewRetriableError(fmt.Errorf("source not ready (missing digest)"), new(10*time.Second)) } if artifact.Revision == "" { - return componentoperatorruntimetypes.NewRetriableError(fmt.Errorf("source not ready (missing revision)"), ref(10*time.Second)) + return componentoperatorruntimetypes.NewRetriableError(fmt.Errorf("source not ready (missing revision)"), new(10*time.Second)) } sourceRefArtifact.Url = artifact.URL @@ -195,13 +231,19 @@ func (r *SourceReference) Artifact() Artifact { // Check if source reference equals other given source reference. func (r *SourceReference) Equals(s *SourceReference) bool { - return equal(r.HttpRepository, s.HttpRepository) && + return equal(r.Blueprint, s.Blueprint) && + equal(r.HttpRepository, s.HttpRepository) && equal(r.FluxGitRepository, s.FluxGitRepository) && equal(r.FluxOciRepository, s.FluxOciRepository) && equal(r.FluxBucket, s.FluxBucket) && equal(r.FluxHelmChart, s.FluxHelmChart) } +// Reference to a Blueprint. +type BlueprintReference struct { + NamespacedName `json:",inline"` +} + // Reference to a generic http repository. type HttpRepository struct { // URL of the source. Authentication is currently not supported. The operator will make HEAD requests to retrieve the digest/revision @@ -217,22 +259,22 @@ type HttpRepository struct { } // Reference to a flux GitRepository. -type FluxGitRepository struct { +type FluxGitRepositoryReference struct { NamespacedName `json:",inline"` } // Reference to a flux OCIRepository. -type FluxOciRepository struct { +type FluxOciRepositoryReference struct { NamespacedName `json:",inline"` } // Reference to a flux Bucket. -type FluxBucket struct { +type FluxBucketReference struct { NamespacedName `json:",inline"` } // Reference to a flux HelmChart. -type FluxHelmChart struct { +type FluxHelmChartReference struct { NamespacedName `json:",inline"` } @@ -404,10 +446,78 @@ func isComponentProcessing(c *Component) bool { return c.Status.ProcessingSince != nil && c.Status.LastObservedAt.Sub(c.Status.ProcessingSince.Time) < timeout } -func equal[T comparable](x *T, y *T) bool { - return x == nil && y == nil || x != nil && y != nil && *x == *y +// BlueprintSpec defines the desired state of Blueprint. +type BlueprintSpec struct { + Files map[string]string `json:"files,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +// +genclient + +// Blueprint is the Schema for the blueprints API. +type Blueprint struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BlueprintSpec `json:"spec"` +} + +func (b *Blueprint) GetDigest() string { + return calculateDigest(b.Spec) +} + +func (b *Blueprint) GetRevision() string { + return fmt.Sprintf("generation:%d", b.Generation) } +// +kubebuilder:object:root=true + +// BlueprintList contains a list of Blueprint. +type BlueprintList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Blueprint `json:"items"` +} + +// BlueprintVersionSpec defines the desired state of BlueprintVersion. +type BlueprintVersionSpec struct { + Blueprint string `json:"blueprint"` + Digest string `json:"digest"` + Revision string `json:"revision"` + BlueprintSpec `json:",inline"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:selectablefield:JSONPath=".spec.blueprint" +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" +// +genclient + +// BlueprintVersion is the Schema for the blueprint versions API. +type BlueprintVersion struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BlueprintVersionSpec `json:"spec"` +} + +// +kubebuilder:object:root=true + +// BlueprintVersionList contains a list of BlueprintVersion. +type BlueprintVersionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []BlueprintVersion `json:"items"` +} + +const ( + KindComponent = "Component" + KindBlueprint = "Blueprint" + KindBlueprintVersion = "BlueprintVersion" +) + func init() { SchemeBuilder.Register(&Component{}, &ComponentList{}) + SchemeBuilder.Register(&Blueprint{}, &BlueprintList{}) + SchemeBuilder.Register(&BlueprintVersion{}, &BlueprintVersionList{}) } diff --git a/api/v1alpha1/util.go b/api/v1alpha1/util.go index ada96eee..554f1207 100644 --- a/api/v1alpha1/util.go +++ b/api/v1alpha1/util.go @@ -13,8 +13,8 @@ import ( // TODO: consolidate all the util files into an internal reuse package -func ref[T any](x T) *T { - return &x +func equal[T comparable](x *T, y *T) bool { + return x == nil && y == nil || x != nil && y != nil && *x == *y } func sha256hex(data []byte) string { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 23e331c3..bacc54fa 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -31,6 +31,176 @@ func (in *Artifact) DeepCopy() *Artifact { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Blueprint) DeepCopyInto(out *Blueprint) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Blueprint. +func (in *Blueprint) DeepCopy() *Blueprint { + if in == nil { + return nil + } + out := new(Blueprint) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Blueprint) 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 *BlueprintList) DeepCopyInto(out *BlueprintList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Blueprint, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BlueprintList. +func (in *BlueprintList) DeepCopy() *BlueprintList { + if in == nil { + return nil + } + out := new(BlueprintList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BlueprintList) 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 *BlueprintReference) DeepCopyInto(out *BlueprintReference) { + *out = *in + out.NamespacedName = in.NamespacedName +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BlueprintReference. +func (in *BlueprintReference) DeepCopy() *BlueprintReference { + if in == nil { + return nil + } + out := new(BlueprintReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BlueprintSpec) DeepCopyInto(out *BlueprintSpec) { + *out = *in + if in.Files != nil { + in, out := &in.Files, &out.Files + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BlueprintSpec. +func (in *BlueprintSpec) DeepCopy() *BlueprintSpec { + if in == nil { + return nil + } + out := new(BlueprintSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BlueprintVersion) DeepCopyInto(out *BlueprintVersion) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BlueprintVersion. +func (in *BlueprintVersion) DeepCopy() *BlueprintVersion { + if in == nil { + return nil + } + out := new(BlueprintVersion) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BlueprintVersion) 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 *BlueprintVersionList) DeepCopyInto(out *BlueprintVersionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]BlueprintVersion, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BlueprintVersionList. +func (in *BlueprintVersionList) DeepCopy() *BlueprintVersionList { + if in == nil { + return nil + } + out := new(BlueprintVersionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BlueprintVersionList) 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 *BlueprintVersionSpec) DeepCopyInto(out *BlueprintVersionSpec) { + *out = *in + in.BlueprintSpec.DeepCopyInto(&out.BlueprintSpec) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BlueprintVersionSpec. +func (in *BlueprintVersionSpec) DeepCopy() *BlueprintVersionSpec { + if in == nil { + return nil + } + out := new(BlueprintVersionSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Component) DeepCopyInto(out *Component) { *out = *in @@ -197,65 +367,65 @@ func (in *Dependency) DeepCopy() *Dependency { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FluxBucket) DeepCopyInto(out *FluxBucket) { +func (in *FluxBucketReference) DeepCopyInto(out *FluxBucketReference) { *out = *in out.NamespacedName = in.NamespacedName } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxBucket. -func (in *FluxBucket) DeepCopy() *FluxBucket { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxBucketReference. +func (in *FluxBucketReference) DeepCopy() *FluxBucketReference { if in == nil { return nil } - out := new(FluxBucket) + out := new(FluxBucketReference) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FluxGitRepository) DeepCopyInto(out *FluxGitRepository) { +func (in *FluxGitRepositoryReference) DeepCopyInto(out *FluxGitRepositoryReference) { *out = *in out.NamespacedName = in.NamespacedName } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxGitRepository. -func (in *FluxGitRepository) DeepCopy() *FluxGitRepository { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxGitRepositoryReference. +func (in *FluxGitRepositoryReference) DeepCopy() *FluxGitRepositoryReference { if in == nil { return nil } - out := new(FluxGitRepository) + out := new(FluxGitRepositoryReference) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FluxHelmChart) DeepCopyInto(out *FluxHelmChart) { +func (in *FluxHelmChartReference) DeepCopyInto(out *FluxHelmChartReference) { *out = *in out.NamespacedName = in.NamespacedName } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxHelmChart. -func (in *FluxHelmChart) DeepCopy() *FluxHelmChart { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxHelmChartReference. +func (in *FluxHelmChartReference) DeepCopy() *FluxHelmChartReference { if in == nil { return nil } - out := new(FluxHelmChart) + out := new(FluxHelmChartReference) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FluxOciRepository) DeepCopyInto(out *FluxOciRepository) { +func (in *FluxOciRepositoryReference) DeepCopyInto(out *FluxOciRepositoryReference) { *out = *in out.NamespacedName = in.NamespacedName } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxOciRepository. -func (in *FluxOciRepository) DeepCopy() *FluxOciRepository { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FluxOciRepositoryReference. +func (in *FluxOciRepositoryReference) DeepCopy() *FluxOciRepositoryReference { if in == nil { return nil } - out := new(FluxOciRepository) + out := new(FluxOciRepositoryReference) in.DeepCopyInto(out) return out } @@ -334,6 +504,11 @@ func (in *PostBuild) DeepCopy() *PostBuild { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SourceReference) DeepCopyInto(out *SourceReference) { *out = *in + if in.Blueprint != nil { + in, out := &in.Blueprint, &out.Blueprint + *out = new(BlueprintReference) + **out = **in + } if in.HttpRepository != nil { in, out := &in.HttpRepository, &out.HttpRepository *out = new(HttpRepository) @@ -341,22 +516,22 @@ func (in *SourceReference) DeepCopyInto(out *SourceReference) { } if in.FluxGitRepository != nil { in, out := &in.FluxGitRepository, &out.FluxGitRepository - *out = new(FluxGitRepository) + *out = new(FluxGitRepositoryReference) **out = **in } if in.FluxOciRepository != nil { in, out := &in.FluxOciRepository, &out.FluxOciRepository - *out = new(FluxOciRepository) + *out = new(FluxOciRepositoryReference) **out = **in } if in.FluxBucket != nil { in, out := &in.FluxBucket, &out.FluxBucket - *out = new(FluxBucket) + *out = new(FluxBucketReference) **out = **in } if in.FluxHelmChart != nil { in, out := &in.FluxHelmChart, &out.FluxHelmChart - *out = new(FluxHelmChart) + *out = new(FluxHelmChartReference) **out = **in } out.artifact = in.artifact diff --git a/crds/core.cs.sap.com_blueprints.yaml b/crds/core.cs.sap.com_blueprints.yaml new file mode 100644 index 00000000..1c08fd2a --- /dev/null +++ b/crds/core.cs.sap.com_blueprints.yaml @@ -0,0 +1,56 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.21.0 + name: blueprints.core.cs.sap.com +spec: + group: core.cs.sap.com + names: + kind: Blueprint + listKind: BlueprintList + plural: blueprints + singular: blueprint + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: Blueprint is the Schema for the blueprints 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: BlueprintSpec defines the desired state of Blueprint. + properties: + files: + additionalProperties: + type: string + type: object + type: object + required: + - spec + type: object + served: true + storage: true + subresources: {} diff --git a/crds/core.cs.sap.com_blueprintversions.yaml b/crds/core.cs.sap.com_blueprintversions.yaml new file mode 100644 index 00000000..a604e613 --- /dev/null +++ b/crds/core.cs.sap.com_blueprintversions.yaml @@ -0,0 +1,68 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.21.0 + name: blueprintversions.core.cs.sap.com +spec: + group: core.cs.sap.com + names: + kind: BlueprintVersion + listKind: BlueprintVersionList + plural: blueprintversions + singular: blueprintversion + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: BlueprintVersion is the Schema for the blueprint versions 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: BlueprintVersionSpec defines the desired state of BlueprintVersion. + properties: + blueprint: + type: string + digest: + type: string + files: + additionalProperties: + type: string + type: object + revision: + type: string + required: + - blueprint + - digest + - revision + type: object + required: + - spec + type: object + selectableFields: + - jsonPath: .spec.blueprint + served: true + storage: true + subresources: {} diff --git a/crds/core.cs.sap.com_components.yaml b/crds/core.cs.sap.com_components.yaml index 7b1029f7..24b24a7c 100644 --- a/crds/core.cs.sap.com_components.yaml +++ b/crds/core.cs.sap.com_components.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.20.1 + controller-gen.kubebuilder.io/version: v0.21.0 name: components.core.cs.sap.com spec: group: core.cs.sap.com @@ -239,6 +239,16 @@ spec: Exactly one of the options must be provided. Before accessing the Artifact() method, the SourceReference must be loaded by calling Load(). properties: + blueprint: + description: Reference to a Blueprint. + properties: + name: + type: string + namespace: + type: string + required: + - name + type: object fluxBucket: description: Reference to a flux Bucket. properties: @@ -301,19 +311,21 @@ spec: type: object type: object x-kubernetes-validations: - - message: Exactly one of 'httpRepository' or 'fluxGitRepository' + - message: Exactly one of 'blueprint' or 'httpRepository' or 'fluxGitRepository' or 'fluxOciRepository' or 'fluxBucket' or 'fluxHelmChart' must be provided - rule: has(self.httpRepository) && !has(self.fluxGitRepository) && - !has(self.fluxOciRepository) && !has(self.fluxBucket) && !has(self.fluxHelmChart) - || !has(self.httpRepository) && has(self.fluxGitRepository) && - !has(self.fluxOciRepository) && !has(self.fluxBucket) && !has(self.fluxHelmChart) - || !has(self.httpRepository) && !has(self.fluxGitRepository) && - has(self.fluxOciRepository) && !has(self.fluxBucket) && !has(self.fluxHelmChart) - || !has(self.httpRepository) && !has(self.fluxGitRepository) && - !has(self.fluxOciRepository) && has(self.fluxBucket) && !has(self.fluxHelmChart) - || !has(self.httpRepository) && !has(self.fluxGitRepository) && - !has(self.fluxOciRepository) && !has(self.fluxBucket) && has(self.fluxHelmChart) + rule: has(self.blueprint) && !has(self.httpRepository) && !has(self.fluxGitRepository) + && !has(self.fluxOciRepository) && !has(self.fluxBucket) && !has(self.fluxHelmChart) + || !has(self.blueprint) && has(self.httpRepository) && !has(self.fluxGitRepository) + && !has(self.fluxOciRepository) && !has(self.fluxBucket) && !has(self.fluxHelmChart) + || !has(self.blueprint) && !has(self.httpRepository) && has(self.fluxGitRepository) + && !has(self.fluxOciRepository) && !has(self.fluxBucket) && !has(self.fluxHelmChart) + || !has(self.blueprint) && !has(self.httpRepository) && !has(self.fluxGitRepository) + && has(self.fluxOciRepository) && !has(self.fluxBucket) && !has(self.fluxHelmChart) + || !has(self.blueprint) && !has(self.httpRepository) && !has(self.fluxGitRepository) + && !has(self.fluxOciRepository) && has(self.fluxBucket) && !has(self.fluxHelmChart) + || !has(self.blueprint) && !has(self.httpRepository) && !has(self.fluxGitRepository) + && !has(self.fluxOciRepository) && !has(self.fluxBucket) && has(self.fluxHelmChart) sticky: type: boolean suspend: diff --git a/go.mod b/go.mod index 9123ea8c..ef262be3 100644 --- a/go.mod +++ b/go.mod @@ -1,24 +1,25 @@ module github.com/sap/component-operator -go 1.26.2 +go 1.26.4 require ( filippo.io/age v1.3.1 github.com/fluxcd/pkg/apis/event v0.24.0 github.com/fluxcd/pkg/runtime v0.102.0 - github.com/fluxcd/source-controller/api v1.8.0 + github.com/fluxcd/source-controller/api v1.8.5 github.com/getsops/sops/v3 v3.12.1 github.com/go-logr/logr v1.4.3 github.com/pkg/errors v0.9.1 - github.com/sap/component-operator-runtime v0.3.142 - github.com/sap/go-generics v0.2.57 - k8s.io/apiextensions-apiserver v0.35.1 - k8s.io/apimachinery v0.36.0-alpha.1 - k8s.io/client-go v0.35.1 - k8s.io/code-generator v0.35.1 - sigs.k8s.io/controller-runtime v0.23.3 + github.com/sap/component-operator-runtime v0.3.150 + github.com/sap/go-generics v0.2.63 + k8s.io/api v0.36.2 + k8s.io/apiextensions-apiserver v0.36.2 + k8s.io/apimachinery v0.36.2 + k8s.io/client-go v0.36.2 + k8s.io/code-generator v0.36.2 + sigs.k8s.io/controller-runtime v0.24.1 sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20260216173200-e4c1c38bcbdb - sigs.k8s.io/controller-tools v0.20.1 + sigs.k8s.io/controller-tools v0.21.0 sigs.k8s.io/yaml v1.6.0 ) @@ -80,31 +81,41 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/drone/envsubst v1.0.3 // indirect - github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect - github.com/fatih/color v1.18.0 // indirect + github.com/fatih/color v1.19.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fluxcd/pkg/apis/acl v0.9.0 // indirect - github.com/fluxcd/pkg/apis/meta v1.25.0 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/fluxcd/pkg/apis/meta v1.25.1 // indirect + github.com/fsnotify/fsnotify v1.10.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.1 // indirect github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e // indirect github.com/go-errors/errors v1.5.1 // indirect github.com/go-git/go-git v4.7.0+incompatible // indirect github.com/go-jose/go-jose/v4 v4.1.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-openapi/jsonpointer v0.23.1 // indirect + github.com/go-openapi/jsonreference v0.21.5 // indirect + github.com/go-openapi/swag v0.26.0 // indirect + github.com/go-openapi/swag/cmdutils v0.26.0 // indirect + github.com/go-openapi/swag/conv v0.26.0 // indirect + github.com/go-openapi/swag/fileutils v0.26.0 // indirect + github.com/go-openapi/swag/jsonname v0.26.0 // indirect + github.com/go-openapi/swag/jsonutils v0.26.0 // indirect + github.com/go-openapi/swag/loading v0.26.0 // indirect + github.com/go-openapi/swag/mangling v0.26.0 // indirect + github.com/go-openapi/swag/netutils v0.26.0 // indirect + github.com/go-openapi/swag/stringutils v0.26.0 // indirect + github.com/go-openapi/swag/typeutils v0.26.0 // indirect + github.com/go-openapi/swag/yamlutils v0.26.0 // indirect github.com/gobuffalo/flect v1.0.3 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-yaml v1.9.8 // indirect github.com/golang-jwt/jwt/v5 v5.3.0 // indirect - github.com/google/btree v1.1.3 // indirect - github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect @@ -127,11 +138,9 @@ require ( github.com/iancoleman/strcase v0.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect - github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/lib/pq v1.11.2 // indirect - github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -148,8 +157,8 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.17.0 // indirect + github.com/prometheus/common v0.67.5 // indirect + github.com/prometheus/procfs v0.20.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect @@ -167,35 +176,35 @@ require ( go.mongodb.org/mongo-driver v1.13.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel v1.39.0 // indirect - go.opentelemetry.io/otel/metric v1.39.0 // indirect - go.opentelemetry.io/otel/sdk v1.39.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect - go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 // indirect + go.opentelemetry.io/otel v1.41.0 // indirect + go.opentelemetry.io/otel/metric v1.41.0 // indirect + go.opentelemetry.io/otel/sdk v1.40.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect + go.opentelemetry.io/otel/trace v1.41.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect - go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.48.0 // indirect - golang.org/x/mod v0.32.0 // indirect - golang.org/x/net v0.50.0 // indirect - golang.org/x/oauth2 v0.35.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.41.0 // indirect - golang.org/x/term v0.40.0 // indirect - golang.org/x/text v0.34.0 // indirect - golang.org/x/time v0.14.0 // indirect - golang.org/x/tools v0.41.0 // indirect + golang.org/x/crypto v0.50.0 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.53.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/term v0.42.0 // indirect + golang.org/x/text v0.36.0 // indirect + golang.org/x/time v0.15.0 // indirect + golang.org/x/tools v0.44.0 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/api v0.266.0 // indirect google.golang.org/genproto v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260203192932-546029d2fa20 // indirect - google.golang.org/grpc v1.79.1 // indirect - google.golang.org/protobuf v1.36.11 // indirect + google.golang.org/grpc v1.79.3 // indirect + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.1 // indirect @@ -204,12 +213,11 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.35.1 // indirect k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect - k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-aggregator v0.35.1 // indirect - k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 // indirect - k8s.io/utils v0.0.0-20260108192941-914a6e750570 // indirect + k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 // indirect + k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 // indirect sigs.k8s.io/cli-utils v0.37.2 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kustomize/api v0.21.1 // indirect diff --git a/go.sum b/go.sum index a7774bfb..3b0b2354 100644 --- a/go.sum +++ b/go.sum @@ -130,6 +130,8 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -159,8 +161,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= -github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= -github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -178,27 +180,27 @@ github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi 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/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fluxcd/pkg/apis/acl v0.9.0 h1:wBpgsKT+jcyZEcM//OmZr9RiF8klL3ebrDp2u2ThsnA= github.com/fluxcd/pkg/apis/acl v0.9.0/go.mod h1:TttNS+gocsGLwnvmgVi3/Yscwqrjc17+vhgYfqkfrV4= github.com/fluxcd/pkg/apis/event v0.24.0 h1:WVPf0FrJ5JExRDDGoo4W0jZgHZt0n4E48/e8b3TSmkA= github.com/fluxcd/pkg/apis/event v0.24.0/go.mod h1:Hoi4DejaNKVahGkRXqGBjT9h1aKmhc7RCYcsgoTieqc= -github.com/fluxcd/pkg/apis/meta v1.25.0 h1:fmZgMoe7yITGfhFqdOs7w2GOu3Y/2Vvz4+4p/eay3eA= -github.com/fluxcd/pkg/apis/meta v1.25.0/go.mod h1:1D92RqAet0/n/cH5S0khBXweirHWkw9rCO0V4NCY6xc= +github.com/fluxcd/pkg/apis/meta v1.25.1 h1:WG1GIC/SOz0GjxT0uVuO6AMicQ3yFsk6bDozCnq+fto= +github.com/fluxcd/pkg/apis/meta v1.25.1/go.mod h1:c7o6mJGLCMvNrfdinGZehkrdZuFT9vZdZNrn66DtVD0= github.com/fluxcd/pkg/runtime v0.102.0 h1:mFgs468p/mCOYQq0s4m4nWkB3AhfLNZ/hiTWQNpixqo= github.com/fluxcd/pkg/runtime v0.102.0/go.mod h1:SctSsHvFwUfiOVP1zirP6mo7I8wQtXeWVl2lNQWal88= -github.com/fluxcd/source-controller/api v1.8.0 h1:ndrYmcv6ZMcdQHFSUkOrFVDO7h16SfDBSw/DOqf/LPo= -github.com/fluxcd/source-controller/api v1.8.0/go.mod h1:1O7+sMbqc1+3tPvjmtgFz+bASTl794Y9SxpebHDDSGA= +github.com/fluxcd/source-controller/api v1.8.5 h1:mLKc9YVMk46JCt1BQbkG6irkrpBZp95kiXh2+GYB6KQ= +github.com/fluxcd/source-controller/api v1.8.5/go.mod h1:sio4t49RDx+S1etHRFAEEw8qfVuw0KKlOg8bRVlEYPM= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= -github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fsnotify/fsnotify v1.10.0 h1:Xx/5Ydg9CeBDX/wi4VJqStNtohYjitZhhlHt4h3St1M= +github.com/fsnotify/fsnotify v1.10.0/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= +github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ= +github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e h1:y/1nzrdF+RPds4lfoEpNhjfmzlgZtPqyO3jMzrqDQws= github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e/go.mod h1:awFzISqLJoZLm+i9QQ4SgMNHDqljH6jWV0B36V5MrUM= github.com/getsops/sops/v3 v3.12.1 h1:DZzLNJx6EH4SZvMjI1Y814WIcOQNUtOP3WgDsHNqQTU= @@ -217,12 +219,40 @@ 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= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= -github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= +github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= +github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE= +github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= +github.com/go-openapi/swag v0.26.0 h1:GVDXCmfvhfu1BxiHo8/FA+BbKmhecHnG3varjON5/RI= +github.com/go-openapi/swag v0.26.0/go.mod h1:82g3193sZJRbocs7bNCqGfIgq8pkuwVwCfhKIRlEQF0= +github.com/go-openapi/swag/cmdutils v0.26.0 h1:iowihOcvq7y4egO8cOq0dmfohz6wfeQ63U1EnuhO2TU= +github.com/go-openapi/swag/cmdutils v0.26.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= +github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= +github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU= +github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc= +github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= +github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= +github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= +github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= +github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= +github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= +github.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ= +github.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0= +github.com/go-openapi/swag/netutils v0.26.0 h1:CmZp+ZT7HrmFwrC3GdGsXBq2+42T1bjKBapcqVpIs3c= +github.com/go-openapi/swag/netutils v0.26.0/go.mod h1:5iK+Ok3ZohWWex1C50BFTPexi03UaPwjW4Oj8kgrpwo= +github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= +github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= +github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= +github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= +github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= +github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= +github.com/go-openapi/testify/enable/yaml/v2 v2.4.2 h1:5zRca5jw7lzVREKCZVNBpysDNBjj74rBh0N2BGQbSR0= +github.com/go-openapi/testify/enable/yaml/v2 v2.4.2/go.mod h1:XVevPw5hUXuV+5AkI1u1PeAm27EQVrhXTTCPAF85LmE= +github.com/go-openapi/testify/v2 v2.4.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4= +github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= @@ -260,12 +290,10 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -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.26.1 h1:iPbVVEdkhTX++hpe3lzSk7D3G3QSYqLGoHOcEio+UXQ= github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -278,8 +306,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= @@ -292,8 +320,8 @@ github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= github.com/goware/prefixer v0.0.0-20160118172347-395022866408 h1:Y9iQJfEqnN3/Nce9cOegemcy/9Ai5k3huT6E80F3zaw= github.com/goware/prefixer v0.0.0-20160118172347-395022866408/go.mod h1:PE1ycukgRPJ7bJ9a1fdfQ9j8i/cEcRAoLZzbxYpNB/s= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -328,8 +356,6 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU= github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0= github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= @@ -352,8 +378,6 @@ github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= -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-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= @@ -391,10 +415,10 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= -github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= -github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= -github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/onsi/ginkgo/v2 v2.29.0 h1:rfh+ZFjgJhYWRoIqVf3Uwx/W20yLrcrE2h2GmYVRaag= +github.com/onsi/ginkgo/v2 v2.29.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/gomega v1.41.0 h1:OwKp4pXNgVxf6sCplzYo794OFNuoL2q2SBMU5NSWOjA= +github.com/onsi/gomega v1.41.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= @@ -419,20 +443,20 @@ github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UH github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= -github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/sap/component-operator-runtime v0.3.142 h1:LJlLO+3DGNvhlu0gAjU0cgQgTVvUScCgFIx9cMtp+jg= -github.com/sap/component-operator-runtime v0.3.142/go.mod h1:tMO/LejPPPNT8dO33yGHSyht0RrLsu+tKugefRx4v3E= -github.com/sap/go-generics v0.2.57 h1:BTINpYYC3oNo8rhRCVwmQZ8wmsoLm6JzJqkE6zkQi80= -github.com/sap/go-generics v0.2.57/go.mod h1:9HF4GCtwEy7ioJTz/QZCY4CoNjHJWYsNB30ALIR3un4= +github.com/sap/component-operator-runtime v0.3.150 h1:lehpvJbI8okklgJrLcueuNA8pGU0dyFSj2XlyTWuqfU= +github.com/sap/component-operator-runtime v0.3.150/go.mod h1:ni30ZxgApoSSq/psYSGbPWjnc1zLGXrVukkNTqgbz/Q= +github.com/sap/go-generics v0.2.63 h1:LCr6C2LJi65lLLFXK0yiYpF5ews9N24oIAz7pK/6J7o= +github.com/sap/go-generics v0.2.63/go.mod h1:VgQx6yybDpJgxju7rC/gjmr3BDJIuZSbCb68rtMyB+o= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= @@ -498,36 +522,36 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0 h1:XmiuHzgJt067+a6kwyAzkhXooYVv3/TOw9cM2VfJgUM= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0/go.mod h1:KDgtbWKTQs4bM+VPUr6WlL9m/WXcmkCcBlIzqxPGzmI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0 h1:7iP2uCb7sGddAr30RRS6xjKy7AZ2JtTOPA3oolgVSw8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= +go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= +go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0 h1:5gn2urDL/FBnK8OkCfD1j3/ER79rUuTYmCvlXBKeYL8= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.39.0/go.mod h1:0fBG6ZJxhqByfFZDwSwpZGzJU671HkwpWaNe2t4VUPI= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= -go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= -go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= -go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= +go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= +go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= +go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= +go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= -go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -537,17 +561,17 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= -golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -559,17 +583,17 @@ golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= -golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= -golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -586,12 +610,12 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= -golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -599,10 +623,10 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= -golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -611,8 +635,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= @@ -641,16 +665,16 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.79.1 h1:zGhSi45ODB9/p3VAawt9a+O/MULLl9dpizzNNpq7flY= -google.golang.org/grpc v1.79.1/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -679,40 +703,40 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.35.1 h1:0PO/1FhlK/EQNVK5+txc4FuhQibV25VLSdLMmGpDE/Q= -k8s.io/api v0.35.1/go.mod h1:28uR9xlXWml9eT0uaGo6y71xK86JBELShLy4wR1XtxM= -k8s.io/apiextensions-apiserver v0.35.1 h1:p5vvALkknlOcAqARwjS20kJffgzHqwyQRM8vHLwgU7w= -k8s.io/apiextensions-apiserver v0.35.1/go.mod h1:2CN4fe1GZ3HMe4wBr25qXyJnJyZaquy4nNlNmb3R7AQ= -k8s.io/apimachinery v0.36.0-alpha.1 h1:MrQLU+TD3A2/ywQiTHEJ5BEKKk3XHpy0RkT98V0XdKI= -k8s.io/apimachinery v0.36.0-alpha.1/go.mod h1:hQkG060WLAG1TIkYsu5lj3tb6YdNpKe5Zrr2UPGg+/k= -k8s.io/apiserver v0.35.1 h1:potxdhhTL4i6AYAa2QCwtlhtB1eCdWQFvJV6fXgJzxs= -k8s.io/apiserver v0.35.1/go.mod h1:BiL6Dd3A2I/0lBnteXfWmCFobHM39vt5+hJQd7Lbpi4= -k8s.io/client-go v0.35.1 h1:+eSfZHwuo/I19PaSxqumjqZ9l5XiTEKbIaJ+j1wLcLM= -k8s.io/client-go v0.35.1/go.mod h1:1p1KxDt3a0ruRfc/pG4qT/3oHmUj1AhSHEcxNSGg+OA= -k8s.io/code-generator v0.35.1 h1:yLKR2la7Z9cWT5qmk67ayx8xXLM4RRKQMnC8YPvTWRI= -k8s.io/code-generator v0.35.1/go.mod h1:F2Fhm7aA69tC/VkMXLDokdovltXEF026Tb9yfQXQWKg= -k8s.io/component-base v0.35.1 h1:XgvpRf4srp037QWfGBLFsYMUQJkE5yMa94UsJU7pmcE= -k8s.io/component-base v0.35.1/go.mod h1:HI/6jXlwkiOL5zL9bqA3en1Ygv60F03oEpnuU1G56Bs= +k8s.io/api v0.36.2 h1:TF6YDLIzKfccK7cq9YpTcGX8TJmEkHVRv78DM51fRYY= +k8s.io/api v0.36.2/go.mod h1:F4LbMO4brjZYh7yFkXWhynSvtB7YauxV4c+HHkNRGNg= +k8s.io/apiextensions-apiserver v0.36.2 h1:3O5gqOj/dt2XWWbpMe+TXWpE9yU6pjM/tXxtHHJT/K4= +k8s.io/apiextensions-apiserver v0.36.2/go.mod h1:cL1tBWe8XSaP1H30iWKGo7hf6iAUUUJPEU70dskmAnA= +k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ= +k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4= +k8s.io/apiserver v0.36.2 h1:6vMnkmHZPeBloNkHUhmZYq7Ylv8WIB8xjyEl+eSt26E= +k8s.io/apiserver v0.36.2/go.mod h1:9PoQ2ikCytrZyZg11mGhLEF5m8Rgsb5FJmYJ4Wvnl1k= +k8s.io/client-go v0.36.2 h1:bfgxmFKc9CgqsgX4xKLAAdmTQlWee7Ob/HlDOrJ5TBI= +k8s.io/client-go v0.36.2/go.mod h1:1vgO4OAlfPnoLcb+Rze2GF5rAr14w8qjrYMoyXJzQj0= +k8s.io/code-generator v0.36.2 h1:iBNFYhClojQaVrF99Z3iTAad7LztQh3yCtwR8L8Ocpg= +k8s.io/code-generator v0.36.2/go.mod h1:IfnsRW1IAq9iPxqs/FfOnVnWWONxS2mPDvWNR4fPlzI= +k8s.io/component-base v0.36.2 h1:Z0VH80O7Ng0HDZnZj3WRR3urEGa0kTwmO8CwEwjVK1w= +k8s.io/component-base v0.36.2/go.mod h1:mGfFOA7Gwpdm1VW2cwSQYbiDIlz8GD2WGwH88QSeCyA= k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ= k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-aggregator v0.35.1 h1:LN+btMJ3yp7biqVgT/0LF6SKIKLyfPU0R+JJ1mycs2I= k8s.io/kube-aggregator v0.35.1/go.mod h1:HQSjPQfOFRzcv7biQ7jV3cEfKHG+bczpLCfh4QfvxZU= -k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4 h1:HhDfevmPS+OalTjQRKbTHppRIz01AWi8s45TMXStgYY= -k8s.io/kube-openapi v0.0.0-20260127142750-a19766b6e2d4/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY= -k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -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= +k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199 h1:sWu4Td5mgJlwunsUydnhKEAfNUHM7hm1wfKEQmD7G5c= +k8s.io/kube-openapi v0.0.0-20260427204847-8949caaa1199/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= +k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 h1:hSfpvjjTQXQY2Fol2CS0QHMNs/WI1MOSGzCm1KhM5ec= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/cli-utils v0.37.2 h1:GOfKw5RV2HDQZDJlru5KkfLO1tbxqMoyn1IYUxqBpNg= sigs.k8s.io/cli-utils v0.37.2/go.mod h1:V+IZZr4UoGj7gMJXklWBg6t5xbdThFBcpj4MrZuCYco= -sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= -sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= +sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= +sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20260216173200-e4c1c38bcbdb h1:idmKgblo0x0z4OGzrva4fg+z02b5FnU/gr5MS21/Kuo= sigs.k8s.io/controller-runtime/tools/setup-envtest v0.0.0-20260216173200-e4c1c38bcbdb/go.mod h1:IlbgWQkCYbpbkygXxnd/sLJWL9WQk9NvvVhkJCm5P7Q= -sigs.k8s.io/controller-tools v0.20.1 h1:gkfMt9YodI0K85oT8rVi80NTXO/kDmabKR5Ajn5GYxs= -sigs.k8s.io/controller-tools v0.20.1/go.mod h1:b4qPmjGU3iZwqn34alUU5tILhNa9+VXK+J3QV0fT/uU= +sigs.k8s.io/controller-tools v0.21.0 h1:KXDQza3bgjlPY6xLR63tI/40gzjhyUAvkCrwzd2/6cs= +sigs.k8s.io/controller-tools v0.21.0/go.mod h1:DLIypi3Q2+azVAP8jr/mHXJgveYYHFjhnNOUuBJ10JE= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= diff --git a/internal/cache/component/cache.go b/internal/cache/component/cache.go new file mode 100644 index 00000000..fca5600e --- /dev/null +++ b/internal/cache/component/cache.go @@ -0,0 +1,221 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +package component + +import ( + "context" + "fmt" + + "github.com/pkg/errors" + "github.com/sap/go-generics/slices" + + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + + fluxsourcev1 "github.com/fluxcd/source-controller/api/v1" + fluxsourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" + + operatorv1alpha1 "github.com/sap/component-operator/api/v1alpha1" + "github.com/sap/component-operator/pkg/meta" +) + +func MatchingDependency(component *operatorv1alpha1.Component) client.ListOption { + return client.MatchingFields{dependenciesIndexKey: client.ObjectKeyFromObject(component).String()} +} + +func MatchingBlueprint(blueprint *operatorv1alpha1.Blueprint) client.ListOption { + return client.MatchingFields{blueprintIndexKey: client.ObjectKeyFromObject(blueprint).String()} +} + +func MatchingBlueprintVersion(blueprintVersion *operatorv1alpha1.BlueprintVersion) client.ListOption { + return client.MatchingFields{blueprintVersionIndexKey: client.ObjectKeyFromObject(blueprintVersion).String()} +} + +func MatchingFluxSource(source meta.FluxSource) client.ListOption { + indexKey := "" + switch source.(type) { + case *fluxsourcev1.GitRepository: + indexKey = gitRepositoryIndexKey + case *fluxsourcev1beta2.OCIRepository: + indexKey = ociRepositoryIndexKey + case *fluxsourcev1beta2.Bucket: + indexKey = bucketIndexKey + case *fluxsourcev1.HelmChart: + indexKey = helmChartIndexKey + default: + panic("this cannot happen") + } + return client.MatchingFields{indexKey: client.ObjectKeyFromObject(source).String()} +} + +func HasBlueprint() client.ListOption { + return client.MatchingFields{sourceTypeIndexKey: sourceTypeBlueprint} +} + +func HasHttpRepository() client.ListOption { + return client.MatchingFields{sourceTypeIndexKey: sourceTypeHttpRepository} +} + +func HasFluxGitRepository() client.ListOption { + return client.MatchingFields{sourceTypeIndexKey: sourceTypeFluxGitRepository} +} + +func HasFluxOciRepository() client.ListOption { + return client.MatchingFields{sourceTypeIndexKey: sourceTypeFluxOciRepository} +} + +func HasFluxBucket() client.ListOption { + return client.MatchingFields{sourceTypeIndexKey: sourceTypeFluxBucket} +} + +func HasFluxHelmChart() client.ListOption { + return client.MatchingFields{sourceTypeIndexKey: sourceTypeFluxHelmChart} +} + +func HasFluxSource() client.ListOption { + return client.MatchingFields{sourceTypeIndexKey: sourceTypeFluxSource} +} + +const ( + sourceTypeIndexKey string = ".metadata.sourceType" + + dependenciesIndexKey string = ".metadata.dependencies" + + blueprintIndexKey string = ".metadata.cs.blueprint" + blueprintVersionIndexKey string = ".metadata.cs.blueprintversion" + + gitRepositoryIndexKey string = ".metadata.flux.gitRepository" + ociRepositoryIndexKey string = ".metadata.flux.ociRepository" + bucketIndexKey string = ".metadata.flux.bucket" + helmChartIndexKey string = ".metadata.flux.helmChart" +) + +const ( + sourceTypeBlueprint string = "blueprint" + sourceTypeHttpRepository string = "httpRepository" + sourceTypeFluxGitRepository string = "fluxGitRepository" + sourceTypeFluxOciRepository string = "fluxOciRepository" + sourceTypeFluxBucket string = "fluxBucket" + sourceTypeFluxHelmChart string = "fluxHelmChart" + sourceTypeFluxSource string = "fluxSource" +) + +func SetupWithManager(mgr manager.Manager) error { + // TODO: should we pass a meaningful context? + if err := mgr.GetCache().IndexField(context.TODO(), &operatorv1alpha1.Component{}, sourceTypeIndexKey, indexBySourceType); err != nil { + return errors.Wrapf(err, "failed setting index field %s", sourceTypeIndexKey) + } + + if err := mgr.GetCache().IndexField(context.TODO(), &operatorv1alpha1.Component{}, dependenciesIndexKey, indexByDependencies); err != nil { + return errors.Wrapf(err, "failed setting index field %s", dependenciesIndexKey) + } + + if err := mgr.GetCache().IndexField(context.TODO(), &operatorv1alpha1.Component{}, blueprintIndexKey, indexByBlueprint); err != nil { + return errors.Wrapf(err, "failed setting index field %s", blueprintIndexKey) + } + if err := mgr.GetCache().IndexField(context.TODO(), &operatorv1alpha1.Component{}, blueprintVersionIndexKey, indexByBlueprintVersion); err != nil { + return errors.Wrapf(err, "failed setting index field %s", blueprintVersionIndexKey) + } + + if err := mgr.GetCache().IndexField(context.TODO(), &operatorv1alpha1.Component{}, gitRepositoryIndexKey, indexCompoonentByGitRepository); err != nil { + return errors.Wrapf(err, "failed setting index field %s", gitRepositoryIndexKey) + } + if err := mgr.GetCache().IndexField(context.TODO(), &operatorv1alpha1.Component{}, ociRepositoryIndexKey, indexCompoonentByOciRepository); err != nil { + return errors.Wrapf(err, "failed setting index field %s", ociRepositoryIndexKey) + } + if err := mgr.GetCache().IndexField(context.TODO(), &operatorv1alpha1.Component{}, bucketIndexKey, indexCompoonentByBucket); err != nil { + return errors.Wrapf(err, "failed setting index field %s", bucketIndexKey) + } + if err := mgr.GetCache().IndexField(context.TODO(), &operatorv1alpha1.Component{}, helmChartIndexKey, indexCompoonentByHelmChart); err != nil { + return errors.Wrapf(err, "failed setting index field %s", helmChartIndexKey) + } + + return nil +} + +func indexBySourceType(object client.Object) []string { + component := object.(*operatorv1alpha1.Component) + if component.Spec.SourceRef.Blueprint != nil { + return []string{sourceTypeBlueprint} + } + if component.Spec.SourceRef.HttpRepository != nil { + return []string{sourceTypeHttpRepository} + } + if component.Spec.SourceRef.FluxGitRepository != nil { + return []string{sourceTypeFluxGitRepository, sourceTypeFluxSource} + } + if component.Spec.SourceRef.FluxOciRepository != nil { + return []string{sourceTypeFluxOciRepository, sourceTypeFluxSource} + } + if component.Spec.SourceRef.FluxBucket != nil { + return []string{sourceTypeFluxBucket, sourceTypeFluxSource} + } + if component.Spec.SourceRef.FluxHelmChart != nil { + return []string{sourceTypeFluxHelmChart, sourceTypeFluxSource} + } + panic("this cannot happen") +} + +func indexByDependencies(object client.Object) []string { + component := object.(*operatorv1alpha1.Component) + return slices.Collect(component.Spec.Dependencies, func(dependency operatorv1alpha1.Dependency) string { + return dependency.WithDefaultNamespace(component.Namespace).String() + }) +} + +func indexByBlueprint(object client.Object) []string { + component := object.(*operatorv1alpha1.Component) + if component.Spec.SourceRef.Blueprint == nil { + return nil + } + return []string{component.Spec.SourceRef.Blueprint.WithDefaultNamespace(component.Namespace).String()} +} + +func indexByBlueprintVersion(object client.Object) []string { + component := object.(*operatorv1alpha1.Component) + if component.Spec.SourceRef.Blueprint == nil { + return nil + } + if component.Status.SourceRef == nil || component.Status.SourceRef.Artifact.Digest == "" { + return nil + } + return []string{operatorv1alpha1.NamespacedName{ + Name: fmt.Sprintf("%s--%s", component.Spec.SourceRef.Blueprint.Name, component.Status.SourceRef.Artifact.Digest), + Namespace: component.Spec.SourceRef.Blueprint.Namespace, + }.WithDefaultNamespace(component.Namespace).String()} +} + +func indexCompoonentByGitRepository(object client.Object) []string { + component := object.(*operatorv1alpha1.Component) + if component.Spec.SourceRef.FluxGitRepository == nil { + return nil + } + return []string{component.Spec.SourceRef.FluxGitRepository.WithDefaultNamespace(component.Namespace).String()} +} + +func indexCompoonentByOciRepository(object client.Object) []string { + component := object.(*operatorv1alpha1.Component) + if component.Spec.SourceRef.FluxOciRepository == nil { + return nil + } + return []string{component.Spec.SourceRef.FluxOciRepository.WithDefaultNamespace(component.Namespace).String()} +} + +func indexCompoonentByBucket(object client.Object) []string { + component := object.(*operatorv1alpha1.Component) + if component.Spec.SourceRef.FluxBucket == nil { + return nil + } + return []string{component.Spec.SourceRef.FluxBucket.WithDefaultNamespace(component.Namespace).String()} +} + +func indexCompoonentByHelmChart(object client.Object) []string { + component := object.(*operatorv1alpha1.Component) + if component.Spec.SourceRef.FluxHelmChart == nil { + return nil + } + return []string{component.Spec.SourceRef.FluxHelmChart.WithDefaultNamespace(component.Namespace).String()} +} diff --git a/internal/controllers/blueprint/handlers.go b/internal/controllers/blueprint/handlers.go new file mode 100644 index 00000000..3d71458b --- /dev/null +++ b/internal/controllers/blueprint/handlers.go @@ -0,0 +1,54 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +package blueprint + +import ( + "context" + + "github.com/go-logr/logr" + + apitypes "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/workqueue" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + operatorv1alpha1 "github.com/sap/component-operator/api/v1alpha1" +) + +type blueprintVersionHandler struct { + cache cache.Cache + log logr.Logger +} + +func newBlueprintVersionHandler(cache cache.Cache, log logr.Logger) handler.TypedEventHandler[client.Object, reconcile.Request] { + return &blueprintVersionHandler{ + cache: cache, + log: log, + } +} + +func (h *blueprintVersionHandler) Create(ctx context.Context, e event.TypedCreateEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { + blueprintVersion := e.Object.(*operatorv1alpha1.BlueprintVersion) + q.Add(reconcile.Request{NamespacedName: apitypes.NamespacedName{ + Namespace: blueprintVersion.Namespace, + Name: blueprintVersion.Spec.Blueprint, + }}) +} + +func (h *blueprintVersionHandler) Update(ctx context.Context, e event.TypedUpdateEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { + // no need to queue components if blueprint version is updated +} + +func (h *blueprintVersionHandler) Delete(ctx context.Context, e event.TypedDeleteEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { + // no need to queue components if blueprint version is deleted +} + +func (h *blueprintVersionHandler) Generic(ctx context.Context, e event.TypedGenericEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { + // generic events are not expected to arrive on the watch that uses this handler, so nothing to do here +} diff --git a/internal/controllers/blueprint/reconciler.go b/internal/controllers/blueprint/reconciler.go new file mode 100644 index 00000000..4c7ff472 --- /dev/null +++ b/internal/controllers/blueprint/reconciler.go @@ -0,0 +1,121 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +package blueprint + +import ( + "context" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + operatorv1alpha1 "github.com/sap/component-operator/api/v1alpha1" + componentcache "github.com/sap/component-operator/internal/cache/component" + "github.com/sap/component-operator/pkg/meta" +) + +const ( + reasonDeletionBlocked = "DeletionBlocked" +) + +type ReconcilerOptions struct { + Name string +} + +type reconciler struct { + client client.Client + cache cache.Cache + eventRecorder record.EventRecorder +} + +func newReconciler(clnt client.Client, cache cache.Cache, eventRecorder record.EventRecorder) *reconciler { + return &reconciler{ + client: clnt, + cache: cache, + eventRecorder: eventRecorder, + } +} + +func (r *reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + blueprint := &operatorv1alpha1.Blueprint{} + if err := r.client.Get(ctx, req.NamespacedName, blueprint); err != nil { + if apierrors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + if blueprint.DeletionTimestamp.IsZero() { + if controllerutil.AddFinalizer(blueprint, meta.Name) { + if err := r.client.Update(ctx, blueprint); err != nil { + return ctrl.Result{}, err + } + } + } + + blueprintVersionList := &operatorv1alpha1.BlueprintVersionList{} + if err := r.client.List(ctx, blueprintVersionList, client.InNamespace(blueprint.Namespace), client.MatchingFields{ + "spec.blueprint": blueprint.Name, + }); err != nil { + return ctrl.Result{}, err + } + + numDeleted := 0 + for _, blueprintVersion := range blueprintVersionList.Items { + comoponentList := &operatorv1alpha1.ComponentList{} + if err := r.cache.List(ctx, comoponentList, componentcache.MatchingBlueprintVersion(&blueprintVersion), client.Limit(1)); err != nil { + return ctrl.Result{}, err + } + if len(comoponentList.Items) == 0 { + // TODO: rule out caching race conditions + if blueprintVersion.DeletionTimestamp.IsZero() { + if err := r.client.Delete(ctx, &blueprintVersion); err != nil { + return ctrl.Result{}, err + } + numDeleted++ + } else { + return ctrl.Result{}, fmt.Errorf("blueprintversion %s/%s is expected to be deleted but is not yet deleted", blueprintVersion.Namespace, blueprintVersion.Name) + } + } + } + + if numDeleted > 0 { + return ctrl.Result{RequeueAfter: 1 * time.Second}, nil + } + + if blueprint.DeletionTimestamp.IsZero() { + return ctrl.Result{RequeueAfter: 10 * time.Minute}, nil + } else { + if len(blueprintVersionList.Items) == 0 { + if controllerutil.RemoveFinalizer(blueprint, meta.Name) { + if err := r.client.Update(ctx, blueprint); err != nil { + return ctrl.Result{}, err + } + } + return ctrl.Result{}, nil + } else { + r.eventRecorder.Eventf(blueprint, corev1.EventTypeNormal, reasonDeletionBlocked, "Blueprint cannot be deleted because there are still %d BlueprintVersions referencing it", len(blueprintVersionList.Items)) + return ctrl.Result{RequeueAfter: 10 * time.Second}, nil + } + } +} + +func SetupWithManager(mgr ctrl.Manager, options ReconcilerOptions) error { + reconciler := newReconciler(mgr.GetClient(), mgr.GetCache(), mgr.GetEventRecorderFor(options.Name)) + + return ctrl.NewControllerManagedBy(mgr). + For(&operatorv1alpha1.Blueprint{}, builder.WithPredicates(predicate.Or(predicate.GenerationChangedPredicate{}, predicate.AnnotationChangedPredicate{}))). + Watches(&operatorv1alpha1.BlueprintVersion{}, newBlueprintVersionHandler(mgr.GetCache(), mgr.GetLogger())). + Complete(reconciler) +} diff --git a/internal/controllers/component/handlers.go b/internal/controllers/component/handlers.go new file mode 100644 index 00000000..4b9974be --- /dev/null +++ b/internal/controllers/component/handlers.go @@ -0,0 +1,181 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +package component + +import ( + "context" + + "github.com/go-logr/logr" + + apitypes "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/workqueue" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + operatorv1alpha1 "github.com/sap/component-operator/api/v1alpha1" + componentcache "github.com/sap/component-operator/internal/cache/component" + "github.com/sap/component-operator/internal/object" + "github.com/sap/component-operator/pkg/meta" +) + +type componentHandler struct { + cache cache.Cache + log logr.Logger +} + +func newComponentHandler(cache cache.Cache, log logr.Logger) handler.TypedEventHandler[client.Object, reconcile.Request] { + return &componentHandler{ + cache: cache, + log: log, + } +} + +func (h *componentHandler) Create(ctx context.Context, e event.TypedCreateEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { + // new components will never be immediately ready, so nothing has to be done here +} + +func (h *componentHandler) Update(ctx context.Context, e event.TypedUpdateEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { + newComponent := e.ObjectNew.(*operatorv1alpha1.Component) + + if !newComponent.IsReady() { + return + } + + componentList := &operatorv1alpha1.ComponentList{} + if err := h.cache.List(ctx, componentList, componentcache.MatchingDependency(newComponent)); err != nil { + h.log.Error(err, "failed to list components matching dependency") + return + } + for _, c := range componentList.Items { + // TODO: be more selective (i.e. queue only depending components that really need it)? + q.Add(reconcile.Request{NamespacedName: apitypes.NamespacedName{ + Namespace: c.Namespace, + Name: c.Name, + }}) + } +} + +func (h *componentHandler) Delete(ctx context.Context, e event.TypedDeleteEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { + component := e.Object.(*operatorv1alpha1.Component) + for _, dependency := range component.Spec.Dependencies { + q.Add(reconcile.Request{NamespacedName: apitypes.NamespacedName{ + Namespace: dependency.WithDefaultNamespace(component.Namespace).Namespace, + Name: dependency.Name, + }}) + } +} + +func (h *componentHandler) Generic(ctx context.Context, e event.TypedGenericEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { + // generic events are not expected to arrive on the watch that uses this handler, so nothing to do here +} + +type blueprintHandler struct { + cache cache.Cache + log logr.Logger +} + +func newBlueprintHandler(cache cache.Cache, log logr.Logger) handler.TypedEventHandler[client.Object, reconcile.Request] { + return &blueprintHandler{ + cache: cache, + log: log, + } +} + +func (h *blueprintHandler) Create(ctx context.Context, e event.TypedCreateEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { + h.createOrUpdate(ctx, e.Object.(*operatorv1alpha1.Blueprint), q) +} + +func (h *blueprintHandler) Update(ctx context.Context, e event.TypedUpdateEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { + h.createOrUpdate(ctx, e.ObjectNew.(*operatorv1alpha1.Blueprint), q) +} + +func (h *blueprintHandler) Delete(ctx context.Context, e event.TypedDeleteEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { + // no need to queue components if blueprint is deleted (reconciliation of the component would anyway fail) +} + +func (h *blueprintHandler) Generic(ctx context.Context, e event.TypedGenericEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { + // generic events are not expected to arrive on the watch that uses this handler, so nothing to do here +} + +func (h *blueprintHandler) createOrUpdate(ctx context.Context, blueprint *operatorv1alpha1.Blueprint, q workqueue.TypedRateLimitingInterface[reconcile.Request]) { + componentList := &operatorv1alpha1.ComponentList{} + if err := h.cache.List(ctx, componentList, componentcache.MatchingBlueprint(blueprint)); err != nil { + h.log.Error(err, "failed to list components matching blueprint") + return + } + for _, c := range componentList.Items { + if c.IsReady() && c.Status.LastAttemptedDigest == blueprint.GetDigest() && c.Status.LastAttemptedRevision == blueprint.GetRevision() { + continue + } + q.Add(reconcile.Request{NamespacedName: apitypes.NamespacedName{ + Namespace: c.Namespace, + Name: c.Name, + }}) + } +} + +type fluxSourceHandler struct { + cache cache.Cache + log logr.Logger +} + +func newFluxSourceHandler(cache cache.Cache, log logr.Logger) handler.TypedEventHandler[client.Object, reconcile.Request] { + return &fluxSourceHandler{ + cache: cache, + log: log, + } +} + +func (h *fluxSourceHandler) Create(ctx context.Context, e event.TypedCreateEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { + // new sources will never be immediately ready, so nothing has to be done here +} + +func (h *fluxSourceHandler) Update(ctx context.Context, e event.TypedUpdateEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { + newSource := e.ObjectNew.(meta.FluxSource) + + if !object.IsReady(newSource) { + return + } + + artifact := newSource.GetArtifact() + if artifact == nil { + return + } + newDigest := artifact.Digest + if newDigest == "" { + return + } + newRevision := artifact.Revision + if newRevision == "" { + return + } + + componentList := &operatorv1alpha1.ComponentList{} + if err := h.cache.List(ctx, componentList, componentcache.MatchingFluxSource(newSource)); err != nil { + h.log.Error(err, "failed to list components matching flux source") + return + } + for _, c := range componentList.Items { + if c.IsReady() && c.Status.LastAttemptedDigest == newDigest && c.Status.LastAttemptedRevision == newRevision { + continue + } + q.Add(reconcile.Request{NamespacedName: apitypes.NamespacedName{ + Namespace: c.Namespace, + Name: c.Name, + }}) + } +} + +func (h *fluxSourceHandler) Delete(ctx context.Context, e event.TypedDeleteEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { + // no need to queue components if source is deleted (reconciliation of the component would anyway fail) +} + +func (h *fluxSourceHandler) Generic(ctx context.Context, e event.TypedGenericEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { + // generic events are not expected to arrive on the watch that uses this handler, so nothing to do here +} diff --git a/pkg/operator/hooks.go b/internal/controllers/component/hooks.go similarity index 94% rename from pkg/operator/hooks.go rename to internal/controllers/component/hooks.go index 4877853f..5f210425 100644 --- a/pkg/operator/hooks.go +++ b/internal/controllers/component/hooks.go @@ -3,7 +3,7 @@ SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-op SPDX-License-Identifier: Apache-2.0 */ -package operator +package component import ( "context" @@ -21,6 +21,7 @@ import ( componentoperatorruntimetypes "github.com/sap/component-operator-runtime/pkg/types" operatorv1alpha1 "github.com/sap/component-operator/api/v1alpha1" + componentcache "github.com/sap/component-operator/internal/cache/component" ) func makeFuncPostRead() component.HookFunc[*operatorv1alpha1.Component] { @@ -29,10 +30,10 @@ func makeFuncPostRead() component.HookFunc[*operatorv1alpha1.Component] { return nil } if component.Spec.Digest != "" && component.Spec.SourceRef.Artifact().Digest != component.Spec.Digest { - return componentoperatorruntimetypes.NewRetriableError(fmt.Errorf("source digest (%s) does not match specified digest (%s)", component.Spec.SourceRef.Artifact().Digest, component.Spec.Digest), ref(10*time.Second)) + return componentoperatorruntimetypes.NewRetriableError(fmt.Errorf("source digest (%s) does not match specified digest (%s)", component.Spec.SourceRef.Artifact().Digest, component.Spec.Digest), new(10*time.Second)) } if component.Spec.Revision != "" && component.Spec.SourceRef.Artifact().Revision != component.Spec.Revision { - return componentoperatorruntimetypes.NewRetriableError(fmt.Errorf("source revision (%s) does not match specified revision (%s)", component.Spec.SourceRef.Artifact().Revision, component.Spec.Revision), ref(10*time.Second)) + return componentoperatorruntimetypes.NewRetriableError(fmt.Errorf("source revision (%s) does not match specified revision (%s)", component.Spec.SourceRef.Artifact().Revision, component.Spec.Revision), new(10*time.Second)) } return nil } @@ -74,9 +75,7 @@ func makeFuncPostReconcile() component.HookFunc[*operatorv1alpha1.Component] { func makeFuncPreDelete(cache cache.Cache) component.HookFunc[*operatorv1alpha1.Component] { return func(ctx context.Context, clnt client.Client, component *operatorv1alpha1.Component) error { componentList := &operatorv1alpha1.ComponentList{} - if err := cache.List(ctx, componentList, client.MatchingFields{ - dependenciesIndexKey: client.ObjectKeyFromObject(component).String(), - }); err != nil { + if err := cache.List(ctx, componentList, componentcache.MatchingDependency(component)); err != nil { return err } if len(componentList.Items) == 0 { diff --git a/internal/controllers/component/reconciler.go b/internal/controllers/component/reconciler.go new file mode 100644 index 00000000..8eb85777 --- /dev/null +++ b/internal/controllers/component/reconciler.go @@ -0,0 +1,95 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +package component + +import ( + "github.com/pkg/errors" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/manager" + + fluxevents "github.com/fluxcd/pkg/runtime/events" + fluxsourcev1 "github.com/fluxcd/source-controller/api/v1" + fluxsourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" + + "github.com/sap/component-operator-runtime/pkg/cluster" + "github.com/sap/component-operator-runtime/pkg/component" + "github.com/sap/component-operator-runtime/pkg/reconciler" + + operatorv1alpha1 "github.com/sap/component-operator/api/v1alpha1" + "github.com/sap/component-operator/internal/generator" +) + +type ReconcilerOptions struct { + Name string + DefaultServiceAccount string + MaxConcurrentReconciles int + EventsAddress string +} + +func SetupWithManager(mgr manager.Manager, options ReconcilerOptions) (*component.Reconciler[*operatorv1alpha1.Component], error) { + blder := ctrl.NewControllerManagedBy(mgr). + WithOptions(controller.Options{MaxConcurrentReconciles: options.MaxConcurrentReconciles}). + Watches( + &operatorv1alpha1.Component{}, + newComponentHandler(mgr.GetCache(), mgr.GetLogger())). + Watches( + &operatorv1alpha1.Blueprint{}, + newBlueprintHandler(mgr.GetCache(), mgr.GetLogger())). + Watches( + &fluxsourcev1.GitRepository{}, + newFluxSourceHandler(mgr.GetCache(), mgr.GetLogger())). + Watches( + &fluxsourcev1beta2.OCIRepository{}, + newFluxSourceHandler(mgr.GetCache(), mgr.GetLogger())). + Watches( + &fluxsourcev1beta2.Bucket{}, + newFluxSourceHandler(mgr.GetCache(), mgr.GetLogger())). + Watches( + &fluxsourcev1.HelmChart{}, + newFluxSourceHandler(mgr.GetCache(), mgr.GetLogger())) + + resourceGenerator, err := generator.NewGenerator(mgr.GetClient()) + if err != nil { + return nil, errors.Wrap(err, "error initializing resource generator") + } + + newClient := func(clnt cluster.Client) (cluster.Client, error) { + if options.EventsAddress == "" { + return clnt, nil + } + eventRecorder, err := fluxevents.NewRecorderForScheme(clnt.Scheme(), clnt.EventRecorder(), mgr.GetLogger(), options.EventsAddress, options.Name) + if err != nil { + return nil, errors.Wrap(err, "error initializing wrapping event recorder") + } + return cluster.NewClient(clnt, clnt.DiscoveryClient(), eventRecorder, clnt.Config(), clnt.HttpClient()), nil + } + + reconciler := component.NewReconciler[*operatorv1alpha1.Component]( + options.Name, + resourceGenerator, + component.ReconcilerOptions{ + DefaultServiceAccount: &options.DefaultServiceAccount, + UpdatePolicy: new(reconciler.UpdatePolicySsaOverride), + NewClient: newClient, + }, + ).WithPostReadHook( + makeFuncPostRead(), + ).WithPreReconcileHook( + makeFuncPreReconcile(mgr.GetCache()), + ).WithPostReconcileHook( + makeFuncPostReconcile(), + ).WithPreDeleteHook( + makeFuncPreDelete(mgr.GetCache()), + ) + + if err := reconciler.SetupWithManagerAndBuilder(mgr, blder); err != nil { + return nil, errors.Wrapf(err, "unable to create component controller") + } + + return reconciler, nil +} diff --git a/internal/generator/factory.go b/internal/generator/factory.go index 8a9459c7..d50d7fdd 100644 --- a/internal/generator/factory.go +++ b/internal/generator/factory.go @@ -9,6 +9,7 @@ import ( "archive/tar" "bytes" "compress/gzip" + "context" "errors" "fmt" "io" @@ -16,13 +17,19 @@ import ( "net/http" "os" "path/filepath" + "regexp" + "strings" "sync" "time" + apitypes "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/sap/component-operator-runtime/pkg/manifests" "github.com/sap/component-operator-runtime/pkg/manifests/helm" "github.com/sap/component-operator-runtime/pkg/manifests/kustomize" + operatorv1alpha1 "github.com/sap/component-operator/api/v1alpha1" "github.com/sap/component-operator/internal/decrypt" ) @@ -34,35 +41,44 @@ type Item struct { // TODO: make configurable const validity = 60 * time.Minute -var items map[string]*Item -var mutex sync.Mutex +type Factory struct { + client client.Client + items map[string]*Item + mutex sync.Mutex +} + +func newFactory(clnt client.Client) *Factory { + factory := &Factory{ + client: clnt, + items: make(map[string]*Item), + } -func init() { - items = make(map[string]*Item) go func() { ticker := time.NewTicker(10 * time.Second) for { <-ticker.C now := time.Now() - mutex.Lock() - for id, item := range items { + factory.mutex.Lock() + for id, item := range factory.items { if item.ValidUntil.Before(now) { - delete(items, id) + delete(factory.items, id) } } - mutex.Unlock() + factory.mutex.Unlock() } }() + + return factory } -func GetGenerator(url string, path string, digest string, decryptionProvider string, decryptionKeys map[string][]byte) (manifests.Generator, error) { - mutex.Lock() - defer mutex.Unlock() +func (f *Factory) GetGenerator(url string, path string, digest string, decryptionProvider string, decryptionKeys map[string][]byte) (manifests.Generator, error) { + f.mutex.Lock() + defer f.mutex.Unlock() // note: url is actually not needed in the generator id, digest and path is enough to identify the content id := url + "\n" + digest + "\n" + path + "\n" + decryptionProvider + "\n" + calculateDigest(decryptionKeys) - if item, ok := items[id]; ok { + if item, ok := f.items[id]; ok { item.ValidUntil = time.Now().Add(validity) return item.Generator, nil } else { @@ -87,8 +103,14 @@ func GetGenerator(url string, path string, digest string, decryptionProvider str return nil, fmt.Errorf("invalid decryption provider: %s", decryptionProvider) } } - if err := downloadArchive(url, tmpdir); err != nil { - return nil, err + if strings.HasPrefix(url, "blueprint://") { + if err := f.downloadBlueprint(url, tmpdir); err != nil { + return nil, err + } + } else { + if err := f.downloadArchive(url, tmpdir); err != nil { + return nil, err + } } fullPath := filepath.Join(tmpdir, path) if info, err := os.Stat(fullPath); err != nil { @@ -123,12 +145,41 @@ func GetGenerator(url string, path string, digest string, decryptionProvider str } else { return nil, err } - items[id] = &Item{Generator: generator, ValidUntil: time.Now().Add(validity)} + f.items[id] = &Item{Generator: generator, ValidUntil: time.Now().Add(validity)} return generator, nil } } -func downloadArchive(url string, targetPath string) error { +func (f *Factory) downloadBlueprint(url string, targetPath string) error { + if m := regexp.MustCompile(`^blueprint://([^/]+)/([^/]+)/([^/]+)$`).FindStringSubmatch(url); m != nil { + blueprintNamespace := m[1] + blueprintName := m[2] + blueprintDigest := m[3] + + blueprintVersion := operatorv1alpha1.BlueprintVersion{} + if err := f.client.Get(context.TODO(), apitypes.NamespacedName{Namespace: blueprintNamespace, Name: fmt.Sprintf("%s--%s", blueprintName, blueprintDigest)}, &blueprintVersion); err != nil { + return err + } + + for path, content := range blueprintVersion.Spec.Files { + if path != filepath.Clean(path) || strings.Contains(path, "..") { + return fmt.Errorf("invalid file path in blueprint: %s", path) + } + if err := os.MkdirAll(filepath.Join(targetPath, filepath.Dir(path)), 0755); err != nil { + return err + } + if err := os.WriteFile(filepath.Join(targetPath, path), []byte(content), 0644); err != nil { + return err + } + } + + return nil + } else { + return fmt.Errorf("invalid blueprint URL: %s", url) + } +} + +func (f *Factory) downloadArchive(url string, targetPath string) error { // TODO: use a local or even global file cache resp, err := http.Get(url) if err != nil { diff --git a/internal/generator/generator.go b/internal/generator/generator.go index b8ed544d..4550c0f7 100644 --- a/internal/generator/generator.go +++ b/internal/generator/generator.go @@ -21,12 +21,16 @@ import ( operatorv1alpha1 "github.com/sap/component-operator/api/v1alpha1" ) -type Generator struct{} +type Generator struct { + factory *Factory +} var _ manifests.Generator = &Generator{} -func NewGenerator() (*Generator, error) { - return &Generator{}, nil +func NewGenerator(clnt client.Client) (*Generator, error) { + return &Generator{ + factory: newFactory(clnt), + }, nil } func (g *Generator) Generate(ctx context.Context, namespace string, name string, parameters componentoperatorruntimetypes.Unstructurable) ([]client.Object, error) { @@ -48,7 +52,7 @@ func (g *Generator) Generate(ctx context.Context, namespace string, name string, decryptionKeys = spec.Decryption.SecretRef.Data() } - generator, err := GetGenerator(url, path, digest, decryptionProvider, decryptionKeys) + generator, err := g.factory.GetGenerator(url, path, digest, decryptionProvider, decryptionKeys) if err != nil { return nil, err } diff --git a/internal/httprepository/checker.go b/internal/httprepository/checker.go new file mode 100644 index 00000000..d81ec9e6 --- /dev/null +++ b/internal/httprepository/checker.go @@ -0,0 +1,79 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +package httprepository + +import ( + "context" + "time" + + "github.com/go-logr/logr" + + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/manager" + + "github.com/sap/component-operator-runtime/pkg/component" + + operatorv1alpha1 "github.com/sap/component-operator/api/v1alpha1" + componentcache "github.com/sap/component-operator/internal/cache/component" + "github.com/sap/component-operator/internal/httprepository/util" +) + +type checker struct { + cache cache.Cache + componentReconciler *component.Reconciler[*operatorv1alpha1.Component] + logger logr.Logger +} + +var _ manager.Runnable = &checker{} +var _ manager.LeaderElectionRunnable = &checker{} + +func newChecker(cache cache.Cache, componentReconciler *component.Reconciler[*operatorv1alpha1.Component], logger logr.Logger) *checker { + return &checker{ + cache: cache, + componentReconciler: componentReconciler, + logger: logger, + } +} + +func (c *checker) Start(ctx context.Context) error { + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return nil + case <-ticker.C: + } + + componentList := &operatorv1alpha1.ComponentList{} + if err := c.cache.List(context.TODO(), componentList, componentcache.HasHttpRepository()); err != nil { + c.logger.Error(err, "error listing components") + continue + } + for _, component := range componentList.Items { + url := component.Spec.SourceRef.HttpRepository.Url + digestHeader := component.Spec.SourceRef.HttpRepository.DigestHeader + revisionHeader := component.Spec.SourceRef.HttpRepository.RevisionHeader + _, digest, revision, err := util.GetArtifact(url, digestHeader, revisionHeader) + if err == nil { + if digest != component.Status.LastAttemptedDigest || revision != component.Status.LastAttemptedRevision { + c.componentReconciler.Trigger(component.Namespace, component.Name) + } + } else { + c.logger.Error(err, "error fetching revision from http repository", "url", url, "digestHeader", digestHeader, "revisionHeader", revisionHeader) + } + } + } +} + +func (c *checker) NeedLeaderElection() bool { + return true +} + +func SetupWithManager(mgr manager.Manager, componentReconciler *component.Reconciler[*operatorv1alpha1.Component]) error { + mgr.Add(newChecker(mgr.GetCache(), componentReconciler, mgr.GetLogger())) + return nil +} diff --git a/internal/sources/httprepository/util/artifact.go b/internal/httprepository/util/artifact.go similarity index 100% rename from internal/sources/httprepository/util/artifact.go rename to internal/httprepository/util/artifact.go diff --git a/internal/sources/flux/cache/cache.go b/internal/sources/flux/cache/cache.go deleted file mode 100644 index 5ef39da6..00000000 --- a/internal/sources/flux/cache/cache.go +++ /dev/null @@ -1,163 +0,0 @@ -/* -SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator contributors -SPDX-License-Identifier: Apache-2.0 -*/ - -package cache - -import ( - "context" - - "github.com/pkg/errors" - - apitypes "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/util/workqueue" - "sigs.k8s.io/controller-runtime/pkg/builder" - "sigs.k8s.io/controller-runtime/pkg/cache" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - fluxsourcev1 "github.com/fluxcd/source-controller/api/v1" - fluxsourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" - - operatorv1alpha1 "github.com/sap/component-operator/api/v1alpha1" - "github.com/sap/component-operator/internal/object" - "github.com/sap/component-operator/internal/sources/flux/types" -) - -const ( - gitRepositoryIndexKey string = ".metadata.flux.gitRepository" - ociRepositoryIndexKey string = ".metadata.flux.ociRepository" - bucketIndexKey string = ".metadata.flux.bucket" - helmChartIndexKey string = ".metadata.flux.helmChart" -) - -func SetupCache(mgr manager.Manager, blder *builder.Builder) error { - // TODO: should we pass a meaningful context? - if err := mgr.GetCache().IndexField(context.TODO(), &operatorv1alpha1.Component{}, gitRepositoryIndexKey, indexByGitRepository); err != nil { - return errors.Wrapf(err, "failed setting index field %s", gitRepositoryIndexKey) - } - if err := mgr.GetCache().IndexField(context.TODO(), &operatorv1alpha1.Component{}, ociRepositoryIndexKey, indexByOciRepository); err != nil { - return errors.Wrapf(err, "failed setting index field %s", ociRepositoryIndexKey) - } - if err := mgr.GetCache().IndexField(context.TODO(), &operatorv1alpha1.Component{}, bucketIndexKey, indexByBucket); err != nil { - return errors.Wrapf(err, "failed setting index field %s", bucketIndexKey) - } - if err := mgr.GetCache().IndexField(context.TODO(), &operatorv1alpha1.Component{}, helmChartIndexKey, indexByHelmChart); err != nil { - return errors.Wrapf(err, "failed setting index field %s", helmChartIndexKey) - } - - blder. - Watches( - &fluxsourcev1.GitRepository{}, - newSourceHandler(mgr.GetCache(), gitRepositoryIndexKey)). - Watches( - &fluxsourcev1beta2.OCIRepository{}, - newSourceHandler(mgr.GetCache(), ociRepositoryIndexKey)). - Watches( - &fluxsourcev1beta2.Bucket{}, - newSourceHandler(mgr.GetCache(), bucketIndexKey)). - Watches( - &fluxsourcev1.HelmChart{}, - newSourceHandler(mgr.GetCache(), helmChartIndexKey)) - - return nil -} - -func indexByGitRepository(object client.Object) []string { - component := object.(*operatorv1alpha1.Component) - if component.Spec.SourceRef.FluxGitRepository == nil { - return nil - } - return []string{component.Spec.SourceRef.FluxGitRepository.WithDefaultNamespace(component.Namespace).String()} -} - -func indexByOciRepository(object client.Object) []string { - component := object.(*operatorv1alpha1.Component) - if component.Spec.SourceRef.FluxOciRepository == nil { - return nil - } - return []string{component.Spec.SourceRef.FluxOciRepository.WithDefaultNamespace(component.Namespace).String()} -} - -func indexByBucket(object client.Object) []string { - component := object.(*operatorv1alpha1.Component) - if component.Spec.SourceRef.FluxBucket == nil { - return nil - } - return []string{component.Spec.SourceRef.FluxBucket.WithDefaultNamespace(component.Namespace).String()} -} - -func indexByHelmChart(object client.Object) []string { - component := object.(*operatorv1alpha1.Component) - if component.Spec.SourceRef.FluxHelmChart == nil { - return nil - } - return []string{component.Spec.SourceRef.FluxHelmChart.WithDefaultNamespace(component.Namespace).String()} -} - -type sourceHandler struct { - cache cache.Cache - indexKey string -} - -func newSourceHandler(cache cache.Cache, indexKey string) handler.TypedEventHandler[client.Object, reconcile.Request] { - return &sourceHandler{ - cache: cache, - indexKey: indexKey, - } -} - -func (h *sourceHandler) Create(ctx context.Context, e event.TypedCreateEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { - // new sources will never be immediately ready, so nothing has to be done here -} - -func (h *sourceHandler) Update(ctx context.Context, e event.TypedUpdateEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { - newSource := e.ObjectNew.(types.Source) - - if !object.IsReady(newSource) { - return - } - - artifact := newSource.GetArtifact() - if artifact == nil { - return - } - newDigest := artifact.Digest - if newDigest == "" { - return - } - newRevision := artifact.Revision - if newRevision == "" { - return - } - - componentList := &operatorv1alpha1.ComponentList{} - if err := h.cache.List(ctx, componentList, client.MatchingFields{ - h.indexKey: client.ObjectKeyFromObject(e.ObjectNew).String(), - }); err != nil { - // TODO - // log.Error(err, "failed to list components") - return - } - for _, c := range componentList.Items { - if c.IsReady() && c.Status.LastAttemptedDigest == newDigest && c.Status.LastAttemptedRevision == newRevision { - continue - } - q.Add(reconcile.Request{NamespacedName: apitypes.NamespacedName{ - Namespace: c.Namespace, - Name: c.Name, - }}) - } -} - -func (h *sourceHandler) Delete(ctx context.Context, e event.TypedDeleteEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { - // no need to queue components if source is deleted (reconciliation of the component would anyway fail) -} - -func (h *sourceHandler) Generic(ctx context.Context, e event.TypedGenericEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { - // generic events are not expected to arrive on the watch that uses this handler, so nothing to do here -} diff --git a/internal/sources/httprepository/checker/checker.go b/internal/sources/httprepository/checker/checker.go deleted file mode 100644 index fa4623e9..00000000 --- a/internal/sources/httprepository/checker/checker.go +++ /dev/null @@ -1,75 +0,0 @@ -/* -SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator contributors -SPDX-License-Identifier: Apache-2.0 -*/ - -package checker - -import ( - "context" - "time" - - "github.com/go-logr/logr" - - "sigs.k8s.io/controller-runtime/pkg/cache" - "sigs.k8s.io/controller-runtime/pkg/manager" - - "github.com/sap/component-operator-runtime/pkg/component" - - operatorv1alpha1 "github.com/sap/component-operator/api/v1alpha1" - "github.com/sap/component-operator/internal/sources/httprepository/util" -) - -type Checker struct { - cache cache.Cache - reconciler *component.Reconciler[*operatorv1alpha1.Component] - logger logr.Logger -} - -var _ manager.Runnable = &Checker{} -var _ manager.LeaderElectionRunnable = &Checker{} - -func NewChecker(cache cache.Cache, reconciler *component.Reconciler[*operatorv1alpha1.Component], logger logr.Logger) *Checker { - return &Checker{ - cache: cache, - reconciler: reconciler, - logger: logger, - } -} - -func (c *Checker) Start(ctx context.Context) error { - ticker := time.NewTicker(30 * time.Second) - defer ticker.Stop() - for { - select { - case <-ctx.Done(): - return nil - case <-ticker.C: - } - - componentList := &operatorv1alpha1.ComponentList{} - if err := c.cache.List(context.TODO(), componentList); err != nil { - c.logger.Error(err, "error listing components") - continue - } - for _, component := range componentList.Items { - if component.Spec.SourceRef.HttpRepository != nil { - url := component.Spec.SourceRef.HttpRepository.Url - digestHeader := component.Spec.SourceRef.HttpRepository.DigestHeader - revisionHeader := component.Spec.SourceRef.HttpRepository.RevisionHeader - _, digest, revision, err := util.GetArtifact(url, digestHeader, revisionHeader) - if err == nil { - if digest != component.Status.LastAttemptedDigest || revision != component.Status.LastAttemptedRevision { - c.reconciler.Trigger(component.Namespace, component.Name) - } - } else { - c.logger.Error(err, "error fetching revision from http repository", "url", url, "digestHeader", digestHeader, "revisionHeader", revisionHeader) - } - } - } - } -} - -func (c *Checker) NeedLeaderElection() bool { - return true -} diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go index a4e40d3c..f57579cb 100644 --- a/pkg/client/clientset/versioned/fake/clientset_generated.go +++ b/pkg/client/clientset/versioned/fake/clientset_generated.go @@ -23,10 +23,6 @@ import ( // It's backed by a very simple object tracker that processes creates, updates and deletions as-is, // without applying any field management, validations and/or defaults. It shouldn't be considered a replacement // for a real clientset and is mostly useful in simple unit tests. -// -// Deprecated: NewClientset replaces this with support for field management, which significantly improves -// server side apply testing. NewClientset is only available when apply configurations are generated (e.g. -// via --with-applyconfig). func NewSimpleClientset(objects ...runtime.Object) *Clientset { o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) for _, obj := range objects { @@ -72,7 +68,7 @@ func (c *Clientset) Tracker() testing.ObjectTracker { return c.tracker } -// IsWatchListSemanticsSupported informs the reflector that this client +// IsWatchListSemanticsUnSupported informs the reflector that this client // doesn't support WatchList semantics. // // This is a synthetic method whose sole purpose is to satisfy the optional diff --git a/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/blueprint.go b/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/blueprint.go new file mode 100644 index 00000000..1160043b --- /dev/null +++ b/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/blueprint.go @@ -0,0 +1,57 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + corecssapcomv1alpha1 "github.com/sap/component-operator/api/v1alpha1" + scheme "github.com/sap/component-operator/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" +) + +// BlueprintsGetter has a method to return a BlueprintInterface. +// A group's client should implement this interface. +type BlueprintsGetter interface { + Blueprints(namespace string) BlueprintInterface +} + +// BlueprintInterface has methods to work with Blueprint resources. +type BlueprintInterface interface { + Create(ctx context.Context, blueprint *corecssapcomv1alpha1.Blueprint, opts v1.CreateOptions) (*corecssapcomv1alpha1.Blueprint, error) + Update(ctx context.Context, blueprint *corecssapcomv1alpha1.Blueprint, opts v1.UpdateOptions) (*corecssapcomv1alpha1.Blueprint, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*corecssapcomv1alpha1.Blueprint, error) + List(ctx context.Context, opts v1.ListOptions) (*corecssapcomv1alpha1.BlueprintList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *corecssapcomv1alpha1.Blueprint, err error) + BlueprintExpansion +} + +// blueprints implements BlueprintInterface +type blueprints struct { + *gentype.ClientWithList[*corecssapcomv1alpha1.Blueprint, *corecssapcomv1alpha1.BlueprintList] +} + +// newBlueprints returns a Blueprints +func newBlueprints(c *CoreV1alpha1Client, namespace string) *blueprints { + return &blueprints{ + gentype.NewClientWithList[*corecssapcomv1alpha1.Blueprint, *corecssapcomv1alpha1.BlueprintList]( + "blueprints", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *corecssapcomv1alpha1.Blueprint { return &corecssapcomv1alpha1.Blueprint{} }, + func() *corecssapcomv1alpha1.BlueprintList { return &corecssapcomv1alpha1.BlueprintList{} }, + ), + } +} diff --git a/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/blueprintversion.go b/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/blueprintversion.go new file mode 100644 index 00000000..aac9bc5c --- /dev/null +++ b/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/blueprintversion.go @@ -0,0 +1,57 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + + corecssapcomv1alpha1 "github.com/sap/component-operator/api/v1alpha1" + scheme "github.com/sap/component-operator/pkg/client/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + gentype "k8s.io/client-go/gentype" +) + +// BlueprintVersionsGetter has a method to return a BlueprintVersionInterface. +// A group's client should implement this interface. +type BlueprintVersionsGetter interface { + BlueprintVersions(namespace string) BlueprintVersionInterface +} + +// BlueprintVersionInterface has methods to work with BlueprintVersion resources. +type BlueprintVersionInterface interface { + Create(ctx context.Context, blueprintVersion *corecssapcomv1alpha1.BlueprintVersion, opts v1.CreateOptions) (*corecssapcomv1alpha1.BlueprintVersion, error) + Update(ctx context.Context, blueprintVersion *corecssapcomv1alpha1.BlueprintVersion, opts v1.UpdateOptions) (*corecssapcomv1alpha1.BlueprintVersion, error) + Delete(ctx context.Context, name string, opts v1.DeleteOptions) error + DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error + Get(ctx context.Context, name string, opts v1.GetOptions) (*corecssapcomv1alpha1.BlueprintVersion, error) + List(ctx context.Context, opts v1.ListOptions) (*corecssapcomv1alpha1.BlueprintVersionList, error) + Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *corecssapcomv1alpha1.BlueprintVersion, err error) + BlueprintVersionExpansion +} + +// blueprintVersions implements BlueprintVersionInterface +type blueprintVersions struct { + *gentype.ClientWithList[*corecssapcomv1alpha1.BlueprintVersion, *corecssapcomv1alpha1.BlueprintVersionList] +} + +// newBlueprintVersions returns a BlueprintVersions +func newBlueprintVersions(c *CoreV1alpha1Client, namespace string) *blueprintVersions { + return &blueprintVersions{ + gentype.NewClientWithList[*corecssapcomv1alpha1.BlueprintVersion, *corecssapcomv1alpha1.BlueprintVersionList]( + "blueprintversions", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *corecssapcomv1alpha1.BlueprintVersion { return &corecssapcomv1alpha1.BlueprintVersion{} }, + func() *corecssapcomv1alpha1.BlueprintVersionList { return &corecssapcomv1alpha1.BlueprintVersionList{} }, + ), + } +} diff --git a/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/core.cs.sap.com_client.go b/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/core.cs.sap.com_client.go index bc6e3fc8..eb221b97 100644 --- a/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/core.cs.sap.com_client.go +++ b/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/core.cs.sap.com_client.go @@ -17,6 +17,8 @@ import ( type CoreV1alpha1Interface interface { RESTClient() rest.Interface + BlueprintsGetter + BlueprintVersionsGetter ComponentsGetter } @@ -25,6 +27,14 @@ type CoreV1alpha1Client struct { restClient rest.Interface } +func (c *CoreV1alpha1Client) Blueprints(namespace string) BlueprintInterface { + return newBlueprints(c, namespace) +} + +func (c *CoreV1alpha1Client) BlueprintVersions(namespace string) BlueprintVersionInterface { + return newBlueprintVersions(c, namespace) +} + func (c *CoreV1alpha1Client) Components(namespace string) ComponentInterface { return newComponents(c, namespace) } diff --git a/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/fake/fake_blueprint.go b/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/fake/fake_blueprint.go new file mode 100644 index 00000000..2640d35c --- /dev/null +++ b/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/fake/fake_blueprint.go @@ -0,0 +1,39 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/sap/component-operator/api/v1alpha1" + corecssapcomv1alpha1 "github.com/sap/component-operator/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1" + gentype "k8s.io/client-go/gentype" +) + +// fakeBlueprints implements BlueprintInterface +type fakeBlueprints struct { + *gentype.FakeClientWithList[*v1alpha1.Blueprint, *v1alpha1.BlueprintList] + Fake *FakeCoreV1alpha1 +} + +func newFakeBlueprints(fake *FakeCoreV1alpha1, namespace string) corecssapcomv1alpha1.BlueprintInterface { + return &fakeBlueprints{ + gentype.NewFakeClientWithList[*v1alpha1.Blueprint, *v1alpha1.BlueprintList]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("blueprints"), + v1alpha1.SchemeGroupVersion.WithKind("Blueprint"), + func() *v1alpha1.Blueprint { return &v1alpha1.Blueprint{} }, + func() *v1alpha1.BlueprintList { return &v1alpha1.BlueprintList{} }, + func(dst, src *v1alpha1.BlueprintList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.BlueprintList) []*v1alpha1.Blueprint { return gentype.ToPointerSlice(list.Items) }, + func(list *v1alpha1.BlueprintList, items []*v1alpha1.Blueprint) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/fake/fake_blueprintversion.go b/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/fake/fake_blueprintversion.go new file mode 100644 index 00000000..c6ca3d94 --- /dev/null +++ b/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/fake/fake_blueprintversion.go @@ -0,0 +1,41 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + v1alpha1 "github.com/sap/component-operator/api/v1alpha1" + corecssapcomv1alpha1 "github.com/sap/component-operator/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1" + gentype "k8s.io/client-go/gentype" +) + +// fakeBlueprintVersions implements BlueprintVersionInterface +type fakeBlueprintVersions struct { + *gentype.FakeClientWithList[*v1alpha1.BlueprintVersion, *v1alpha1.BlueprintVersionList] + Fake *FakeCoreV1alpha1 +} + +func newFakeBlueprintVersions(fake *FakeCoreV1alpha1, namespace string) corecssapcomv1alpha1.BlueprintVersionInterface { + return &fakeBlueprintVersions{ + gentype.NewFakeClientWithList[*v1alpha1.BlueprintVersion, *v1alpha1.BlueprintVersionList]( + fake.Fake, + namespace, + v1alpha1.SchemeGroupVersion.WithResource("blueprintversions"), + v1alpha1.SchemeGroupVersion.WithKind("BlueprintVersion"), + func() *v1alpha1.BlueprintVersion { return &v1alpha1.BlueprintVersion{} }, + func() *v1alpha1.BlueprintVersionList { return &v1alpha1.BlueprintVersionList{} }, + func(dst, src *v1alpha1.BlueprintVersionList) { dst.ListMeta = src.ListMeta }, + func(list *v1alpha1.BlueprintVersionList) []*v1alpha1.BlueprintVersion { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1alpha1.BlueprintVersionList, items []*v1alpha1.BlueprintVersion) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, + } +} diff --git a/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/fake/fake_core.cs.sap.com_client.go b/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/fake/fake_core.cs.sap.com_client.go index 7f0da84b..54b7c85d 100644 --- a/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/fake/fake_core.cs.sap.com_client.go +++ b/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/fake/fake_core.cs.sap.com_client.go @@ -17,6 +17,14 @@ type FakeCoreV1alpha1 struct { *testing.Fake } +func (c *FakeCoreV1alpha1) Blueprints(namespace string) v1alpha1.BlueprintInterface { + return newFakeBlueprints(c, namespace) +} + +func (c *FakeCoreV1alpha1) BlueprintVersions(namespace string) v1alpha1.BlueprintVersionInterface { + return newFakeBlueprintVersions(c, namespace) +} + func (c *FakeCoreV1alpha1) Components(namespace string) v1alpha1.ComponentInterface { return newFakeComponents(c, namespace) } diff --git a/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/generated_expansion.go b/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/generated_expansion.go index c1113689..face0b91 100644 --- a/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/generated_expansion.go +++ b/pkg/client/clientset/versioned/typed/core.cs.sap.com/v1alpha1/generated_expansion.go @@ -7,4 +7,8 @@ SPDX-License-Identifier: Apache-2.0 package v1alpha1 +type BlueprintExpansion interface{} + +type BlueprintVersionExpansion interface{} + type ComponentExpansion interface{} diff --git a/pkg/client/informers/externalversions/core.cs.sap.com/v1alpha1/blueprint.go b/pkg/client/informers/externalversions/core.cs.sap.com/v1alpha1/blueprint.go new file mode 100644 index 00000000..ab59ee9b --- /dev/null +++ b/pkg/client/informers/externalversions/core.cs.sap.com/v1alpha1/blueprint.go @@ -0,0 +1,105 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + time "time" + + apiscorecssapcomv1alpha1 "github.com/sap/component-operator/api/v1alpha1" + versioned "github.com/sap/component-operator/pkg/client/clientset/versioned" + internalinterfaces "github.com/sap/component-operator/pkg/client/informers/externalversions/internalinterfaces" + corecssapcomv1alpha1 "github.com/sap/component-operator/pkg/client/listers/core.cs.sap.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// BlueprintInformer provides access to a shared informer and lister for +// Blueprints. +type BlueprintInformer interface { + Informer() cache.SharedIndexInformer + Lister() corecssapcomv1alpha1.BlueprintLister +} + +type blueprintInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewBlueprintInformer constructs a new informer for Blueprint type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewBlueprintInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewBlueprintInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) +} + +// NewFilteredBlueprintInformer constructs a new informer for Blueprint type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredBlueprintInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return NewBlueprintInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewBlueprintInformerWithOptions constructs a new informer for Blueprint type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewBlueprintInformerWithOptions(client versioned.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "core.cs.sap.com", Version: "v1alpha1", Resource: "blueprints"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( + cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.CoreV1alpha1().Blueprints(namespace).List(context.Background(), opts) + }, + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.CoreV1alpha1().Blueprints(namespace).Watch(context.Background(), opts) + }, + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.CoreV1alpha1().Blueprints(namespace).List(ctx, opts) + }, + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.CoreV1alpha1().Blueprints(namespace).Watch(ctx, opts) + }, + }, client), + &apiscorecssapcomv1alpha1.Blueprint{}, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, + ) +} + +func (f *blueprintInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewBlueprintInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) +} + +func (f *blueprintInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apiscorecssapcomv1alpha1.Blueprint{}, f.defaultInformer) +} + +func (f *blueprintInformer) Lister() corecssapcomv1alpha1.BlueprintLister { + return corecssapcomv1alpha1.NewBlueprintLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/core.cs.sap.com/v1alpha1/blueprintversion.go b/pkg/client/informers/externalversions/core.cs.sap.com/v1alpha1/blueprintversion.go new file mode 100644 index 00000000..32bf7b3e --- /dev/null +++ b/pkg/client/informers/externalversions/core.cs.sap.com/v1alpha1/blueprintversion.go @@ -0,0 +1,105 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + context "context" + time "time" + + apiscorecssapcomv1alpha1 "github.com/sap/component-operator/api/v1alpha1" + versioned "github.com/sap/component-operator/pkg/client/clientset/versioned" + internalinterfaces "github.com/sap/component-operator/pkg/client/informers/externalversions/internalinterfaces" + corecssapcomv1alpha1 "github.com/sap/component-operator/pkg/client/listers/core.cs.sap.com/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// BlueprintVersionInformer provides access to a shared informer and lister for +// BlueprintVersions. +type BlueprintVersionInformer interface { + Informer() cache.SharedIndexInformer + Lister() corecssapcomv1alpha1.BlueprintVersionLister +} + +type blueprintVersionInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewBlueprintVersionInformer constructs a new informer for BlueprintVersion type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewBlueprintVersionInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewBlueprintVersionInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) +} + +// NewFilteredBlueprintVersionInformer constructs a new informer for BlueprintVersion type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredBlueprintVersionInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return NewBlueprintVersionInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewBlueprintVersionInformerWithOptions constructs a new informer for BlueprintVersion type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewBlueprintVersionInformerWithOptions(client versioned.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "core.cs.sap.com", Version: "v1alpha1", Resource: "blueprintversions"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( + cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.CoreV1alpha1().BlueprintVersions(namespace).List(context.Background(), opts) + }, + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.CoreV1alpha1().BlueprintVersions(namespace).Watch(context.Background(), opts) + }, + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.CoreV1alpha1().BlueprintVersions(namespace).List(ctx, opts) + }, + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&opts) + } + return client.CoreV1alpha1().BlueprintVersions(namespace).Watch(ctx, opts) + }, + }, client), + &apiscorecssapcomv1alpha1.BlueprintVersion{}, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, + ) +} + +func (f *blueprintVersionInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewBlueprintVersionInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) +} + +func (f *blueprintVersionInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apiscorecssapcomv1alpha1.BlueprintVersion{}, f.defaultInformer) +} + +func (f *blueprintVersionInformer) Lister() corecssapcomv1alpha1.BlueprintVersionLister { + return corecssapcomv1alpha1.NewBlueprintVersionLister(f.Informer().GetIndexer()) +} diff --git a/pkg/client/informers/externalversions/core.cs.sap.com/v1alpha1/component.go b/pkg/client/informers/externalversions/core.cs.sap.com/v1alpha1/component.go index da796a47..059b1870 100644 --- a/pkg/client/informers/externalversions/core.cs.sap.com/v1alpha1/component.go +++ b/pkg/client/informers/externalversions/core.cs.sap.com/v1alpha1/component.go @@ -17,6 +17,7 @@ import ( corecssapcomv1alpha1 "github.com/sap/component-operator/pkg/client/listers/core.cs.sap.com/v1alpha1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" watch "k8s.io/apimachinery/pkg/watch" cache "k8s.io/client-go/tools/cache" ) @@ -38,48 +39,61 @@ type componentInformer struct { // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewComponentInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { - return NewFilteredComponentInformer(client, namespace, resyncPeriod, indexers, nil) + return NewComponentInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers}) } // NewFilteredComponentInformer constructs a new informer for Component type. // Always prefer using an informer factory to get a shared informer instead of getting an independent // one. This reduces memory footprint and number of connections to the server. func NewFilteredComponentInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { - return cache.NewSharedIndexInformer( + return NewComponentInformerWithOptions(client, namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: indexers, TweakListOptions: tweakListOptions}) +} + +// NewComponentInformerWithOptions constructs a new informer for Component type with additional options. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewComponentInformerWithOptions(client versioned.Interface, namespace string, options internalinterfaces.InformerOptions) cache.SharedIndexInformer { + gvr := schema.GroupVersionResource{Group: "core.cs.sap.com", Version: "v1alpha1", Resource: "components"} + identifier := options.InformerName.WithResource(gvr) + tweakListOptions := options.TweakListOptions + return cache.NewSharedIndexInformerWithOptions( cache.ToListWatcherWithWatchListSemantics(&cache.ListWatch{ - ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + ListFunc: func(opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1alpha1().Components(namespace).List(context.Background(), options) + return client.CoreV1alpha1().Components(namespace).List(context.Background(), opts) }, - WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + WatchFunc: func(opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1alpha1().Components(namespace).Watch(context.Background(), options) + return client.CoreV1alpha1().Components(namespace).Watch(context.Background(), opts) }, - ListWithContextFunc: func(ctx context.Context, options v1.ListOptions) (runtime.Object, error) { + ListWithContextFunc: func(ctx context.Context, opts v1.ListOptions) (runtime.Object, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1alpha1().Components(namespace).List(ctx, options) + return client.CoreV1alpha1().Components(namespace).List(ctx, opts) }, - WatchFuncWithContext: func(ctx context.Context, options v1.ListOptions) (watch.Interface, error) { + WatchFuncWithContext: func(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { - tweakListOptions(&options) + tweakListOptions(&opts) } - return client.CoreV1alpha1().Components(namespace).Watch(ctx, options) + return client.CoreV1alpha1().Components(namespace).Watch(ctx, opts) }, }, client), &apiscorecssapcomv1alpha1.Component{}, - resyncPeriod, - indexers, + cache.SharedIndexInformerOptions{ + ResyncPeriod: options.ResyncPeriod, + Indexers: options.Indexers, + Identifier: identifier, + }, ) } func (f *componentInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { - return NewFilteredComponentInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) + return NewComponentInformerWithOptions(client, f.namespace, internalinterfaces.InformerOptions{ResyncPeriod: resyncPeriod, Indexers: cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, InformerName: f.factory.InformerName(), TweakListOptions: f.tweakListOptions}) } func (f *componentInformer) Informer() cache.SharedIndexInformer { diff --git a/pkg/client/informers/externalversions/core.cs.sap.com/v1alpha1/interface.go b/pkg/client/informers/externalversions/core.cs.sap.com/v1alpha1/interface.go index 74bba92f..2fd126cb 100644 --- a/pkg/client/informers/externalversions/core.cs.sap.com/v1alpha1/interface.go +++ b/pkg/client/informers/externalversions/core.cs.sap.com/v1alpha1/interface.go @@ -13,6 +13,10 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { + // Blueprints returns a BlueprintInformer. + Blueprints() BlueprintInformer + // BlueprintVersions returns a BlueprintVersionInformer. + BlueprintVersions() BlueprintVersionInformer // Components returns a ComponentInformer. Components() ComponentInformer } @@ -28,6 +32,16 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } +// Blueprints returns a BlueprintInformer. +func (v *version) Blueprints() BlueprintInformer { + return &blueprintInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + +// BlueprintVersions returns a BlueprintVersionInformer. +func (v *version) BlueprintVersions() BlueprintVersionInformer { + return &blueprintVersionInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // Components returns a ComponentInformer. func (v *version) Components() ComponentInformer { return &componentInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/client/informers/externalversions/factory.go b/pkg/client/informers/externalversions/factory.go index de9535cb..3825ce7b 100644 --- a/pkg/client/informers/externalversions/factory.go +++ b/pkg/client/informers/externalversions/factory.go @@ -8,6 +8,7 @@ SPDX-License-Identifier: Apache-2.0 package externalversions import ( + context "context" reflect "reflect" sync "sync" time "time" @@ -18,6 +19,7 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" schema "k8s.io/apimachinery/pkg/runtime/schema" + wait "k8s.io/apimachinery/pkg/util/wait" cache "k8s.io/client-go/tools/cache" ) @@ -32,6 +34,7 @@ type sharedInformerFactory struct { defaultResync time.Duration customResync map[reflect.Type]time.Duration transform cache.TransformFunc + informerName *cache.InformerName informers map[reflect.Type]cache.SharedIndexInformer // startedInformers is used for tracking which informers have been started. @@ -78,6 +81,21 @@ func WithTransform(transform cache.TransformFunc) SharedInformerOption { } } +// WithInformerName sets the InformerName for informer identity used in metrics. +// The InformerName must be created via cache.NewInformerName() at startup, +// which validates global uniqueness. Each informer type will register its +// GVR under this name. +func WithInformerName(informerName *cache.InformerName) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.informerName = informerName + return factory + } +} + +func (f *sharedInformerFactory) InformerName() *cache.InformerName { + return f.informerName +} + // NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { return NewSharedInformerFactoryWithOptions(client, defaultResync) @@ -112,6 +130,10 @@ func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResy } func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { + f.StartWithContext(wait.ContextForChannel(stopCh)) +} + +func (f *sharedInformerFactory) StartWithContext(ctx context.Context) { f.lock.Lock() defer f.lock.Unlock() @@ -121,15 +143,9 @@ func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { for informerType, informer := range f.informers { if !f.startedInformers[informerType] { - f.wg.Add(1) - // We need a new variable in each loop iteration, - // otherwise the goroutine would use the loop variable - // and that keeps changing. - informer := informer - go func() { - defer f.wg.Done() - informer.Run(stopCh) - }() + f.wg.Go(func() { + informer.RunWithContext(ctx) + }) f.startedInformers[informerType] = true } } @@ -142,9 +158,15 @@ func (f *sharedInformerFactory) Shutdown() { // Will return immediately if there is nothing to wait for. f.wg.Wait() + f.informerName.Release() } func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { + result := f.WaitForCacheSyncWithContext(wait.ContextForChannel(stopCh)) + return result.Synced +} + +func (f *sharedInformerFactory) WaitForCacheSyncWithContext(ctx context.Context) cache.SyncResult { informers := func() map[reflect.Type]cache.SharedIndexInformer { f.lock.Lock() defer f.lock.Unlock() @@ -158,10 +180,31 @@ func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[ref return informers }() - res := map[reflect.Type]bool{} + // Wait for informers to sync, without polling. + cacheSyncs := make([]cache.DoneChecker, 0, len(informers)) + for _, informer := range informers { + cacheSyncs = append(cacheSyncs, informer.HasSyncedChecker()) + } + cache.WaitFor(ctx, "" /* no logging */, cacheSyncs...) + + res := cache.SyncResult{ + Synced: make(map[reflect.Type]bool, len(informers)), + } + failed := false for informType, informer := range informers { - res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) + hasSynced := informer.HasSynced() + if !hasSynced { + failed = true + } + res.Synced[informType] = hasSynced } + if failed { + // context.Cause is more informative than ctx.Err(). + // This must be non-nil, otherwise WaitFor wouldn't have stopped + // prematurely. + res.Err = context.Cause(ctx) + } + return res } @@ -183,7 +226,9 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal } informer = newFunc(f.client, resyncPeriod) - informer.SetTransform(f.transform) + if f.transform != nil { + informer.SetTransform(f.transform) + } f.informers[informerType] = informer return informer @@ -200,27 +245,46 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal // defer factory.WaitForStop() // Returns immediately if nothing was started. // genericInformer := factory.ForResource(resource) // typedInformer := factory.SomeAPIGroup().V1().SomeType() -// factory.Start(ctx.Done()) // Start processing these informers. -// synced := factory.WaitForCacheSync(ctx.Done()) -// for v, ok := range synced { -// if !ok { -// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) -// return -// } +// handle, err := typeInformer.Informer().AddEventHandler(...) +// if err != nil { +// return fmt.Errorf("register event handler: %v", err) +// } +// defer typeInformer.Informer().RemoveEventHandler(handle) // Avoids leaking goroutines. +// factory.StartWithContext(ctx) // Start processing these informers. +// synced := factory.WaitForCacheSyncWithContext(ctx) +// if err := synced.AsError(); err != nil { +// return err +// } +// for v := range synced { +// // Only if desired log some information similar to this. +// fmt.Fprintf(os.Stdout, "cache synced: %s", v) +// } +// +// // Also make sure that all of the initial cache events have been delivered. +// if !WaitFor(ctx, "event handler sync", handle.HasSyncedChecker()) { +// // Must have failed because of context. +// return fmt.Errorf("sync event handler: %w", context.Cause(ctx)) // } // // // Creating informers can also be created after Start, but then // // Start must be called again: // anotherGenericInformer := factory.ForResource(resource) -// factory.Start(ctx.Done()) +// factory.StartWithContext(ctx) type SharedInformerFactory interface { internalinterfaces.SharedInformerFactory // Start initializes all requested informers. They are handled in goroutines // which run until the stop channel gets closed. // Warning: Start does not block. When run in a go-routine, it will race with a later WaitForCacheSync. + // + // Contextual logging: StartWithContext should be used instead of Start in code which supports contextual logging. Start(stopCh <-chan struct{}) + // StartWithContext initializes all requested informers. They are handled in goroutines + // which run until the context gets canceled. + // Warning: StartWithContext does not block. When run in a go-routine, it will race with a later WaitForCacheSync. + StartWithContext(ctx context.Context) + // Shutdown marks a factory as shutting down. At that point no new // informers can be started anymore and Start will return without // doing anything. @@ -235,8 +299,14 @@ type SharedInformerFactory interface { // WaitForCacheSync blocks until all started informers' caches were synced // or the stop channel gets closed. + // + // Contextual logging: WaitForCacheSync should be used instead of WaitForCacheSync in code which supports contextual logging. It also returns a more useful result. WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + // WaitForCacheSyncWithContext blocks until all started informers' caches were synced + // or the context gets canceled. + WaitForCacheSyncWithContext(ctx context.Context) cache.SyncResult + // ForResource gives generic access to a shared informer of the matching type. ForResource(resource schema.GroupVersionResource) (GenericInformer, error) diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 9a2b7bd8..921ebaa6 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -42,6 +42,10 @@ func (f *genericInformer) Lister() cache.GenericLister { func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { // Group=core.cs.sap.com, Version=v1alpha1 + case v1alpha1.SchemeGroupVersion.WithResource("blueprints"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Core().V1alpha1().Blueprints().Informer()}, nil + case v1alpha1.SchemeGroupVersion.WithResource("blueprintversions"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Core().V1alpha1().BlueprintVersions().Informer()}, nil case v1alpha1.SchemeGroupVersion.WithResource("components"): return &genericInformer{resource: resource.GroupResource(), informer: f.Core().V1alpha1().Components().Informer()}, nil diff --git a/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go b/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go index 88e94787..0818c6c0 100644 --- a/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go +++ b/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -23,7 +23,26 @@ type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexI type SharedInformerFactory interface { Start(stopCh <-chan struct{}) InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer + InformerName() *cache.InformerName } // TweakListOptionsFunc is a function that transforms a v1.ListOptions. type TweakListOptionsFunc func(*v1.ListOptions) + +// InformerOptions holds the options for creating an informer. +type InformerOptions struct { + // ResyncPeriod is the resync period for this informer. + // If not set, defaults to 0 (no resync). + ResyncPeriod time.Duration + + // Indexers are the indexers for this informer. + Indexers cache.Indexers + + // InformerName is used to uniquely identify this informer for metrics. + // If not set, metrics will not be published for this informer. + // Use cache.NewInformerName() to create an InformerName at startup. + InformerName *cache.InformerName + + // TweakListOptions is an optional function to modify the list options. + TweakListOptions TweakListOptionsFunc +} diff --git a/pkg/client/listers/core.cs.sap.com/v1alpha1/blueprint.go b/pkg/client/listers/core.cs.sap.com/v1alpha1/blueprint.go new file mode 100644 index 00000000..9216d4fd --- /dev/null +++ b/pkg/client/listers/core.cs.sap.com/v1alpha1/blueprint.go @@ -0,0 +1,59 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + corecssapcomv1alpha1 "github.com/sap/component-operator/api/v1alpha1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// BlueprintLister helps list Blueprints. +// All objects returned here must be treated as read-only. +type BlueprintLister interface { + // List lists all Blueprints in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*corecssapcomv1alpha1.Blueprint, err error) + // Blueprints returns an object that can list and get Blueprints. + Blueprints(namespace string) BlueprintNamespaceLister + BlueprintListerExpansion +} + +// blueprintLister implements the BlueprintLister interface. +type blueprintLister struct { + listers.ResourceIndexer[*corecssapcomv1alpha1.Blueprint] +} + +// NewBlueprintLister returns a new BlueprintLister. +func NewBlueprintLister(indexer cache.Indexer) BlueprintLister { + return &blueprintLister{listers.New[*corecssapcomv1alpha1.Blueprint](indexer, corecssapcomv1alpha1.Resource("blueprint"))} +} + +// Blueprints returns an object that can list and get Blueprints. +func (s *blueprintLister) Blueprints(namespace string) BlueprintNamespaceLister { + return blueprintNamespaceLister{listers.NewNamespaced[*corecssapcomv1alpha1.Blueprint](s.ResourceIndexer, namespace)} +} + +// BlueprintNamespaceLister helps list and get Blueprints. +// All objects returned here must be treated as read-only. +type BlueprintNamespaceLister interface { + // List lists all Blueprints in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*corecssapcomv1alpha1.Blueprint, err error) + // Get retrieves the Blueprint from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*corecssapcomv1alpha1.Blueprint, error) + BlueprintNamespaceListerExpansion +} + +// blueprintNamespaceLister implements the BlueprintNamespaceLister +// interface. +type blueprintNamespaceLister struct { + listers.ResourceIndexer[*corecssapcomv1alpha1.Blueprint] +} diff --git a/pkg/client/listers/core.cs.sap.com/v1alpha1/blueprintversion.go b/pkg/client/listers/core.cs.sap.com/v1alpha1/blueprintversion.go new file mode 100644 index 00000000..7aca8c8e --- /dev/null +++ b/pkg/client/listers/core.cs.sap.com/v1alpha1/blueprintversion.go @@ -0,0 +1,59 @@ +/* +SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator contributors +SPDX-License-Identifier: Apache-2.0 +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + corecssapcomv1alpha1 "github.com/sap/component-operator/api/v1alpha1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" +) + +// BlueprintVersionLister helps list BlueprintVersions. +// All objects returned here must be treated as read-only. +type BlueprintVersionLister interface { + // List lists all BlueprintVersions in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*corecssapcomv1alpha1.BlueprintVersion, err error) + // BlueprintVersions returns an object that can list and get BlueprintVersions. + BlueprintVersions(namespace string) BlueprintVersionNamespaceLister + BlueprintVersionListerExpansion +} + +// blueprintVersionLister implements the BlueprintVersionLister interface. +type blueprintVersionLister struct { + listers.ResourceIndexer[*corecssapcomv1alpha1.BlueprintVersion] +} + +// NewBlueprintVersionLister returns a new BlueprintVersionLister. +func NewBlueprintVersionLister(indexer cache.Indexer) BlueprintVersionLister { + return &blueprintVersionLister{listers.New[*corecssapcomv1alpha1.BlueprintVersion](indexer, corecssapcomv1alpha1.Resource("blueprintversion"))} +} + +// BlueprintVersions returns an object that can list and get BlueprintVersions. +func (s *blueprintVersionLister) BlueprintVersions(namespace string) BlueprintVersionNamespaceLister { + return blueprintVersionNamespaceLister{listers.NewNamespaced[*corecssapcomv1alpha1.BlueprintVersion](s.ResourceIndexer, namespace)} +} + +// BlueprintVersionNamespaceLister helps list and get BlueprintVersions. +// All objects returned here must be treated as read-only. +type BlueprintVersionNamespaceLister interface { + // List lists all BlueprintVersions in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*corecssapcomv1alpha1.BlueprintVersion, err error) + // Get retrieves the BlueprintVersion from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*corecssapcomv1alpha1.BlueprintVersion, error) + BlueprintVersionNamespaceListerExpansion +} + +// blueprintVersionNamespaceLister implements the BlueprintVersionNamespaceLister +// interface. +type blueprintVersionNamespaceLister struct { + listers.ResourceIndexer[*corecssapcomv1alpha1.BlueprintVersion] +} diff --git a/pkg/client/listers/core.cs.sap.com/v1alpha1/expansion_generated.go b/pkg/client/listers/core.cs.sap.com/v1alpha1/expansion_generated.go index 389eb491..891923fa 100644 --- a/pkg/client/listers/core.cs.sap.com/v1alpha1/expansion_generated.go +++ b/pkg/client/listers/core.cs.sap.com/v1alpha1/expansion_generated.go @@ -7,6 +7,22 @@ SPDX-License-Identifier: Apache-2.0 package v1alpha1 +// BlueprintListerExpansion allows custom methods to be added to +// BlueprintLister. +type BlueprintListerExpansion interface{} + +// BlueprintNamespaceListerExpansion allows custom methods to be added to +// BlueprintNamespaceLister. +type BlueprintNamespaceListerExpansion interface{} + +// BlueprintVersionListerExpansion allows custom methods to be added to +// BlueprintVersionLister. +type BlueprintVersionListerExpansion interface{} + +// BlueprintVersionNamespaceListerExpansion allows custom methods to be added to +// BlueprintVersionNamespaceLister. +type BlueprintVersionNamespaceListerExpansion interface{} + // ComponentListerExpansion allows custom methods to be added to // ComponentLister. type ComponentListerExpansion interface{} diff --git a/pkg/operator/util.go b/pkg/meta/constants.go similarity index 52% rename from pkg/operator/util.go rename to pkg/meta/constants.go index 2ff0a4ad..6cd2d221 100644 --- a/pkg/operator/util.go +++ b/pkg/meta/constants.go @@ -3,10 +3,8 @@ SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-op SPDX-License-Identifier: Apache-2.0 */ -package operator +package meta -// TODO: consolidate all the util files into an internal reuse package - -func ref[T any](x T) *T { - return &x -} +const ( + Name = "component-operator.cs.sap.com" +) diff --git a/internal/sources/flux/types/source.go b/pkg/meta/types.go similarity index 88% rename from internal/sources/flux/types/source.go rename to pkg/meta/types.go index 2df2180f..dca77b67 100644 --- a/internal/sources/flux/types/source.go +++ b/pkg/meta/types.go @@ -3,7 +3,7 @@ SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-op SPDX-License-Identifier: Apache-2.0 */ -package types +package meta import ( fluxsourcev1 "github.com/fluxcd/source-controller/api/v1" @@ -11,7 +11,7 @@ import ( "github.com/sap/component-operator/internal/object" ) -type Source interface { +type FluxSource interface { object.Object fluxsourcev1.Source } diff --git a/pkg/operator/cache.go b/pkg/operator/cache.go deleted file mode 100644 index decf1465..00000000 --- a/pkg/operator/cache.go +++ /dev/null @@ -1,104 +0,0 @@ -/* -SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and component-operator contributors -SPDX-License-Identifier: Apache-2.0 -*/ - -package operator - -import ( - "context" - - "github.com/pkg/errors" - "github.com/sap/go-generics/slices" - - apitypes "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/util/workqueue" - "sigs.k8s.io/controller-runtime/pkg/builder" - "sigs.k8s.io/controller-runtime/pkg/cache" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - operatorv1alpha1 "github.com/sap/component-operator/api/v1alpha1" -) - -const ( - dependenciesIndexKey string = ".metadata.dependencies" -) - -func setupCache(mgr manager.Manager, blder *builder.Builder) error { - // TODO: should we pass a meaningful context? - if err := mgr.GetCache().IndexField(context.TODO(), &operatorv1alpha1.Component{}, dependenciesIndexKey, indexByDependencies); err != nil { - return errors.Wrapf(err, "failed setting index field %s", dependenciesIndexKey) - } - - blder. - Watches( - &operatorv1alpha1.Component{}, - newComponentHandler(mgr.GetCache(), dependenciesIndexKey)) - - return nil -} - -func indexByDependencies(object client.Object) []string { - component := object.(*operatorv1alpha1.Component) - return slices.Collect(component.Spec.Dependencies, func(dependency operatorv1alpha1.Dependency) string { - return dependency.WithDefaultNamespace(component.Namespace).String() - }) -} - -type componentHandler struct { - cache cache.Cache - indexKey string -} - -func newComponentHandler(cache cache.Cache, indexKey string) handler.TypedEventHandler[client.Object, reconcile.Request] { - return &componentHandler{ - cache: cache, - indexKey: indexKey, - } -} - -func (h *componentHandler) Create(ctx context.Context, e event.TypedCreateEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { - // new components will never be immediately ready, so nothing has to be done here -} - -func (h *componentHandler) Update(ctx context.Context, e event.TypedUpdateEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { - newComponent := e.ObjectNew.(*operatorv1alpha1.Component) - - if !newComponent.IsReady() { - return - } - - componentList := &operatorv1alpha1.ComponentList{} - if err := h.cache.List(ctx, componentList, client.MatchingFields{ - h.indexKey: client.ObjectKeyFromObject(e.ObjectNew).String(), - }); err != nil { - // TODO - // log.Error(err, "failed to list objects for component state change") - return - } - for _, c := range componentList.Items { - // TODO: be more selective - q.Add(reconcile.Request{NamespacedName: apitypes.NamespacedName{ - Namespace: c.Namespace, - Name: c.Name, - }}) - } -} - -func (h *componentHandler) Delete(ctx context.Context, e event.TypedDeleteEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { - component := e.Object.(*operatorv1alpha1.Component) - for _, dependency := range component.Spec.Dependencies { - q.Add(reconcile.Request{NamespacedName: apitypes.NamespacedName{ - Namespace: dependency.WithDefaultNamespace(component.Namespace).Namespace, - Name: dependency.Name, - }}) - } -} - -func (h *componentHandler) Generic(ctx context.Context, e event.TypedGenericEvent[client.Object], q workqueue.TypedRateLimitingInterface[reconcile.Request]) { - // generic events are not expected to arrive on the watch that uses this handler, so nothing to do here -} diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go index 16f6d161..18463356 100644 --- a/pkg/operator/operator.go +++ b/pkg/operator/operator.go @@ -15,29 +15,25 @@ import ( clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller" - fluxevents "github.com/fluxcd/pkg/runtime/events" fluxsourcev1 "github.com/fluxcd/source-controller/api/v1" fluxsourcev1beta1 "github.com/fluxcd/source-controller/api/v1beta1" fluxsourcev1beta2 "github.com/fluxcd/source-controller/api/v1beta2" - "github.com/sap/component-operator-runtime/pkg/cluster" - "github.com/sap/component-operator-runtime/pkg/component" "github.com/sap/component-operator-runtime/pkg/operator" - "github.com/sap/component-operator-runtime/pkg/reconciler" operatorv1alpha1 "github.com/sap/component-operator/api/v1alpha1" - "github.com/sap/component-operator/internal/generator" - flux "github.com/sap/component-operator/internal/sources/flux/cache" - httprepository "github.com/sap/component-operator/internal/sources/httprepository/checker" + componentcache "github.com/sap/component-operator/internal/cache/component" + blueprintcontroller "github.com/sap/component-operator/internal/controllers/blueprint" + componentcontroller "github.com/sap/component-operator/internal/controllers/component" + "github.com/sap/component-operator/internal/httprepository" + "github.com/sap/component-operator/pkg/meta" ) // TODO: write some logs (e.g. in the hooks) // TODO: use source digest instead of (resp. in parallel to) source revision const ( - Name = "component-operator.cs.sap.com" MaxConcurrentReconciles = 5 ) @@ -86,7 +82,7 @@ func New() *Operator { func NewWithOptions(options Options) *Operator { operator := &Operator{options: options} if operator.options.Name == "" { - operator.options.Name = Name + operator.options.Name = meta.Name } if operator.options.MaxConcurrentReconciles == 0 { operator.options.MaxConcurrentReconciles = MaxConcurrentReconciles @@ -117,59 +113,33 @@ func (o *Operator) ValidateFlags() error { } func (o *Operator) GetUncacheableTypes() []client.Object { - return []client.Object{&operatorv1alpha1.Component{}} + return []client.Object{&operatorv1alpha1.Component{}, &operatorv1alpha1.Blueprint{}, &operatorv1alpha1.BlueprintVersion{}} } func (o *Operator) Setup(mgr ctrl.Manager) error { - blder := ctrl.NewControllerManagedBy(mgr). - WithOptions(controller.Options{MaxConcurrentReconciles: o.options.MaxConcurrentReconciles}) - - if err := setupCache(mgr, blder); err != nil { - return errors.Wrap(err, "error registering component resource") - } - if err := flux.SetupCache(mgr, blder); err != nil { - return errors.Wrap(err, "error registering flux resources") + if err := componentcache.SetupWithManager(mgr); err != nil { + return errors.Wrap(err, "error configuring component cache") } - resourceGenerator, err := generator.NewGenerator() + componentReconciler, err := componentcontroller.SetupWithManager(mgr, componentcontroller.ReconcilerOptions{ + Name: o.options.Name, + DefaultServiceAccount: o.options.DefaultServiceAccount, + MaxConcurrentReconciles: o.options.MaxConcurrentReconciles, + EventsAddress: o.options.EventsAddress, + }) if err != nil { - return errors.Wrap(err, "error initializing resource generator") + return errors.Wrapf(err, "error registering component controller") } - newClient := func(clnt cluster.Client) (cluster.Client, error) { - if o.options.EventsAddress == "" { - return clnt, nil - } - eventRecorder, err := fluxevents.NewRecorderForScheme(clnt.Scheme(), clnt.EventRecorder(), mgr.GetLogger(), o.options.EventsAddress, o.options.Name) - if err != nil { - return nil, errors.Wrap(err, "error initializing wrapping event recorder") - } - return cluster.NewClient(clnt, clnt.DiscoveryClient(), eventRecorder, clnt.Config(), clnt.HttpClient()), nil + if err := blueprintcontroller.SetupWithManager(mgr, blueprintcontroller.ReconcilerOptions{ + Name: o.options.Name, + }); err != nil { + return errors.Wrapf(err, "error registering blueprint controller") } - reconciler := component.NewReconciler[*operatorv1alpha1.Component]( - o.options.Name, - resourceGenerator, - component.ReconcilerOptions{ - DefaultServiceAccount: &o.options.DefaultServiceAccount, - UpdatePolicy: ref(reconciler.UpdatePolicySsaOverride), - NewClient: newClient, - }, - ).WithPostReadHook( - makeFuncPostRead(), - ).WithPreReconcileHook( - makeFuncPreReconcile(mgr.GetCache()), - ).WithPostReconcileHook( - makeFuncPostReconcile(), - ).WithPreDeleteHook( - makeFuncPreDelete(mgr.GetCache()), - ) - - if err := reconciler.SetupWithManagerAndBuilder(mgr, blder); err != nil { - return errors.Wrapf(err, "unable to create controller") + if err := httprepository.SetupWithManager(mgr, componentReconciler); err != nil { + return errors.Wrapf(err, "error registering http repository checker") } - mgr.Add(httprepository.NewChecker(mgr.GetCache(), reconciler, mgr.GetLogger())) - return nil } From faf91a8cfd8c29bd5cf97b01716f1489c2f9eb63 Mon Sep 17 00:00:00 2001 From: Christoph Barbian Date: Sun, 14 Jun 2026 15:07:52 +0200 Subject: [PATCH 2/3] add docs for blueprints --- README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e7022510..741729c7 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,11 @@ The mandatory field `spec.sourceRef` defines the source of the manifests used fo Currently, the following types of sources are supported (exactly one must be present): ```yaml +# In-cluster blueprint +sourceRef: + # namespace: source-ns + name: blueprint-name + # Flux GitRepository sourceRef: fluxGitRepository: @@ -117,7 +122,26 @@ sourceRef: name: helmchart-name ``` -Cross-namespace references are allowed; if namespace is not provided, the source will be assumed to exist in the component's namespace. +Cross-namespace references are allowed; if namespace is not provided, the source will be assumed to exist in the component's namespace. Unlike the flux based source types, blueprints serve as an in-cluster representation holding the manifests of the dependent resources. A blueprint could look like this: + +```yaml +--- +apiVersion: core.cs.sap.com/v1alpha1 +kind: Blueprint +metadata: + namespace: source-ns + name: blueprint-name +spec: + files: + resources/cm.yaml: |- + --- + apiVersion: v1 + kind: ConfigMap + metadata: + name: {{ name }}-cm + data: + foo: bar +``` ### Source revision and digest From b35c5bf220e71ce959a4ba9daed55e007126a3f3 Mon Sep 17 00:00:00 2001 From: Christoph Barbian Date: Sun, 14 Jun 2026 15:08:55 +0200 Subject: [PATCH 3/3] update README --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 741729c7..3aad8518 100644 --- a/README.md +++ b/README.md @@ -94,8 +94,9 @@ Currently, the following types of sources are supported (exactly one must be pre ```yaml # In-cluster blueprint sourceRef: - # namespace: source-ns - name: blueprint-name + blueprint: + # namespace: source-ns + name: blueprint-name # Flux GitRepository sourceRef: