From 42de19ee2696056e3c87de775156843deefe9c8c Mon Sep 17 00:00:00 2001 From: Martin Dekov Date: Fri, 19 Sep 2025 15:43:51 +0300 Subject: [PATCH 01/12] Validate disk removals Rejecting disk removals when there are attached volumes in Degraded state on single node harvester set ups. We also make sure to reject removals when virtualmachineimages are in failed state as well. The logic is split in two parts: * We take the disk selectors of volumes in degraded state and compare the selectors against the disktags * We take the disk selectors of virtualmachineimages in failed state and compare against disk tags of disk In both scenarios in case there is match while disk is being disabled, we list to the user which volumes and then images needs to get fixed. Signed-off-by: Martin Dekov --- cmd/node-disk-manager-webhook/main.go | 22 +- pkg/webhook/blockdevice/validator.go | 106 ++++++++- .../controllers/longhorn.io/factory.go | 72 ++++++ .../controllers/longhorn.io/interface.go | 43 ++++ .../longhorn.io/v1beta2/backingimage.go | 208 ++++++++++++++++++ .../v1beta2/backingimagedatasource.go | 208 ++++++++++++++++++ .../controllers/longhorn.io/v1beta2/backup.go | 208 ++++++++++++++++++ .../longhorn.io/v1beta2/backupbackingimage.go | 208 ++++++++++++++++++ .../longhorn.io/v1beta2/backuptarget.go | 208 ++++++++++++++++++ .../longhorn.io/v1beta2/backupvolume.go | 208 ++++++++++++++++++ .../controllers/longhorn.io/v1beta2/engine.go | 208 ++++++++++++++++++ .../longhorn.io/v1beta2/interface.go | 104 +++++++++ .../controllers/longhorn.io/v1beta2/node.go | 208 ++++++++++++++++++ .../longhorn.io/v1beta2/replica.go | 208 ++++++++++++++++++ .../longhorn.io/v1beta2/setting.go | 208 ++++++++++++++++++ .../longhorn.io/v1beta2/snapshot.go | 208 ++++++++++++++++++ .../controllers/longhorn.io/v1beta2/volume.go | 208 ++++++++++++++++++ vendor/modules.txt | 2 + 18 files changed, 2842 insertions(+), 3 deletions(-) create mode 100644 vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/factory.go create mode 100644 vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/interface.go create mode 100644 vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backingimage.go create mode 100644 vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backingimagedatasource.go create mode 100644 vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backup.go create mode 100644 vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backupbackingimage.go create mode 100644 vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backuptarget.go create mode 100644 vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backupvolume.go create mode 100644 vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/engine.go create mode 100644 vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/interface.go create mode 100644 vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/node.go create mode 100644 vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/replica.go create mode 100644 vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/setting.go create mode 100644 vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/snapshot.go create mode 100644 vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/volume.go diff --git a/cmd/node-disk-manager-webhook/main.go b/cmd/node-disk-manager-webhook/main.go index 30e764701..ea11dec34 100644 --- a/cmd/node-disk-manager-webhook/main.go +++ b/cmd/node-disk-manager-webhook/main.go @@ -5,6 +5,10 @@ import ( "fmt" "os" + ctlharv "github.com/harvester/harvester/pkg/generated/controllers/harvesterhci.io" + ctlharvv1beta1 "github.com/harvester/harvester/pkg/generated/controllers/harvesterhci.io/v1beta1" + ctrllh "github.com/harvester/harvester/pkg/generated/controllers/longhorn.io" + lhv1beta2 "github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2" "github.com/harvester/webhook/pkg/config" "github.com/harvester/webhook/pkg/server" "github.com/harvester/webhook/pkg/server/admission" @@ -32,6 +36,9 @@ type resourceCaches struct { lvmVGCache ctldiskv1.LVMVolumeGroupCache storageClassCache ctlstoragev1.StorageClassCache pvCache ctlcorev1.PersistentVolumeCache + volumeCache lhv1beta2.VolumeCache + nodeCache ctlcorev1.NodeCache + vmImageCache ctlharvv1beta1.VirtualMachineImageCache } func main() { @@ -117,7 +124,8 @@ func runWebhookServer(ctx context.Context, cfg *rest.Config, options *config.Opt bdMutator, } - bdValidator := blockdevice.NewBlockdeviceValidator(resourceCaches.bdCache, resourceCaches.storageClassCache, resourceCaches.pvCache) + bdValidator := blockdevice.NewBlockdeviceValidator(resourceCaches.bdCache, resourceCaches.storageClassCache, resourceCaches.pvCache, + resourceCaches.volumeCache, resourceCaches.nodeCache, resourceCaches.vmImageCache) scValidator := storageclass.NewStorageClassValidator(resourceCaches.lvmVGCache) var validators = []admission.Validator{ bdValidator, @@ -156,12 +164,24 @@ func newCaches(ctx context.Context, cfg *rest.Config, threadiness int) (*resourc if err != nil { return nil, err } + lhFactory, err := ctrllh.NewFactoryFromConfig(cfg) + if err != nil { + return nil, err + } + ctrlFactory, err := ctlharv.NewFactoryFromConfig(cfg) + if err != nil { + return nil, err + } + print(ctrlFactory) starters = append(starters, disks, storageFactory, coreFactory) resourceCaches := &resourceCaches{ bdCache: disks.Harvesterhci().V1beta1().BlockDevice().Cache(), lvmVGCache: disks.Harvesterhci().V1beta1().LVMVolumeGroup().Cache(), storageClassCache: storageFactory.Storage().V1().StorageClass().Cache(), pvCache: coreFactory.Core().V1().PersistentVolume().Cache(), + volumeCache: lhFactory.Longhorn().V1beta2().Volume().Cache(), + nodeCache: coreFactory.Core().V1().Node().Cache(), + vmImageCache: ctrlFactory.Harvesterhci().V1beta1().VirtualMachineImage().Cache(), } if err := start.All(ctx, threadiness, starters...); err != nil { diff --git a/pkg/webhook/blockdevice/validator.go b/pkg/webhook/blockdevice/validator.go index c7e2afccf..aef7a1953 100644 --- a/pkg/webhook/blockdevice/validator.go +++ b/pkg/webhook/blockdevice/validator.go @@ -3,8 +3,10 @@ package blockdevice import ( "fmt" + lhv1beta2 "github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2" werror "github.com/harvester/webhook/pkg/error" "github.com/harvester/webhook/pkg/server/admission" + lhv1 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" ctlcorev1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/core/v1" ctlstoragev1 "github.com/rancher/wrangler/v3/pkg/generated/controllers/storage/v1" "github.com/sirupsen/logrus" @@ -13,24 +15,36 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + ctlharvv1beta1 "github.com/harvester/harvester/pkg/generated/controllers/harvesterhci.io/v1beta1" diskv1 "github.com/harvester/node-disk-manager/pkg/apis/harvesterhci.io/v1beta1" ctldiskv1 "github.com/harvester/node-disk-manager/pkg/generated/controllers/harvesterhci.io/v1beta1" "github.com/harvester/node-disk-manager/pkg/utils" ) +const ( + DiskSelectorKey = "diskSelector" +) + type Validator struct { admission.DefaultValidator BlockdeviceCache ctldiskv1.BlockDeviceCache storageClassCache ctlstoragev1.StorageClassCache pvCache ctlcorev1.PersistentVolumeCache + volumeCache lhv1beta2.VolumeCache + nodeCache ctlcorev1.NodeCache + vmImageCache ctlharvv1beta1.VirtualMachineImageCache } -func NewBlockdeviceValidator(blockdeviceCache ctldiskv1.BlockDeviceCache, storageClassCache ctlstoragev1.StorageClassCache, pvCache ctlcorev1.PersistentVolumeCache) *Validator { +func NewBlockdeviceValidator(blockdeviceCache ctldiskv1.BlockDeviceCache, storageClassCache ctlstoragev1.StorageClassCache, + pvCache ctlcorev1.PersistentVolumeCache, volumeCache lhv1beta2.VolumeCache, nodeCache ctlcorev1.NodeCache, vmImageCache ctlharvv1beta1.VirtualMachineImageCache) *Validator { return &Validator{ BlockdeviceCache: blockdeviceCache, storageClassCache: storageClassCache, pvCache: pvCache, + volumeCache: volumeCache, + nodeCache: nodeCache, + vmImageCache: vmImageCache, } } @@ -45,10 +59,14 @@ func (v *Validator) Create(_ *admission.Request, newObj runtime.Object) error { func (v *Validator) Update(_ *admission.Request, oldObj, newObj runtime.Object) error { newBd := newObj.(*diskv1.BlockDevice) oldBd := oldObj.(*diskv1.BlockDevice) + if err := v.validateProvisioner(newBd); err != nil { return err } - return v.validateLVMProvisioner(oldBd, newBd) + if err := v.validateLVMProvisioner(oldBd, newBd); err != nil { + return err + } + return v.validateLHDisk(oldBd, newBd) } func (v *Validator) validateProvisioner(bd *diskv1.BlockDevice) error { @@ -62,6 +80,26 @@ func (v *Validator) validateProvisioner(bd *diskv1.BlockDevice) error { return nil } +func (v *Validator) validateLHDisk(oldBd, newBd *diskv1.BlockDevice) error { + if oldBd.Spec.Provisioner.Longhorn != nil && newBd.Spec.Provisioner.Longhorn != nil && oldBd.Spec.Provision && !newBd.Spec.Provision { + nodeList, err := v.nodeCache.List(labels.Everything()) + if err != nil { + return err + } + if len(nodeList) == 1 { + err := v.validateDegradedVolumes(oldBd) + if err != nil { + return err + } + err = v.validateFailedVMImages(oldBd) + if err != nil { + return err + } + } + } + return nil +} + // validateLVMProvisioner will check the block device with LVM provisioner and block // if there is already have any pvc created with in the target volume group func (v *Validator) validateLVMProvisioner(oldbd, newbd *diskv1.BlockDevice) error { @@ -127,6 +165,70 @@ func (v *Validator) validateVGIsAlreadyUsed(bd *diskv1.BlockDevice) error { return nil } +func (v *Validator) validateDegradedVolumes(old *diskv1.BlockDevice) error { + volumeList, err := v.volumeCache.List("longhorn-system", labels.Everything()) + if err != nil { + return err + } + degradedVolumes := make(map[string]bool) + for _, vol := range volumeList { + if vol.Status.Robustness == lhv1.VolumeRobustnessDegraded { + degradedVolumes[vol.Name] = true + } + } + if len(degradedVolumes) == 0 { + return nil + } + pvList, err := v.pvCache.List(labels.Everything()) + if err != nil { + return err + } + selectorDegradedVol := make(map[string]string) + for _, pv := range pvList { + if degradedVolumes[pv.Name] { + diskSelector := pv.Spec.CSI.VolumeAttributes[DiskSelectorKey] + if len(diskSelector) != 0 { + selectorDegradedVol[pv.Spec.CSI.VolumeAttributes[DiskSelectorKey]] = pv.Name + } + } + } + degradedVolString := "" + for _, diskTag := range old.Spec.Tags { + if val, ok := selectorDegradedVol[diskTag]; ok { + degradedVolString = fmt.Sprintf("%s, %s", degradedVolString, val) + } + } + if len(degradedVolString) > 0 { + return fmt.Errorf("the following volumes: %s attached to disk: %s are in degraded state; evict disk before proceeding", + degradedVolString, old.Spec.DevPath) + } + return nil +} + +func (v *Validator) validateFailedVMImages(old *diskv1.BlockDevice) error { + vmImageList, err := v.vmImageCache.List("", labels.Everything()) + if err != nil { + return err + } + selectorFailedVMIMage := make(map[string]string) + for _, vmImage := range vmImageList { + if vmImage.Status.Failed != 0 && len(vmImage.Spec.StorageClassParameters[DiskSelectorKey]) > 0 { + selectorFailedVMIMage[vmImage.Spec.StorageClassParameters[DiskSelectorKey]] = vmImage.Spec.DisplayName + } + } + failedVMImages := "" + for _, diskTag := range old.Spec.Tags { + if val, ok := selectorFailedVMIMage[diskTag]; ok { + failedVMImages = fmt.Sprintf("%s, %s", failedVMImages, val) + } + } + if len(failedVMImages) > 0 { + return fmt.Errorf("the following virtualmachineimages: %s attached to disk: %s are in failed state; evict disk before proceeding", + failedVMImages, old.Spec.DevPath) + } + return nil +} + func (v *Validator) Resource() admission.Resource { return admission.Resource{ Names: []string{"blockdevices"}, diff --git a/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/factory.go b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/factory.go new file mode 100644 index 000000000..8304f4252 --- /dev/null +++ b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/factory.go @@ -0,0 +1,72 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package longhorn + +import ( + "github.com/rancher/lasso/pkg/controller" + "github.com/rancher/wrangler/v3/pkg/generic" + "k8s.io/client-go/rest" +) + +type Factory struct { + *generic.Factory +} + +func NewFactoryFromConfigOrDie(config *rest.Config) *Factory { + f, err := NewFactoryFromConfig(config) + if err != nil { + panic(err) + } + return f +} + +func NewFactoryFromConfig(config *rest.Config) (*Factory, error) { + return NewFactoryFromConfigWithOptions(config, nil) +} + +func NewFactoryFromConfigWithNamespace(config *rest.Config, namespace string) (*Factory, error) { + return NewFactoryFromConfigWithOptions(config, &FactoryOptions{ + Namespace: namespace, + }) +} + +type FactoryOptions = generic.FactoryOptions + +func NewFactoryFromConfigWithOptions(config *rest.Config, opts *FactoryOptions) (*Factory, error) { + f, err := generic.NewFactoryFromConfigWithOptions(config, opts) + return &Factory{ + Factory: f, + }, err +} + +func NewFactoryFromConfigWithOptionsOrDie(config *rest.Config, opts *FactoryOptions) *Factory { + f, err := NewFactoryFromConfigWithOptions(config, opts) + if err != nil { + panic(err) + } + return f +} + +func (c *Factory) Longhorn() Interface { + return New(c.ControllerFactory()) +} + +func (c *Factory) WithAgent(userAgent string) Interface { + return New(controller.NewSharedControllerFactoryWithAgent(userAgent, c.ControllerFactory())) +} diff --git a/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/interface.go b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/interface.go new file mode 100644 index 000000000..fac85872d --- /dev/null +++ b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/interface.go @@ -0,0 +1,43 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package longhorn + +import ( + v1beta2 "github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2" + "github.com/rancher/lasso/pkg/controller" +) + +type Interface interface { + V1beta2() v1beta2.Interface +} + +type group struct { + controllerFactory controller.SharedControllerFactory +} + +// New returns a new Interface. +func New(controllerFactory controller.SharedControllerFactory) Interface { + return &group{ + controllerFactory: controllerFactory, + } +} + +func (g *group) V1beta2() v1beta2.Interface { + return v1beta2.New(g.controllerFactory) +} diff --git a/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backingimage.go b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backingimage.go new file mode 100644 index 000000000..3be057e2e --- /dev/null +++ b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backingimage.go @@ -0,0 +1,208 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1beta2 + +import ( + "context" + "sync" + "time" + + v1beta2 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" + "github.com/rancher/wrangler/v3/pkg/apply" + "github.com/rancher/wrangler/v3/pkg/condition" + "github.com/rancher/wrangler/v3/pkg/generic" + "github.com/rancher/wrangler/v3/pkg/kv" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// BackingImageController interface for managing BackingImage resources. +type BackingImageController interface { + generic.ControllerInterface[*v1beta2.BackingImage, *v1beta2.BackingImageList] +} + +// BackingImageClient interface for managing BackingImage resources in Kubernetes. +type BackingImageClient interface { + generic.ClientInterface[*v1beta2.BackingImage, *v1beta2.BackingImageList] +} + +// BackingImageCache interface for retrieving BackingImage resources in memory. +type BackingImageCache interface { + generic.CacheInterface[*v1beta2.BackingImage] +} + +// BackingImageStatusHandler is executed for every added or modified BackingImage. Should return the new status to be updated +type BackingImageStatusHandler func(obj *v1beta2.BackingImage, status v1beta2.BackingImageStatus) (v1beta2.BackingImageStatus, error) + +// BackingImageGeneratingHandler is the top-level handler that is executed for every BackingImage event. It extends BackingImageStatusHandler by a returning a slice of child objects to be passed to apply.Apply +type BackingImageGeneratingHandler func(obj *v1beta2.BackingImage, status v1beta2.BackingImageStatus) ([]runtime.Object, v1beta2.BackingImageStatus, error) + +// RegisterBackingImageStatusHandler configures a BackingImageController to execute a BackingImageStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterBackingImageStatusHandler(ctx context.Context, controller BackingImageController, condition condition.Cond, name string, handler BackingImageStatusHandler) { + statusHandler := &backingImageStatusHandler{ + client: controller, + condition: condition, + handler: handler, + } + controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) +} + +// RegisterBackingImageGeneratingHandler configures a BackingImageController to execute a BackingImageGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterBackingImageGeneratingHandler(ctx context.Context, controller BackingImageController, apply apply.Apply, + condition condition.Cond, name string, handler BackingImageGeneratingHandler, opts *generic.GeneratingHandlerOptions) { + statusHandler := &backingImageGeneratingHandler{ + BackingImageGeneratingHandler: handler, + apply: apply, + name: name, + gvk: controller.GroupVersionKind(), + } + if opts != nil { + statusHandler.opts = *opts + } + controller.OnChange(ctx, name, statusHandler.Remove) + RegisterBackingImageStatusHandler(ctx, controller, condition, name, statusHandler.Handle) +} + +type backingImageStatusHandler struct { + client BackingImageClient + condition condition.Cond + handler BackingImageStatusHandler +} + +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API +func (a *backingImageStatusHandler) sync(key string, obj *v1beta2.BackingImage) (*v1beta2.BackingImage, error) { + if obj == nil { + return obj, nil + } + + origStatus := obj.Status.DeepCopy() + obj = obj.DeepCopy() + newStatus, err := a.handler(obj, obj.Status) + if err != nil { + // Revert to old status on error + newStatus = *origStatus.DeepCopy() + } + + if a.condition != "" { + if errors.IsConflict(err) { + a.condition.SetError(&newStatus, "", nil) + } else { + a.condition.SetError(&newStatus, "", err) + } + } + if !equality.Semantic.DeepEqual(origStatus, &newStatus) { + if a.condition != "" { + // Since status has changed, update the lastUpdatedTime + a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339)) + } + + var newErr error + obj.Status = newStatus + newObj, newErr := a.client.UpdateStatus(obj) + if err == nil { + err = newErr + } + if newErr == nil { + obj = newObj + } + } + return obj, err +} + +type backingImageGeneratingHandler struct { + BackingImageGeneratingHandler + apply apply.Apply + opts generic.GeneratingHandlerOptions + gvk schema.GroupVersionKind + name string + seen sync.Map +} + +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied +func (a *backingImageGeneratingHandler) Remove(key string, obj *v1beta2.BackingImage) (*v1beta2.BackingImage, error) { + if obj != nil { + return obj, nil + } + + obj = &v1beta2.BackingImage{} + obj.Namespace, obj.Name = kv.RSplit(key, "/") + obj.SetGroupVersionKind(a.gvk) + + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects() +} + +// Handle executes the configured BackingImageGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource +func (a *backingImageGeneratingHandler) Handle(obj *v1beta2.BackingImage, status v1beta2.BackingImageStatus) (v1beta2.BackingImageStatus, error) { + if !obj.DeletionTimestamp.IsZero() { + return status, nil + } + + objs, newStatus, err := a.BackingImageGeneratingHandler(obj, status) + if err != nil { + return newStatus, err + } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } + + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *backingImageGeneratingHandler) isNewResourceVersion(obj *v1beta2.BackingImage) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *backingImageGeneratingHandler) storeResourceVersion(obj *v1beta2.BackingImage) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) +} diff --git a/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backingimagedatasource.go b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backingimagedatasource.go new file mode 100644 index 000000000..cc192114d --- /dev/null +++ b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backingimagedatasource.go @@ -0,0 +1,208 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1beta2 + +import ( + "context" + "sync" + "time" + + v1beta2 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" + "github.com/rancher/wrangler/v3/pkg/apply" + "github.com/rancher/wrangler/v3/pkg/condition" + "github.com/rancher/wrangler/v3/pkg/generic" + "github.com/rancher/wrangler/v3/pkg/kv" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// BackingImageDataSourceController interface for managing BackingImageDataSource resources. +type BackingImageDataSourceController interface { + generic.ControllerInterface[*v1beta2.BackingImageDataSource, *v1beta2.BackingImageDataSourceList] +} + +// BackingImageDataSourceClient interface for managing BackingImageDataSource resources in Kubernetes. +type BackingImageDataSourceClient interface { + generic.ClientInterface[*v1beta2.BackingImageDataSource, *v1beta2.BackingImageDataSourceList] +} + +// BackingImageDataSourceCache interface for retrieving BackingImageDataSource resources in memory. +type BackingImageDataSourceCache interface { + generic.CacheInterface[*v1beta2.BackingImageDataSource] +} + +// BackingImageDataSourceStatusHandler is executed for every added or modified BackingImageDataSource. Should return the new status to be updated +type BackingImageDataSourceStatusHandler func(obj *v1beta2.BackingImageDataSource, status v1beta2.BackingImageDataSourceStatus) (v1beta2.BackingImageDataSourceStatus, error) + +// BackingImageDataSourceGeneratingHandler is the top-level handler that is executed for every BackingImageDataSource event. It extends BackingImageDataSourceStatusHandler by a returning a slice of child objects to be passed to apply.Apply +type BackingImageDataSourceGeneratingHandler func(obj *v1beta2.BackingImageDataSource, status v1beta2.BackingImageDataSourceStatus) ([]runtime.Object, v1beta2.BackingImageDataSourceStatus, error) + +// RegisterBackingImageDataSourceStatusHandler configures a BackingImageDataSourceController to execute a BackingImageDataSourceStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterBackingImageDataSourceStatusHandler(ctx context.Context, controller BackingImageDataSourceController, condition condition.Cond, name string, handler BackingImageDataSourceStatusHandler) { + statusHandler := &backingImageDataSourceStatusHandler{ + client: controller, + condition: condition, + handler: handler, + } + controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) +} + +// RegisterBackingImageDataSourceGeneratingHandler configures a BackingImageDataSourceController to execute a BackingImageDataSourceGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterBackingImageDataSourceGeneratingHandler(ctx context.Context, controller BackingImageDataSourceController, apply apply.Apply, + condition condition.Cond, name string, handler BackingImageDataSourceGeneratingHandler, opts *generic.GeneratingHandlerOptions) { + statusHandler := &backingImageDataSourceGeneratingHandler{ + BackingImageDataSourceGeneratingHandler: handler, + apply: apply, + name: name, + gvk: controller.GroupVersionKind(), + } + if opts != nil { + statusHandler.opts = *opts + } + controller.OnChange(ctx, name, statusHandler.Remove) + RegisterBackingImageDataSourceStatusHandler(ctx, controller, condition, name, statusHandler.Handle) +} + +type backingImageDataSourceStatusHandler struct { + client BackingImageDataSourceClient + condition condition.Cond + handler BackingImageDataSourceStatusHandler +} + +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API +func (a *backingImageDataSourceStatusHandler) sync(key string, obj *v1beta2.BackingImageDataSource) (*v1beta2.BackingImageDataSource, error) { + if obj == nil { + return obj, nil + } + + origStatus := obj.Status.DeepCopy() + obj = obj.DeepCopy() + newStatus, err := a.handler(obj, obj.Status) + if err != nil { + // Revert to old status on error + newStatus = *origStatus.DeepCopy() + } + + if a.condition != "" { + if errors.IsConflict(err) { + a.condition.SetError(&newStatus, "", nil) + } else { + a.condition.SetError(&newStatus, "", err) + } + } + if !equality.Semantic.DeepEqual(origStatus, &newStatus) { + if a.condition != "" { + // Since status has changed, update the lastUpdatedTime + a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339)) + } + + var newErr error + obj.Status = newStatus + newObj, newErr := a.client.UpdateStatus(obj) + if err == nil { + err = newErr + } + if newErr == nil { + obj = newObj + } + } + return obj, err +} + +type backingImageDataSourceGeneratingHandler struct { + BackingImageDataSourceGeneratingHandler + apply apply.Apply + opts generic.GeneratingHandlerOptions + gvk schema.GroupVersionKind + name string + seen sync.Map +} + +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied +func (a *backingImageDataSourceGeneratingHandler) Remove(key string, obj *v1beta2.BackingImageDataSource) (*v1beta2.BackingImageDataSource, error) { + if obj != nil { + return obj, nil + } + + obj = &v1beta2.BackingImageDataSource{} + obj.Namespace, obj.Name = kv.RSplit(key, "/") + obj.SetGroupVersionKind(a.gvk) + + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects() +} + +// Handle executes the configured BackingImageDataSourceGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource +func (a *backingImageDataSourceGeneratingHandler) Handle(obj *v1beta2.BackingImageDataSource, status v1beta2.BackingImageDataSourceStatus) (v1beta2.BackingImageDataSourceStatus, error) { + if !obj.DeletionTimestamp.IsZero() { + return status, nil + } + + objs, newStatus, err := a.BackingImageDataSourceGeneratingHandler(obj, status) + if err != nil { + return newStatus, err + } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } + + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *backingImageDataSourceGeneratingHandler) isNewResourceVersion(obj *v1beta2.BackingImageDataSource) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *backingImageDataSourceGeneratingHandler) storeResourceVersion(obj *v1beta2.BackingImageDataSource) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) +} diff --git a/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backup.go b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backup.go new file mode 100644 index 000000000..7e20ee82d --- /dev/null +++ b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backup.go @@ -0,0 +1,208 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1beta2 + +import ( + "context" + "sync" + "time" + + v1beta2 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" + "github.com/rancher/wrangler/v3/pkg/apply" + "github.com/rancher/wrangler/v3/pkg/condition" + "github.com/rancher/wrangler/v3/pkg/generic" + "github.com/rancher/wrangler/v3/pkg/kv" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// BackupController interface for managing Backup resources. +type BackupController interface { + generic.ControllerInterface[*v1beta2.Backup, *v1beta2.BackupList] +} + +// BackupClient interface for managing Backup resources in Kubernetes. +type BackupClient interface { + generic.ClientInterface[*v1beta2.Backup, *v1beta2.BackupList] +} + +// BackupCache interface for retrieving Backup resources in memory. +type BackupCache interface { + generic.CacheInterface[*v1beta2.Backup] +} + +// BackupStatusHandler is executed for every added or modified Backup. Should return the new status to be updated +type BackupStatusHandler func(obj *v1beta2.Backup, status v1beta2.BackupStatus) (v1beta2.BackupStatus, error) + +// BackupGeneratingHandler is the top-level handler that is executed for every Backup event. It extends BackupStatusHandler by a returning a slice of child objects to be passed to apply.Apply +type BackupGeneratingHandler func(obj *v1beta2.Backup, status v1beta2.BackupStatus) ([]runtime.Object, v1beta2.BackupStatus, error) + +// RegisterBackupStatusHandler configures a BackupController to execute a BackupStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterBackupStatusHandler(ctx context.Context, controller BackupController, condition condition.Cond, name string, handler BackupStatusHandler) { + statusHandler := &backupStatusHandler{ + client: controller, + condition: condition, + handler: handler, + } + controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) +} + +// RegisterBackupGeneratingHandler configures a BackupController to execute a BackupGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterBackupGeneratingHandler(ctx context.Context, controller BackupController, apply apply.Apply, + condition condition.Cond, name string, handler BackupGeneratingHandler, opts *generic.GeneratingHandlerOptions) { + statusHandler := &backupGeneratingHandler{ + BackupGeneratingHandler: handler, + apply: apply, + name: name, + gvk: controller.GroupVersionKind(), + } + if opts != nil { + statusHandler.opts = *opts + } + controller.OnChange(ctx, name, statusHandler.Remove) + RegisterBackupStatusHandler(ctx, controller, condition, name, statusHandler.Handle) +} + +type backupStatusHandler struct { + client BackupClient + condition condition.Cond + handler BackupStatusHandler +} + +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API +func (a *backupStatusHandler) sync(key string, obj *v1beta2.Backup) (*v1beta2.Backup, error) { + if obj == nil { + return obj, nil + } + + origStatus := obj.Status.DeepCopy() + obj = obj.DeepCopy() + newStatus, err := a.handler(obj, obj.Status) + if err != nil { + // Revert to old status on error + newStatus = *origStatus.DeepCopy() + } + + if a.condition != "" { + if errors.IsConflict(err) { + a.condition.SetError(&newStatus, "", nil) + } else { + a.condition.SetError(&newStatus, "", err) + } + } + if !equality.Semantic.DeepEqual(origStatus, &newStatus) { + if a.condition != "" { + // Since status has changed, update the lastUpdatedTime + a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339)) + } + + var newErr error + obj.Status = newStatus + newObj, newErr := a.client.UpdateStatus(obj) + if err == nil { + err = newErr + } + if newErr == nil { + obj = newObj + } + } + return obj, err +} + +type backupGeneratingHandler struct { + BackupGeneratingHandler + apply apply.Apply + opts generic.GeneratingHandlerOptions + gvk schema.GroupVersionKind + name string + seen sync.Map +} + +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied +func (a *backupGeneratingHandler) Remove(key string, obj *v1beta2.Backup) (*v1beta2.Backup, error) { + if obj != nil { + return obj, nil + } + + obj = &v1beta2.Backup{} + obj.Namespace, obj.Name = kv.RSplit(key, "/") + obj.SetGroupVersionKind(a.gvk) + + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects() +} + +// Handle executes the configured BackupGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource +func (a *backupGeneratingHandler) Handle(obj *v1beta2.Backup, status v1beta2.BackupStatus) (v1beta2.BackupStatus, error) { + if !obj.DeletionTimestamp.IsZero() { + return status, nil + } + + objs, newStatus, err := a.BackupGeneratingHandler(obj, status) + if err != nil { + return newStatus, err + } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } + + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *backupGeneratingHandler) isNewResourceVersion(obj *v1beta2.Backup) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *backupGeneratingHandler) storeResourceVersion(obj *v1beta2.Backup) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) +} diff --git a/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backupbackingimage.go b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backupbackingimage.go new file mode 100644 index 000000000..7b8d1f84b --- /dev/null +++ b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backupbackingimage.go @@ -0,0 +1,208 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1beta2 + +import ( + "context" + "sync" + "time" + + v1beta2 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" + "github.com/rancher/wrangler/v3/pkg/apply" + "github.com/rancher/wrangler/v3/pkg/condition" + "github.com/rancher/wrangler/v3/pkg/generic" + "github.com/rancher/wrangler/v3/pkg/kv" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// BackupBackingImageController interface for managing BackupBackingImage resources. +type BackupBackingImageController interface { + generic.ControllerInterface[*v1beta2.BackupBackingImage, *v1beta2.BackupBackingImageList] +} + +// BackupBackingImageClient interface for managing BackupBackingImage resources in Kubernetes. +type BackupBackingImageClient interface { + generic.ClientInterface[*v1beta2.BackupBackingImage, *v1beta2.BackupBackingImageList] +} + +// BackupBackingImageCache interface for retrieving BackupBackingImage resources in memory. +type BackupBackingImageCache interface { + generic.CacheInterface[*v1beta2.BackupBackingImage] +} + +// BackupBackingImageStatusHandler is executed for every added or modified BackupBackingImage. Should return the new status to be updated +type BackupBackingImageStatusHandler func(obj *v1beta2.BackupBackingImage, status v1beta2.BackupBackingImageStatus) (v1beta2.BackupBackingImageStatus, error) + +// BackupBackingImageGeneratingHandler is the top-level handler that is executed for every BackupBackingImage event. It extends BackupBackingImageStatusHandler by a returning a slice of child objects to be passed to apply.Apply +type BackupBackingImageGeneratingHandler func(obj *v1beta2.BackupBackingImage, status v1beta2.BackupBackingImageStatus) ([]runtime.Object, v1beta2.BackupBackingImageStatus, error) + +// RegisterBackupBackingImageStatusHandler configures a BackupBackingImageController to execute a BackupBackingImageStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterBackupBackingImageStatusHandler(ctx context.Context, controller BackupBackingImageController, condition condition.Cond, name string, handler BackupBackingImageStatusHandler) { + statusHandler := &backupBackingImageStatusHandler{ + client: controller, + condition: condition, + handler: handler, + } + controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) +} + +// RegisterBackupBackingImageGeneratingHandler configures a BackupBackingImageController to execute a BackupBackingImageGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterBackupBackingImageGeneratingHandler(ctx context.Context, controller BackupBackingImageController, apply apply.Apply, + condition condition.Cond, name string, handler BackupBackingImageGeneratingHandler, opts *generic.GeneratingHandlerOptions) { + statusHandler := &backupBackingImageGeneratingHandler{ + BackupBackingImageGeneratingHandler: handler, + apply: apply, + name: name, + gvk: controller.GroupVersionKind(), + } + if opts != nil { + statusHandler.opts = *opts + } + controller.OnChange(ctx, name, statusHandler.Remove) + RegisterBackupBackingImageStatusHandler(ctx, controller, condition, name, statusHandler.Handle) +} + +type backupBackingImageStatusHandler struct { + client BackupBackingImageClient + condition condition.Cond + handler BackupBackingImageStatusHandler +} + +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API +func (a *backupBackingImageStatusHandler) sync(key string, obj *v1beta2.BackupBackingImage) (*v1beta2.BackupBackingImage, error) { + if obj == nil { + return obj, nil + } + + origStatus := obj.Status.DeepCopy() + obj = obj.DeepCopy() + newStatus, err := a.handler(obj, obj.Status) + if err != nil { + // Revert to old status on error + newStatus = *origStatus.DeepCopy() + } + + if a.condition != "" { + if errors.IsConflict(err) { + a.condition.SetError(&newStatus, "", nil) + } else { + a.condition.SetError(&newStatus, "", err) + } + } + if !equality.Semantic.DeepEqual(origStatus, &newStatus) { + if a.condition != "" { + // Since status has changed, update the lastUpdatedTime + a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339)) + } + + var newErr error + obj.Status = newStatus + newObj, newErr := a.client.UpdateStatus(obj) + if err == nil { + err = newErr + } + if newErr == nil { + obj = newObj + } + } + return obj, err +} + +type backupBackingImageGeneratingHandler struct { + BackupBackingImageGeneratingHandler + apply apply.Apply + opts generic.GeneratingHandlerOptions + gvk schema.GroupVersionKind + name string + seen sync.Map +} + +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied +func (a *backupBackingImageGeneratingHandler) Remove(key string, obj *v1beta2.BackupBackingImage) (*v1beta2.BackupBackingImage, error) { + if obj != nil { + return obj, nil + } + + obj = &v1beta2.BackupBackingImage{} + obj.Namespace, obj.Name = kv.RSplit(key, "/") + obj.SetGroupVersionKind(a.gvk) + + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects() +} + +// Handle executes the configured BackupBackingImageGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource +func (a *backupBackingImageGeneratingHandler) Handle(obj *v1beta2.BackupBackingImage, status v1beta2.BackupBackingImageStatus) (v1beta2.BackupBackingImageStatus, error) { + if !obj.DeletionTimestamp.IsZero() { + return status, nil + } + + objs, newStatus, err := a.BackupBackingImageGeneratingHandler(obj, status) + if err != nil { + return newStatus, err + } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } + + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *backupBackingImageGeneratingHandler) isNewResourceVersion(obj *v1beta2.BackupBackingImage) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *backupBackingImageGeneratingHandler) storeResourceVersion(obj *v1beta2.BackupBackingImage) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) +} diff --git a/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backuptarget.go b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backuptarget.go new file mode 100644 index 000000000..7837c390e --- /dev/null +++ b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backuptarget.go @@ -0,0 +1,208 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1beta2 + +import ( + "context" + "sync" + "time" + + v1beta2 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" + "github.com/rancher/wrangler/v3/pkg/apply" + "github.com/rancher/wrangler/v3/pkg/condition" + "github.com/rancher/wrangler/v3/pkg/generic" + "github.com/rancher/wrangler/v3/pkg/kv" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// BackupTargetController interface for managing BackupTarget resources. +type BackupTargetController interface { + generic.ControllerInterface[*v1beta2.BackupTarget, *v1beta2.BackupTargetList] +} + +// BackupTargetClient interface for managing BackupTarget resources in Kubernetes. +type BackupTargetClient interface { + generic.ClientInterface[*v1beta2.BackupTarget, *v1beta2.BackupTargetList] +} + +// BackupTargetCache interface for retrieving BackupTarget resources in memory. +type BackupTargetCache interface { + generic.CacheInterface[*v1beta2.BackupTarget] +} + +// BackupTargetStatusHandler is executed for every added or modified BackupTarget. Should return the new status to be updated +type BackupTargetStatusHandler func(obj *v1beta2.BackupTarget, status v1beta2.BackupTargetStatus) (v1beta2.BackupTargetStatus, error) + +// BackupTargetGeneratingHandler is the top-level handler that is executed for every BackupTarget event. It extends BackupTargetStatusHandler by a returning a slice of child objects to be passed to apply.Apply +type BackupTargetGeneratingHandler func(obj *v1beta2.BackupTarget, status v1beta2.BackupTargetStatus) ([]runtime.Object, v1beta2.BackupTargetStatus, error) + +// RegisterBackupTargetStatusHandler configures a BackupTargetController to execute a BackupTargetStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterBackupTargetStatusHandler(ctx context.Context, controller BackupTargetController, condition condition.Cond, name string, handler BackupTargetStatusHandler) { + statusHandler := &backupTargetStatusHandler{ + client: controller, + condition: condition, + handler: handler, + } + controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) +} + +// RegisterBackupTargetGeneratingHandler configures a BackupTargetController to execute a BackupTargetGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterBackupTargetGeneratingHandler(ctx context.Context, controller BackupTargetController, apply apply.Apply, + condition condition.Cond, name string, handler BackupTargetGeneratingHandler, opts *generic.GeneratingHandlerOptions) { + statusHandler := &backupTargetGeneratingHandler{ + BackupTargetGeneratingHandler: handler, + apply: apply, + name: name, + gvk: controller.GroupVersionKind(), + } + if opts != nil { + statusHandler.opts = *opts + } + controller.OnChange(ctx, name, statusHandler.Remove) + RegisterBackupTargetStatusHandler(ctx, controller, condition, name, statusHandler.Handle) +} + +type backupTargetStatusHandler struct { + client BackupTargetClient + condition condition.Cond + handler BackupTargetStatusHandler +} + +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API +func (a *backupTargetStatusHandler) sync(key string, obj *v1beta2.BackupTarget) (*v1beta2.BackupTarget, error) { + if obj == nil { + return obj, nil + } + + origStatus := obj.Status.DeepCopy() + obj = obj.DeepCopy() + newStatus, err := a.handler(obj, obj.Status) + if err != nil { + // Revert to old status on error + newStatus = *origStatus.DeepCopy() + } + + if a.condition != "" { + if errors.IsConflict(err) { + a.condition.SetError(&newStatus, "", nil) + } else { + a.condition.SetError(&newStatus, "", err) + } + } + if !equality.Semantic.DeepEqual(origStatus, &newStatus) { + if a.condition != "" { + // Since status has changed, update the lastUpdatedTime + a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339)) + } + + var newErr error + obj.Status = newStatus + newObj, newErr := a.client.UpdateStatus(obj) + if err == nil { + err = newErr + } + if newErr == nil { + obj = newObj + } + } + return obj, err +} + +type backupTargetGeneratingHandler struct { + BackupTargetGeneratingHandler + apply apply.Apply + opts generic.GeneratingHandlerOptions + gvk schema.GroupVersionKind + name string + seen sync.Map +} + +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied +func (a *backupTargetGeneratingHandler) Remove(key string, obj *v1beta2.BackupTarget) (*v1beta2.BackupTarget, error) { + if obj != nil { + return obj, nil + } + + obj = &v1beta2.BackupTarget{} + obj.Namespace, obj.Name = kv.RSplit(key, "/") + obj.SetGroupVersionKind(a.gvk) + + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects() +} + +// Handle executes the configured BackupTargetGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource +func (a *backupTargetGeneratingHandler) Handle(obj *v1beta2.BackupTarget, status v1beta2.BackupTargetStatus) (v1beta2.BackupTargetStatus, error) { + if !obj.DeletionTimestamp.IsZero() { + return status, nil + } + + objs, newStatus, err := a.BackupTargetGeneratingHandler(obj, status) + if err != nil { + return newStatus, err + } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } + + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *backupTargetGeneratingHandler) isNewResourceVersion(obj *v1beta2.BackupTarget) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *backupTargetGeneratingHandler) storeResourceVersion(obj *v1beta2.BackupTarget) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) +} diff --git a/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backupvolume.go b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backupvolume.go new file mode 100644 index 000000000..f809b8392 --- /dev/null +++ b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/backupvolume.go @@ -0,0 +1,208 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1beta2 + +import ( + "context" + "sync" + "time" + + v1beta2 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" + "github.com/rancher/wrangler/v3/pkg/apply" + "github.com/rancher/wrangler/v3/pkg/condition" + "github.com/rancher/wrangler/v3/pkg/generic" + "github.com/rancher/wrangler/v3/pkg/kv" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// BackupVolumeController interface for managing BackupVolume resources. +type BackupVolumeController interface { + generic.ControllerInterface[*v1beta2.BackupVolume, *v1beta2.BackupVolumeList] +} + +// BackupVolumeClient interface for managing BackupVolume resources in Kubernetes. +type BackupVolumeClient interface { + generic.ClientInterface[*v1beta2.BackupVolume, *v1beta2.BackupVolumeList] +} + +// BackupVolumeCache interface for retrieving BackupVolume resources in memory. +type BackupVolumeCache interface { + generic.CacheInterface[*v1beta2.BackupVolume] +} + +// BackupVolumeStatusHandler is executed for every added or modified BackupVolume. Should return the new status to be updated +type BackupVolumeStatusHandler func(obj *v1beta2.BackupVolume, status v1beta2.BackupVolumeStatus) (v1beta2.BackupVolumeStatus, error) + +// BackupVolumeGeneratingHandler is the top-level handler that is executed for every BackupVolume event. It extends BackupVolumeStatusHandler by a returning a slice of child objects to be passed to apply.Apply +type BackupVolumeGeneratingHandler func(obj *v1beta2.BackupVolume, status v1beta2.BackupVolumeStatus) ([]runtime.Object, v1beta2.BackupVolumeStatus, error) + +// RegisterBackupVolumeStatusHandler configures a BackupVolumeController to execute a BackupVolumeStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterBackupVolumeStatusHandler(ctx context.Context, controller BackupVolumeController, condition condition.Cond, name string, handler BackupVolumeStatusHandler) { + statusHandler := &backupVolumeStatusHandler{ + client: controller, + condition: condition, + handler: handler, + } + controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) +} + +// RegisterBackupVolumeGeneratingHandler configures a BackupVolumeController to execute a BackupVolumeGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterBackupVolumeGeneratingHandler(ctx context.Context, controller BackupVolumeController, apply apply.Apply, + condition condition.Cond, name string, handler BackupVolumeGeneratingHandler, opts *generic.GeneratingHandlerOptions) { + statusHandler := &backupVolumeGeneratingHandler{ + BackupVolumeGeneratingHandler: handler, + apply: apply, + name: name, + gvk: controller.GroupVersionKind(), + } + if opts != nil { + statusHandler.opts = *opts + } + controller.OnChange(ctx, name, statusHandler.Remove) + RegisterBackupVolumeStatusHandler(ctx, controller, condition, name, statusHandler.Handle) +} + +type backupVolumeStatusHandler struct { + client BackupVolumeClient + condition condition.Cond + handler BackupVolumeStatusHandler +} + +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API +func (a *backupVolumeStatusHandler) sync(key string, obj *v1beta2.BackupVolume) (*v1beta2.BackupVolume, error) { + if obj == nil { + return obj, nil + } + + origStatus := obj.Status.DeepCopy() + obj = obj.DeepCopy() + newStatus, err := a.handler(obj, obj.Status) + if err != nil { + // Revert to old status on error + newStatus = *origStatus.DeepCopy() + } + + if a.condition != "" { + if errors.IsConflict(err) { + a.condition.SetError(&newStatus, "", nil) + } else { + a.condition.SetError(&newStatus, "", err) + } + } + if !equality.Semantic.DeepEqual(origStatus, &newStatus) { + if a.condition != "" { + // Since status has changed, update the lastUpdatedTime + a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339)) + } + + var newErr error + obj.Status = newStatus + newObj, newErr := a.client.UpdateStatus(obj) + if err == nil { + err = newErr + } + if newErr == nil { + obj = newObj + } + } + return obj, err +} + +type backupVolumeGeneratingHandler struct { + BackupVolumeGeneratingHandler + apply apply.Apply + opts generic.GeneratingHandlerOptions + gvk schema.GroupVersionKind + name string + seen sync.Map +} + +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied +func (a *backupVolumeGeneratingHandler) Remove(key string, obj *v1beta2.BackupVolume) (*v1beta2.BackupVolume, error) { + if obj != nil { + return obj, nil + } + + obj = &v1beta2.BackupVolume{} + obj.Namespace, obj.Name = kv.RSplit(key, "/") + obj.SetGroupVersionKind(a.gvk) + + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects() +} + +// Handle executes the configured BackupVolumeGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource +func (a *backupVolumeGeneratingHandler) Handle(obj *v1beta2.BackupVolume, status v1beta2.BackupVolumeStatus) (v1beta2.BackupVolumeStatus, error) { + if !obj.DeletionTimestamp.IsZero() { + return status, nil + } + + objs, newStatus, err := a.BackupVolumeGeneratingHandler(obj, status) + if err != nil { + return newStatus, err + } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } + + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *backupVolumeGeneratingHandler) isNewResourceVersion(obj *v1beta2.BackupVolume) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *backupVolumeGeneratingHandler) storeResourceVersion(obj *v1beta2.BackupVolume) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) +} diff --git a/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/engine.go b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/engine.go new file mode 100644 index 000000000..dd86ecb07 --- /dev/null +++ b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/engine.go @@ -0,0 +1,208 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1beta2 + +import ( + "context" + "sync" + "time" + + v1beta2 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" + "github.com/rancher/wrangler/v3/pkg/apply" + "github.com/rancher/wrangler/v3/pkg/condition" + "github.com/rancher/wrangler/v3/pkg/generic" + "github.com/rancher/wrangler/v3/pkg/kv" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// EngineController interface for managing Engine resources. +type EngineController interface { + generic.ControllerInterface[*v1beta2.Engine, *v1beta2.EngineList] +} + +// EngineClient interface for managing Engine resources in Kubernetes. +type EngineClient interface { + generic.ClientInterface[*v1beta2.Engine, *v1beta2.EngineList] +} + +// EngineCache interface for retrieving Engine resources in memory. +type EngineCache interface { + generic.CacheInterface[*v1beta2.Engine] +} + +// EngineStatusHandler is executed for every added or modified Engine. Should return the new status to be updated +type EngineStatusHandler func(obj *v1beta2.Engine, status v1beta2.EngineStatus) (v1beta2.EngineStatus, error) + +// EngineGeneratingHandler is the top-level handler that is executed for every Engine event. It extends EngineStatusHandler by a returning a slice of child objects to be passed to apply.Apply +type EngineGeneratingHandler func(obj *v1beta2.Engine, status v1beta2.EngineStatus) ([]runtime.Object, v1beta2.EngineStatus, error) + +// RegisterEngineStatusHandler configures a EngineController to execute a EngineStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterEngineStatusHandler(ctx context.Context, controller EngineController, condition condition.Cond, name string, handler EngineStatusHandler) { + statusHandler := &engineStatusHandler{ + client: controller, + condition: condition, + handler: handler, + } + controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) +} + +// RegisterEngineGeneratingHandler configures a EngineController to execute a EngineGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterEngineGeneratingHandler(ctx context.Context, controller EngineController, apply apply.Apply, + condition condition.Cond, name string, handler EngineGeneratingHandler, opts *generic.GeneratingHandlerOptions) { + statusHandler := &engineGeneratingHandler{ + EngineGeneratingHandler: handler, + apply: apply, + name: name, + gvk: controller.GroupVersionKind(), + } + if opts != nil { + statusHandler.opts = *opts + } + controller.OnChange(ctx, name, statusHandler.Remove) + RegisterEngineStatusHandler(ctx, controller, condition, name, statusHandler.Handle) +} + +type engineStatusHandler struct { + client EngineClient + condition condition.Cond + handler EngineStatusHandler +} + +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API +func (a *engineStatusHandler) sync(key string, obj *v1beta2.Engine) (*v1beta2.Engine, error) { + if obj == nil { + return obj, nil + } + + origStatus := obj.Status.DeepCopy() + obj = obj.DeepCopy() + newStatus, err := a.handler(obj, obj.Status) + if err != nil { + // Revert to old status on error + newStatus = *origStatus.DeepCopy() + } + + if a.condition != "" { + if errors.IsConflict(err) { + a.condition.SetError(&newStatus, "", nil) + } else { + a.condition.SetError(&newStatus, "", err) + } + } + if !equality.Semantic.DeepEqual(origStatus, &newStatus) { + if a.condition != "" { + // Since status has changed, update the lastUpdatedTime + a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339)) + } + + var newErr error + obj.Status = newStatus + newObj, newErr := a.client.UpdateStatus(obj) + if err == nil { + err = newErr + } + if newErr == nil { + obj = newObj + } + } + return obj, err +} + +type engineGeneratingHandler struct { + EngineGeneratingHandler + apply apply.Apply + opts generic.GeneratingHandlerOptions + gvk schema.GroupVersionKind + name string + seen sync.Map +} + +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied +func (a *engineGeneratingHandler) Remove(key string, obj *v1beta2.Engine) (*v1beta2.Engine, error) { + if obj != nil { + return obj, nil + } + + obj = &v1beta2.Engine{} + obj.Namespace, obj.Name = kv.RSplit(key, "/") + obj.SetGroupVersionKind(a.gvk) + + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects() +} + +// Handle executes the configured EngineGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource +func (a *engineGeneratingHandler) Handle(obj *v1beta2.Engine, status v1beta2.EngineStatus) (v1beta2.EngineStatus, error) { + if !obj.DeletionTimestamp.IsZero() { + return status, nil + } + + objs, newStatus, err := a.EngineGeneratingHandler(obj, status) + if err != nil { + return newStatus, err + } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } + + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *engineGeneratingHandler) isNewResourceVersion(obj *v1beta2.Engine) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *engineGeneratingHandler) storeResourceVersion(obj *v1beta2.Engine) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) +} diff --git a/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/interface.go b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/interface.go new file mode 100644 index 000000000..85935eabc --- /dev/null +++ b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/interface.go @@ -0,0 +1,104 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1beta2 + +import ( + v1beta2 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" + "github.com/rancher/lasso/pkg/controller" + "github.com/rancher/wrangler/v3/pkg/generic" + "github.com/rancher/wrangler/v3/pkg/schemes" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func init() { + schemes.Register(v1beta2.AddToScheme) +} + +type Interface interface { + BackingImage() BackingImageController + BackingImageDataSource() BackingImageDataSourceController + Backup() BackupController + BackupBackingImage() BackupBackingImageController + BackupTarget() BackupTargetController + BackupVolume() BackupVolumeController + Engine() EngineController + Node() NodeController + Replica() ReplicaController + Setting() SettingController + Snapshot() SnapshotController + Volume() VolumeController +} + +func New(controllerFactory controller.SharedControllerFactory) Interface { + return &version{ + controllerFactory: controllerFactory, + } +} + +type version struct { + controllerFactory controller.SharedControllerFactory +} + +func (v *version) BackingImage() BackingImageController { + return generic.NewController[*v1beta2.BackingImage, *v1beta2.BackingImageList](schema.GroupVersionKind{Group: "longhorn.io", Version: "v1beta2", Kind: "BackingImage"}, "backingimages", true, v.controllerFactory) +} + +func (v *version) BackingImageDataSource() BackingImageDataSourceController { + return generic.NewController[*v1beta2.BackingImageDataSource, *v1beta2.BackingImageDataSourceList](schema.GroupVersionKind{Group: "longhorn.io", Version: "v1beta2", Kind: "BackingImageDataSource"}, "backingimagedatasources", true, v.controllerFactory) +} + +func (v *version) Backup() BackupController { + return generic.NewController[*v1beta2.Backup, *v1beta2.BackupList](schema.GroupVersionKind{Group: "longhorn.io", Version: "v1beta2", Kind: "Backup"}, "backups", true, v.controllerFactory) +} + +func (v *version) BackupBackingImage() BackupBackingImageController { + return generic.NewController[*v1beta2.BackupBackingImage, *v1beta2.BackupBackingImageList](schema.GroupVersionKind{Group: "longhorn.io", Version: "v1beta2", Kind: "BackupBackingImage"}, "backupbackingimages", true, v.controllerFactory) +} + +func (v *version) BackupTarget() BackupTargetController { + return generic.NewController[*v1beta2.BackupTarget, *v1beta2.BackupTargetList](schema.GroupVersionKind{Group: "longhorn.io", Version: "v1beta2", Kind: "BackupTarget"}, "backuptargets", true, v.controllerFactory) +} + +func (v *version) BackupVolume() BackupVolumeController { + return generic.NewController[*v1beta2.BackupVolume, *v1beta2.BackupVolumeList](schema.GroupVersionKind{Group: "longhorn.io", Version: "v1beta2", Kind: "BackupVolume"}, "backupvolumes", true, v.controllerFactory) +} + +func (v *version) Engine() EngineController { + return generic.NewController[*v1beta2.Engine, *v1beta2.EngineList](schema.GroupVersionKind{Group: "longhorn.io", Version: "v1beta2", Kind: "Engine"}, "engines", true, v.controllerFactory) +} + +func (v *version) Node() NodeController { + return generic.NewController[*v1beta2.Node, *v1beta2.NodeList](schema.GroupVersionKind{Group: "longhorn.io", Version: "v1beta2", Kind: "Node"}, "nodes", true, v.controllerFactory) +} + +func (v *version) Replica() ReplicaController { + return generic.NewController[*v1beta2.Replica, *v1beta2.ReplicaList](schema.GroupVersionKind{Group: "longhorn.io", Version: "v1beta2", Kind: "Replica"}, "replicas", true, v.controllerFactory) +} + +func (v *version) Setting() SettingController { + return generic.NewController[*v1beta2.Setting, *v1beta2.SettingList](schema.GroupVersionKind{Group: "longhorn.io", Version: "v1beta2", Kind: "Setting"}, "settings", true, v.controllerFactory) +} + +func (v *version) Snapshot() SnapshotController { + return generic.NewController[*v1beta2.Snapshot, *v1beta2.SnapshotList](schema.GroupVersionKind{Group: "longhorn.io", Version: "v1beta2", Kind: "Snapshot"}, "snapshots", true, v.controllerFactory) +} + +func (v *version) Volume() VolumeController { + return generic.NewController[*v1beta2.Volume, *v1beta2.VolumeList](schema.GroupVersionKind{Group: "longhorn.io", Version: "v1beta2", Kind: "Volume"}, "volumes", true, v.controllerFactory) +} diff --git a/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/node.go b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/node.go new file mode 100644 index 000000000..8b6a07acd --- /dev/null +++ b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/node.go @@ -0,0 +1,208 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1beta2 + +import ( + "context" + "sync" + "time" + + v1beta2 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" + "github.com/rancher/wrangler/v3/pkg/apply" + "github.com/rancher/wrangler/v3/pkg/condition" + "github.com/rancher/wrangler/v3/pkg/generic" + "github.com/rancher/wrangler/v3/pkg/kv" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// NodeController interface for managing Node resources. +type NodeController interface { + generic.ControllerInterface[*v1beta2.Node, *v1beta2.NodeList] +} + +// NodeClient interface for managing Node resources in Kubernetes. +type NodeClient interface { + generic.ClientInterface[*v1beta2.Node, *v1beta2.NodeList] +} + +// NodeCache interface for retrieving Node resources in memory. +type NodeCache interface { + generic.CacheInterface[*v1beta2.Node] +} + +// NodeStatusHandler is executed for every added or modified Node. Should return the new status to be updated +type NodeStatusHandler func(obj *v1beta2.Node, status v1beta2.NodeStatus) (v1beta2.NodeStatus, error) + +// NodeGeneratingHandler is the top-level handler that is executed for every Node event. It extends NodeStatusHandler by a returning a slice of child objects to be passed to apply.Apply +type NodeGeneratingHandler func(obj *v1beta2.Node, status v1beta2.NodeStatus) ([]runtime.Object, v1beta2.NodeStatus, error) + +// RegisterNodeStatusHandler configures a NodeController to execute a NodeStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterNodeStatusHandler(ctx context.Context, controller NodeController, condition condition.Cond, name string, handler NodeStatusHandler) { + statusHandler := &nodeStatusHandler{ + client: controller, + condition: condition, + handler: handler, + } + controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) +} + +// RegisterNodeGeneratingHandler configures a NodeController to execute a NodeGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterNodeGeneratingHandler(ctx context.Context, controller NodeController, apply apply.Apply, + condition condition.Cond, name string, handler NodeGeneratingHandler, opts *generic.GeneratingHandlerOptions) { + statusHandler := &nodeGeneratingHandler{ + NodeGeneratingHandler: handler, + apply: apply, + name: name, + gvk: controller.GroupVersionKind(), + } + if opts != nil { + statusHandler.opts = *opts + } + controller.OnChange(ctx, name, statusHandler.Remove) + RegisterNodeStatusHandler(ctx, controller, condition, name, statusHandler.Handle) +} + +type nodeStatusHandler struct { + client NodeClient + condition condition.Cond + handler NodeStatusHandler +} + +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API +func (a *nodeStatusHandler) sync(key string, obj *v1beta2.Node) (*v1beta2.Node, error) { + if obj == nil { + return obj, nil + } + + origStatus := obj.Status.DeepCopy() + obj = obj.DeepCopy() + newStatus, err := a.handler(obj, obj.Status) + if err != nil { + // Revert to old status on error + newStatus = *origStatus.DeepCopy() + } + + if a.condition != "" { + if errors.IsConflict(err) { + a.condition.SetError(&newStatus, "", nil) + } else { + a.condition.SetError(&newStatus, "", err) + } + } + if !equality.Semantic.DeepEqual(origStatus, &newStatus) { + if a.condition != "" { + // Since status has changed, update the lastUpdatedTime + a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339)) + } + + var newErr error + obj.Status = newStatus + newObj, newErr := a.client.UpdateStatus(obj) + if err == nil { + err = newErr + } + if newErr == nil { + obj = newObj + } + } + return obj, err +} + +type nodeGeneratingHandler struct { + NodeGeneratingHandler + apply apply.Apply + opts generic.GeneratingHandlerOptions + gvk schema.GroupVersionKind + name string + seen sync.Map +} + +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied +func (a *nodeGeneratingHandler) Remove(key string, obj *v1beta2.Node) (*v1beta2.Node, error) { + if obj != nil { + return obj, nil + } + + obj = &v1beta2.Node{} + obj.Namespace, obj.Name = kv.RSplit(key, "/") + obj.SetGroupVersionKind(a.gvk) + + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects() +} + +// Handle executes the configured NodeGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource +func (a *nodeGeneratingHandler) Handle(obj *v1beta2.Node, status v1beta2.NodeStatus) (v1beta2.NodeStatus, error) { + if !obj.DeletionTimestamp.IsZero() { + return status, nil + } + + objs, newStatus, err := a.NodeGeneratingHandler(obj, status) + if err != nil { + return newStatus, err + } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } + + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *nodeGeneratingHandler) isNewResourceVersion(obj *v1beta2.Node) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *nodeGeneratingHandler) storeResourceVersion(obj *v1beta2.Node) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) +} diff --git a/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/replica.go b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/replica.go new file mode 100644 index 000000000..7772fa934 --- /dev/null +++ b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/replica.go @@ -0,0 +1,208 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1beta2 + +import ( + "context" + "sync" + "time" + + v1beta2 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" + "github.com/rancher/wrangler/v3/pkg/apply" + "github.com/rancher/wrangler/v3/pkg/condition" + "github.com/rancher/wrangler/v3/pkg/generic" + "github.com/rancher/wrangler/v3/pkg/kv" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// ReplicaController interface for managing Replica resources. +type ReplicaController interface { + generic.ControllerInterface[*v1beta2.Replica, *v1beta2.ReplicaList] +} + +// ReplicaClient interface for managing Replica resources in Kubernetes. +type ReplicaClient interface { + generic.ClientInterface[*v1beta2.Replica, *v1beta2.ReplicaList] +} + +// ReplicaCache interface for retrieving Replica resources in memory. +type ReplicaCache interface { + generic.CacheInterface[*v1beta2.Replica] +} + +// ReplicaStatusHandler is executed for every added or modified Replica. Should return the new status to be updated +type ReplicaStatusHandler func(obj *v1beta2.Replica, status v1beta2.ReplicaStatus) (v1beta2.ReplicaStatus, error) + +// ReplicaGeneratingHandler is the top-level handler that is executed for every Replica event. It extends ReplicaStatusHandler by a returning a slice of child objects to be passed to apply.Apply +type ReplicaGeneratingHandler func(obj *v1beta2.Replica, status v1beta2.ReplicaStatus) ([]runtime.Object, v1beta2.ReplicaStatus, error) + +// RegisterReplicaStatusHandler configures a ReplicaController to execute a ReplicaStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterReplicaStatusHandler(ctx context.Context, controller ReplicaController, condition condition.Cond, name string, handler ReplicaStatusHandler) { + statusHandler := &replicaStatusHandler{ + client: controller, + condition: condition, + handler: handler, + } + controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) +} + +// RegisterReplicaGeneratingHandler configures a ReplicaController to execute a ReplicaGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterReplicaGeneratingHandler(ctx context.Context, controller ReplicaController, apply apply.Apply, + condition condition.Cond, name string, handler ReplicaGeneratingHandler, opts *generic.GeneratingHandlerOptions) { + statusHandler := &replicaGeneratingHandler{ + ReplicaGeneratingHandler: handler, + apply: apply, + name: name, + gvk: controller.GroupVersionKind(), + } + if opts != nil { + statusHandler.opts = *opts + } + controller.OnChange(ctx, name, statusHandler.Remove) + RegisterReplicaStatusHandler(ctx, controller, condition, name, statusHandler.Handle) +} + +type replicaStatusHandler struct { + client ReplicaClient + condition condition.Cond + handler ReplicaStatusHandler +} + +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API +func (a *replicaStatusHandler) sync(key string, obj *v1beta2.Replica) (*v1beta2.Replica, error) { + if obj == nil { + return obj, nil + } + + origStatus := obj.Status.DeepCopy() + obj = obj.DeepCopy() + newStatus, err := a.handler(obj, obj.Status) + if err != nil { + // Revert to old status on error + newStatus = *origStatus.DeepCopy() + } + + if a.condition != "" { + if errors.IsConflict(err) { + a.condition.SetError(&newStatus, "", nil) + } else { + a.condition.SetError(&newStatus, "", err) + } + } + if !equality.Semantic.DeepEqual(origStatus, &newStatus) { + if a.condition != "" { + // Since status has changed, update the lastUpdatedTime + a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339)) + } + + var newErr error + obj.Status = newStatus + newObj, newErr := a.client.UpdateStatus(obj) + if err == nil { + err = newErr + } + if newErr == nil { + obj = newObj + } + } + return obj, err +} + +type replicaGeneratingHandler struct { + ReplicaGeneratingHandler + apply apply.Apply + opts generic.GeneratingHandlerOptions + gvk schema.GroupVersionKind + name string + seen sync.Map +} + +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied +func (a *replicaGeneratingHandler) Remove(key string, obj *v1beta2.Replica) (*v1beta2.Replica, error) { + if obj != nil { + return obj, nil + } + + obj = &v1beta2.Replica{} + obj.Namespace, obj.Name = kv.RSplit(key, "/") + obj.SetGroupVersionKind(a.gvk) + + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects() +} + +// Handle executes the configured ReplicaGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource +func (a *replicaGeneratingHandler) Handle(obj *v1beta2.Replica, status v1beta2.ReplicaStatus) (v1beta2.ReplicaStatus, error) { + if !obj.DeletionTimestamp.IsZero() { + return status, nil + } + + objs, newStatus, err := a.ReplicaGeneratingHandler(obj, status) + if err != nil { + return newStatus, err + } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } + + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *replicaGeneratingHandler) isNewResourceVersion(obj *v1beta2.Replica) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *replicaGeneratingHandler) storeResourceVersion(obj *v1beta2.Replica) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) +} diff --git a/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/setting.go b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/setting.go new file mode 100644 index 000000000..dca865f0f --- /dev/null +++ b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/setting.go @@ -0,0 +1,208 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1beta2 + +import ( + "context" + "sync" + "time" + + v1beta2 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" + "github.com/rancher/wrangler/v3/pkg/apply" + "github.com/rancher/wrangler/v3/pkg/condition" + "github.com/rancher/wrangler/v3/pkg/generic" + "github.com/rancher/wrangler/v3/pkg/kv" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// SettingController interface for managing Setting resources. +type SettingController interface { + generic.ControllerInterface[*v1beta2.Setting, *v1beta2.SettingList] +} + +// SettingClient interface for managing Setting resources in Kubernetes. +type SettingClient interface { + generic.ClientInterface[*v1beta2.Setting, *v1beta2.SettingList] +} + +// SettingCache interface for retrieving Setting resources in memory. +type SettingCache interface { + generic.CacheInterface[*v1beta2.Setting] +} + +// SettingStatusHandler is executed for every added or modified Setting. Should return the new status to be updated +type SettingStatusHandler func(obj *v1beta2.Setting, status v1beta2.SettingStatus) (v1beta2.SettingStatus, error) + +// SettingGeneratingHandler is the top-level handler that is executed for every Setting event. It extends SettingStatusHandler by a returning a slice of child objects to be passed to apply.Apply +type SettingGeneratingHandler func(obj *v1beta2.Setting, status v1beta2.SettingStatus) ([]runtime.Object, v1beta2.SettingStatus, error) + +// RegisterSettingStatusHandler configures a SettingController to execute a SettingStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterSettingStatusHandler(ctx context.Context, controller SettingController, condition condition.Cond, name string, handler SettingStatusHandler) { + statusHandler := &settingStatusHandler{ + client: controller, + condition: condition, + handler: handler, + } + controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) +} + +// RegisterSettingGeneratingHandler configures a SettingController to execute a SettingGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterSettingGeneratingHandler(ctx context.Context, controller SettingController, apply apply.Apply, + condition condition.Cond, name string, handler SettingGeneratingHandler, opts *generic.GeneratingHandlerOptions) { + statusHandler := &settingGeneratingHandler{ + SettingGeneratingHandler: handler, + apply: apply, + name: name, + gvk: controller.GroupVersionKind(), + } + if opts != nil { + statusHandler.opts = *opts + } + controller.OnChange(ctx, name, statusHandler.Remove) + RegisterSettingStatusHandler(ctx, controller, condition, name, statusHandler.Handle) +} + +type settingStatusHandler struct { + client SettingClient + condition condition.Cond + handler SettingStatusHandler +} + +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API +func (a *settingStatusHandler) sync(key string, obj *v1beta2.Setting) (*v1beta2.Setting, error) { + if obj == nil { + return obj, nil + } + + origStatus := obj.Status.DeepCopy() + obj = obj.DeepCopy() + newStatus, err := a.handler(obj, obj.Status) + if err != nil { + // Revert to old status on error + newStatus = *origStatus.DeepCopy() + } + + if a.condition != "" { + if errors.IsConflict(err) { + a.condition.SetError(&newStatus, "", nil) + } else { + a.condition.SetError(&newStatus, "", err) + } + } + if !equality.Semantic.DeepEqual(origStatus, &newStatus) { + if a.condition != "" { + // Since status has changed, update the lastUpdatedTime + a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339)) + } + + var newErr error + obj.Status = newStatus + newObj, newErr := a.client.UpdateStatus(obj) + if err == nil { + err = newErr + } + if newErr == nil { + obj = newObj + } + } + return obj, err +} + +type settingGeneratingHandler struct { + SettingGeneratingHandler + apply apply.Apply + opts generic.GeneratingHandlerOptions + gvk schema.GroupVersionKind + name string + seen sync.Map +} + +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied +func (a *settingGeneratingHandler) Remove(key string, obj *v1beta2.Setting) (*v1beta2.Setting, error) { + if obj != nil { + return obj, nil + } + + obj = &v1beta2.Setting{} + obj.Namespace, obj.Name = kv.RSplit(key, "/") + obj.SetGroupVersionKind(a.gvk) + + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects() +} + +// Handle executes the configured SettingGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource +func (a *settingGeneratingHandler) Handle(obj *v1beta2.Setting, status v1beta2.SettingStatus) (v1beta2.SettingStatus, error) { + if !obj.DeletionTimestamp.IsZero() { + return status, nil + } + + objs, newStatus, err := a.SettingGeneratingHandler(obj, status) + if err != nil { + return newStatus, err + } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } + + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *settingGeneratingHandler) isNewResourceVersion(obj *v1beta2.Setting) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *settingGeneratingHandler) storeResourceVersion(obj *v1beta2.Setting) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) +} diff --git a/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/snapshot.go b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/snapshot.go new file mode 100644 index 000000000..37e6d3926 --- /dev/null +++ b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/snapshot.go @@ -0,0 +1,208 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1beta2 + +import ( + "context" + "sync" + "time" + + v1beta2 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" + "github.com/rancher/wrangler/v3/pkg/apply" + "github.com/rancher/wrangler/v3/pkg/condition" + "github.com/rancher/wrangler/v3/pkg/generic" + "github.com/rancher/wrangler/v3/pkg/kv" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// SnapshotController interface for managing Snapshot resources. +type SnapshotController interface { + generic.ControllerInterface[*v1beta2.Snapshot, *v1beta2.SnapshotList] +} + +// SnapshotClient interface for managing Snapshot resources in Kubernetes. +type SnapshotClient interface { + generic.ClientInterface[*v1beta2.Snapshot, *v1beta2.SnapshotList] +} + +// SnapshotCache interface for retrieving Snapshot resources in memory. +type SnapshotCache interface { + generic.CacheInterface[*v1beta2.Snapshot] +} + +// SnapshotStatusHandler is executed for every added or modified Snapshot. Should return the new status to be updated +type SnapshotStatusHandler func(obj *v1beta2.Snapshot, status v1beta2.SnapshotStatus) (v1beta2.SnapshotStatus, error) + +// SnapshotGeneratingHandler is the top-level handler that is executed for every Snapshot event. It extends SnapshotStatusHandler by a returning a slice of child objects to be passed to apply.Apply +type SnapshotGeneratingHandler func(obj *v1beta2.Snapshot, status v1beta2.SnapshotStatus) ([]runtime.Object, v1beta2.SnapshotStatus, error) + +// RegisterSnapshotStatusHandler configures a SnapshotController to execute a SnapshotStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterSnapshotStatusHandler(ctx context.Context, controller SnapshotController, condition condition.Cond, name string, handler SnapshotStatusHandler) { + statusHandler := &snapshotStatusHandler{ + client: controller, + condition: condition, + handler: handler, + } + controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) +} + +// RegisterSnapshotGeneratingHandler configures a SnapshotController to execute a SnapshotGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterSnapshotGeneratingHandler(ctx context.Context, controller SnapshotController, apply apply.Apply, + condition condition.Cond, name string, handler SnapshotGeneratingHandler, opts *generic.GeneratingHandlerOptions) { + statusHandler := &snapshotGeneratingHandler{ + SnapshotGeneratingHandler: handler, + apply: apply, + name: name, + gvk: controller.GroupVersionKind(), + } + if opts != nil { + statusHandler.opts = *opts + } + controller.OnChange(ctx, name, statusHandler.Remove) + RegisterSnapshotStatusHandler(ctx, controller, condition, name, statusHandler.Handle) +} + +type snapshotStatusHandler struct { + client SnapshotClient + condition condition.Cond + handler SnapshotStatusHandler +} + +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API +func (a *snapshotStatusHandler) sync(key string, obj *v1beta2.Snapshot) (*v1beta2.Snapshot, error) { + if obj == nil { + return obj, nil + } + + origStatus := obj.Status.DeepCopy() + obj = obj.DeepCopy() + newStatus, err := a.handler(obj, obj.Status) + if err != nil { + // Revert to old status on error + newStatus = *origStatus.DeepCopy() + } + + if a.condition != "" { + if errors.IsConflict(err) { + a.condition.SetError(&newStatus, "", nil) + } else { + a.condition.SetError(&newStatus, "", err) + } + } + if !equality.Semantic.DeepEqual(origStatus, &newStatus) { + if a.condition != "" { + // Since status has changed, update the lastUpdatedTime + a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339)) + } + + var newErr error + obj.Status = newStatus + newObj, newErr := a.client.UpdateStatus(obj) + if err == nil { + err = newErr + } + if newErr == nil { + obj = newObj + } + } + return obj, err +} + +type snapshotGeneratingHandler struct { + SnapshotGeneratingHandler + apply apply.Apply + opts generic.GeneratingHandlerOptions + gvk schema.GroupVersionKind + name string + seen sync.Map +} + +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied +func (a *snapshotGeneratingHandler) Remove(key string, obj *v1beta2.Snapshot) (*v1beta2.Snapshot, error) { + if obj != nil { + return obj, nil + } + + obj = &v1beta2.Snapshot{} + obj.Namespace, obj.Name = kv.RSplit(key, "/") + obj.SetGroupVersionKind(a.gvk) + + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects() +} + +// Handle executes the configured SnapshotGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource +func (a *snapshotGeneratingHandler) Handle(obj *v1beta2.Snapshot, status v1beta2.SnapshotStatus) (v1beta2.SnapshotStatus, error) { + if !obj.DeletionTimestamp.IsZero() { + return status, nil + } + + objs, newStatus, err := a.SnapshotGeneratingHandler(obj, status) + if err != nil { + return newStatus, err + } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } + + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *snapshotGeneratingHandler) isNewResourceVersion(obj *v1beta2.Snapshot) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *snapshotGeneratingHandler) storeResourceVersion(obj *v1beta2.Snapshot) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) +} diff --git a/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/volume.go b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/volume.go new file mode 100644 index 000000000..d8f9ccf2d --- /dev/null +++ b/vendor/github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2/volume.go @@ -0,0 +1,208 @@ +/* +Copyright 2025 Rancher Labs, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by main. DO NOT EDIT. + +package v1beta2 + +import ( + "context" + "sync" + "time" + + v1beta2 "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" + "github.com/rancher/wrangler/v3/pkg/apply" + "github.com/rancher/wrangler/v3/pkg/condition" + "github.com/rancher/wrangler/v3/pkg/generic" + "github.com/rancher/wrangler/v3/pkg/kv" + "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// VolumeController interface for managing Volume resources. +type VolumeController interface { + generic.ControllerInterface[*v1beta2.Volume, *v1beta2.VolumeList] +} + +// VolumeClient interface for managing Volume resources in Kubernetes. +type VolumeClient interface { + generic.ClientInterface[*v1beta2.Volume, *v1beta2.VolumeList] +} + +// VolumeCache interface for retrieving Volume resources in memory. +type VolumeCache interface { + generic.CacheInterface[*v1beta2.Volume] +} + +// VolumeStatusHandler is executed for every added or modified Volume. Should return the new status to be updated +type VolumeStatusHandler func(obj *v1beta2.Volume, status v1beta2.VolumeStatus) (v1beta2.VolumeStatus, error) + +// VolumeGeneratingHandler is the top-level handler that is executed for every Volume event. It extends VolumeStatusHandler by a returning a slice of child objects to be passed to apply.Apply +type VolumeGeneratingHandler func(obj *v1beta2.Volume, status v1beta2.VolumeStatus) ([]runtime.Object, v1beta2.VolumeStatus, error) + +// RegisterVolumeStatusHandler configures a VolumeController to execute a VolumeStatusHandler for every events observed. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterVolumeStatusHandler(ctx context.Context, controller VolumeController, condition condition.Cond, name string, handler VolumeStatusHandler) { + statusHandler := &volumeStatusHandler{ + client: controller, + condition: condition, + handler: handler, + } + controller.AddGenericHandler(ctx, name, generic.FromObjectHandlerToHandler(statusHandler.sync)) +} + +// RegisterVolumeGeneratingHandler configures a VolumeController to execute a VolumeGeneratingHandler for every events observed, passing the returned objects to the provided apply.Apply. +// If a non-empty condition is provided, it will be updated in the status conditions for every handler execution +func RegisterVolumeGeneratingHandler(ctx context.Context, controller VolumeController, apply apply.Apply, + condition condition.Cond, name string, handler VolumeGeneratingHandler, opts *generic.GeneratingHandlerOptions) { + statusHandler := &volumeGeneratingHandler{ + VolumeGeneratingHandler: handler, + apply: apply, + name: name, + gvk: controller.GroupVersionKind(), + } + if opts != nil { + statusHandler.opts = *opts + } + controller.OnChange(ctx, name, statusHandler.Remove) + RegisterVolumeStatusHandler(ctx, controller, condition, name, statusHandler.Handle) +} + +type volumeStatusHandler struct { + client VolumeClient + condition condition.Cond + handler VolumeStatusHandler +} + +// sync is executed on every resource addition or modification. Executes the configured handlers and sends the updated status to the Kubernetes API +func (a *volumeStatusHandler) sync(key string, obj *v1beta2.Volume) (*v1beta2.Volume, error) { + if obj == nil { + return obj, nil + } + + origStatus := obj.Status.DeepCopy() + obj = obj.DeepCopy() + newStatus, err := a.handler(obj, obj.Status) + if err != nil { + // Revert to old status on error + newStatus = *origStatus.DeepCopy() + } + + if a.condition != "" { + if errors.IsConflict(err) { + a.condition.SetError(&newStatus, "", nil) + } else { + a.condition.SetError(&newStatus, "", err) + } + } + if !equality.Semantic.DeepEqual(origStatus, &newStatus) { + if a.condition != "" { + // Since status has changed, update the lastUpdatedTime + a.condition.LastUpdated(&newStatus, time.Now().UTC().Format(time.RFC3339)) + } + + var newErr error + obj.Status = newStatus + newObj, newErr := a.client.UpdateStatus(obj) + if err == nil { + err = newErr + } + if newErr == nil { + obj = newObj + } + } + return obj, err +} + +type volumeGeneratingHandler struct { + VolumeGeneratingHandler + apply apply.Apply + opts generic.GeneratingHandlerOptions + gvk schema.GroupVersionKind + name string + seen sync.Map +} + +// Remove handles the observed deletion of a resource, cascade deleting every associated resource previously applied +func (a *volumeGeneratingHandler) Remove(key string, obj *v1beta2.Volume) (*v1beta2.Volume, error) { + if obj != nil { + return obj, nil + } + + obj = &v1beta2.Volume{} + obj.Namespace, obj.Name = kv.RSplit(key, "/") + obj.SetGroupVersionKind(a.gvk) + + if a.opts.UniqueApplyForResourceVersion { + a.seen.Delete(key) + } + + return nil, generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects() +} + +// Handle executes the configured VolumeGeneratingHandler and pass the resulting objects to apply.Apply, finally returning the new status of the resource +func (a *volumeGeneratingHandler) Handle(obj *v1beta2.Volume, status v1beta2.VolumeStatus) (v1beta2.VolumeStatus, error) { + if !obj.DeletionTimestamp.IsZero() { + return status, nil + } + + objs, newStatus, err := a.VolumeGeneratingHandler(obj, status) + if err != nil { + return newStatus, err + } + if !a.isNewResourceVersion(obj) { + return newStatus, nil + } + + err = generic.ConfigureApplyForObject(a.apply, obj, &a.opts). + WithOwner(obj). + WithSetID(a.name). + ApplyObjects(objs...) + if err != nil { + return newStatus, err + } + a.storeResourceVersion(obj) + return newStatus, nil +} + +// isNewResourceVersion detects if a specific resource version was already successfully processed. +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *volumeGeneratingHandler) isNewResourceVersion(obj *v1beta2.Volume) bool { + if !a.opts.UniqueApplyForResourceVersion { + return true + } + + // Apply once per resource version + key := obj.Namespace + "/" + obj.Name + previous, ok := a.seen.Load(key) + return !ok || previous != obj.ResourceVersion +} + +// storeResourceVersion keeps track of the latest resource version of an object for which Apply was executed +// Only used if UniqueApplyForResourceVersion is set in generic.GeneratingHandlerOptions +func (a *volumeGeneratingHandler) storeResourceVersion(obj *v1beta2.Volume) { + if !a.opts.UniqueApplyForResourceVersion { + return + } + + key := obj.Namespace + "/" + obj.Name + a.seen.Store(key, obj.ResourceVersion) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 6e377bcad..aba3554a4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -87,6 +87,8 @@ github.com/harvester/harvester/pkg/apis/harvesterhci.io github.com/harvester/harvester/pkg/apis/harvesterhci.io/v1beta1 github.com/harvester/harvester/pkg/generated/controllers/harvesterhci.io github.com/harvester/harvester/pkg/generated/controllers/harvesterhci.io/v1beta1 +github.com/harvester/harvester/pkg/generated/controllers/longhorn.io +github.com/harvester/harvester/pkg/generated/controllers/longhorn.io/v1beta2 # github.com/harvester/webhook v0.1.5 ## explicit; go 1.22.7 github.com/harvester/webhook/pkg/clients From 9352dcf8676700c3d7e0f4cad7b25fdda913b163 Mon Sep 17 00:00:00 2001 From: Martin Dekov Date: Wed, 24 Sep 2025 13:52:02 +0300 Subject: [PATCH 02/12] Address feedback from Moritz Addressing feedback form Moritz with suggestion to use get for specific volumes which are degraded. Initial assumption was that get would reach out kubernetes API for cache, but he clairified that cache is local so calls don't weight on the API. Switched to that. Signed-off-by: Martin Dekov --- pkg/webhook/blockdevice/validator.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/webhook/blockdevice/validator.go b/pkg/webhook/blockdevice/validator.go index aef7a1953..2f8ea64f0 100644 --- a/pkg/webhook/blockdevice/validator.go +++ b/pkg/webhook/blockdevice/validator.go @@ -179,16 +179,16 @@ func (v *Validator) validateDegradedVolumes(old *diskv1.BlockDevice) error { if len(degradedVolumes) == 0 { return nil } - pvList, err := v.pvCache.List(labels.Everything()) - if err != nil { - return err - } selectorDegradedVol := make(map[string]string) - for _, pv := range pvList { - if degradedVolumes[pv.Name] { + for name, degraded := range degradedVolumes { + if degraded { + pv, err := v.pvCache.Get(name) + if err != nil { + return err + } diskSelector := pv.Spec.CSI.VolumeAttributes[DiskSelectorKey] if len(diskSelector) != 0 { - selectorDegradedVol[pv.Spec.CSI.VolumeAttributes[DiskSelectorKey]] = pv.Name + selectorDegradedVol[diskSelector] = pv.Name } } } From 3b0f1aee839e2120b67996ce5dded5af128b7f80 Mon Sep 17 00:00:00 2001 From: Martin Dekov Date: Wed, 24 Sep 2025 15:47:32 +0300 Subject: [PATCH 03/12] Skip checks in case of missing tags Skipping checks in case of missing tags as if storage is missing tags then its either the default one which comes with installation or is not being used as the tag is the selector which matches the objects to the disk Signed-off-by: Martin Dekov --- pkg/webhook/blockdevice/validator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/webhook/blockdevice/validator.go b/pkg/webhook/blockdevice/validator.go index 2f8ea64f0..5d88358ae 100644 --- a/pkg/webhook/blockdevice/validator.go +++ b/pkg/webhook/blockdevice/validator.go @@ -86,7 +86,7 @@ func (v *Validator) validateLHDisk(oldBd, newBd *diskv1.BlockDevice) error { if err != nil { return err } - if len(nodeList) == 1 { + if len(nodeList) == 1 && len(oldBd.Spec.Tags) > 0 { err := v.validateDegradedVolumes(oldBd) if err != nil { return err From 602c43e32fd351b20b5c64778f67a3c728234413 Mon Sep 17 00:00:00 2001 From: Martin Dekov Date: Fri, 26 Sep 2025 10:39:11 +0300 Subject: [PATCH 04/12] Fix nil pointer and rbac with node access Fixing nil pointer by checking the Provisioner of both old and new objects as those can be nil and then panic would be handled silently. Also since we have cache for nodes added added nodes to the cluster role Signed-off-by: Martin Dekov --- deploy/charts/harvester-node-disk-manager/templates/rbac.yaml | 3 +++ pkg/webhook/blockdevice/validator.go | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/deploy/charts/harvester-node-disk-manager/templates/rbac.yaml b/deploy/charts/harvester-node-disk-manager/templates/rbac.yaml index 593ee1466..6cad3f63c 100644 --- a/deploy/charts/harvester-node-disk-manager/templates/rbac.yaml +++ b/deploy/charts/harvester-node-disk-manager/templates/rbac.yaml @@ -68,6 +68,9 @@ rules: - apiGroups: [ "admissionregistration.k8s.io" ] resources: [ "validatingwebhookconfigurations", "mutatingwebhookconfigurations" ] verbs: [ "*" ] + - apiGroups: [ "" ] + resources: [ "nodes" ] + verbs: [ "get", "watch", "list" ] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/pkg/webhook/blockdevice/validator.go b/pkg/webhook/blockdevice/validator.go index 5d88358ae..221cb8b32 100644 --- a/pkg/webhook/blockdevice/validator.go +++ b/pkg/webhook/blockdevice/validator.go @@ -81,7 +81,9 @@ func (v *Validator) validateProvisioner(bd *diskv1.BlockDevice) error { } func (v *Validator) validateLHDisk(oldBd, newBd *diskv1.BlockDevice) error { - if oldBd.Spec.Provisioner.Longhorn != nil && newBd.Spec.Provisioner.Longhorn != nil && oldBd.Spec.Provision && !newBd.Spec.Provision { + if oldBd.Spec.Provisioner != nil && newBd.Spec.Provisioner != nil && + oldBd.Spec.Provisioner.Longhorn != nil && newBd.Spec.Provisioner.Longhorn != nil && + oldBd.Spec.Provision && !newBd.Spec.Provision { nodeList, err := v.nodeCache.List(labels.Everything()) if err != nil { return err From 91f342802127ba34c136943e0fb432f21d7e5c91 Mon Sep 17 00:00:00 2001 From: Martin Dekov Date: Fri, 26 Sep 2025 12:40:00 +0300 Subject: [PATCH 05/12] Add feedback from Jian Adding feedback from Jian and fixing potential nil pointer on pv spec CSI, which can be nil before checking it's selector Signed-off-by: Martin Dekov --- pkg/utils/utils.go | 4 +++ pkg/webhook/blockdevice/validator.go | 42 +++++++++++++++------------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 9b4cf1351..4f88cd566 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -23,6 +23,10 @@ const ( LVMCSIDriver = "lvm.driver.harvesterhci.io" // LVMTopologyNodeKey is the key of LVM topology node LVMTopologyNodeKey = "topology.lvm.csi/node" + // DiskSelectorKey is the key which points to the disk tag value + DiskSelectorKey = "diskSelector" + // LonghornSystemNamespaceName is the namespace containing longhorn components + LonghornSystemNamespaceName = "longhorn-system" ) var CmdTimeoutError error diff --git a/pkg/webhook/blockdevice/validator.go b/pkg/webhook/blockdevice/validator.go index 221cb8b32..2024f3b4e 100644 --- a/pkg/webhook/blockdevice/validator.go +++ b/pkg/webhook/blockdevice/validator.go @@ -21,10 +21,6 @@ import ( "github.com/harvester/node-disk-manager/pkg/utils" ) -const ( - DiskSelectorKey = "diskSelector" -) - type Validator struct { admission.DefaultValidator @@ -168,30 +164,34 @@ func (v *Validator) validateVGIsAlreadyUsed(bd *diskv1.BlockDevice) error { } func (v *Validator) validateDegradedVolumes(old *diskv1.BlockDevice) error { - volumeList, err := v.volumeCache.List("longhorn-system", labels.Everything()) + volumeList, err := v.volumeCache.List(utils.LonghornSystemNamespaceName, labels.Everything()) if err != nil { return err } - degradedVolumes := make(map[string]bool) + if len(volumeList) == 0 { + return nil + } + degradedVolumes := make(map[string]struct{}) for _, vol := range volumeList { if vol.Status.Robustness == lhv1.VolumeRobustnessDegraded { - degradedVolumes[vol.Name] = true + degradedVolumes[vol.Name] = struct{}{} } } if len(degradedVolumes) == 0 { return nil } selectorDegradedVol := make(map[string]string) - for name, degraded := range degradedVolumes { - if degraded { - pv, err := v.pvCache.Get(name) - if err != nil { - return err - } - diskSelector := pv.Spec.CSI.VolumeAttributes[DiskSelectorKey] - if len(diskSelector) != 0 { - selectorDegradedVol[diskSelector] = pv.Name - } + for name := range degradedVolumes { + pv, err := v.pvCache.Get(name) + if err != nil { + return err + } + diskSelector := "" + if pv.Spec.CSI != nil { + diskSelector = pv.Spec.CSI.VolumeAttributes[utils.DiskSelectorKey] + } + if len(diskSelector) != 0 { + selectorDegradedVol[diskSelector] = pv.Name } } degradedVolString := "" @@ -212,10 +212,14 @@ func (v *Validator) validateFailedVMImages(old *diskv1.BlockDevice) error { if err != nil { return err } + if len(vmImageList) == 0 { + return nil + } selectorFailedVMIMage := make(map[string]string) for _, vmImage := range vmImageList { - if vmImage.Status.Failed != 0 && len(vmImage.Spec.StorageClassParameters[DiskSelectorKey]) > 0 { - selectorFailedVMIMage[vmImage.Spec.StorageClassParameters[DiskSelectorKey]] = vmImage.Spec.DisplayName + diskSelectorValue := vmImage.Spec.StorageClassParameters[utils.DiskSelectorKey] + if vmImage.Status.Failed != 0 && len(diskSelectorValue) > 0 { + selectorFailedVMIMage[diskSelectorValue] = vmImage.Spec.DisplayName } } failedVMImages := "" From c109bd0ff4a647dfb7670314ab7abccefe2f5944 Mon Sep 17 00:00:00 2001 From: Martin Dekov Date: Fri, 26 Sep 2025 14:42:11 +0300 Subject: [PATCH 06/12] Address feedback for multiple objects per tag Addressing additional feedback where I returned the last problematic object for the tag as opposed to a list of objects related with the tag. Also small optimization to skip logic in case vm image is not in a failed state. Signed-off-by: Martin Dekov --- pkg/webhook/blockdevice/validator.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/pkg/webhook/blockdevice/validator.go b/pkg/webhook/blockdevice/validator.go index 2024f3b4e..3d7df4fb9 100644 --- a/pkg/webhook/blockdevice/validator.go +++ b/pkg/webhook/blockdevice/validator.go @@ -180,7 +180,7 @@ func (v *Validator) validateDegradedVolumes(old *diskv1.BlockDevice) error { if len(degradedVolumes) == 0 { return nil } - selectorDegradedVol := make(map[string]string) + selectorDegradedVol := make(map[string][]string) for name := range degradedVolumes { pv, err := v.pvCache.Get(name) if err != nil { @@ -191,17 +191,17 @@ func (v *Validator) validateDegradedVolumes(old *diskv1.BlockDevice) error { diskSelector = pv.Spec.CSI.VolumeAttributes[utils.DiskSelectorKey] } if len(diskSelector) != 0 { - selectorDegradedVol[diskSelector] = pv.Name + selectorDegradedVol[diskSelector] = append(selectorDegradedVol[diskSelector], pv.Name) } } degradedVolString := "" for _, diskTag := range old.Spec.Tags { if val, ok := selectorDegradedVol[diskTag]; ok { - degradedVolString = fmt.Sprintf("%s, %s", degradedVolString, val) + degradedVolString += fmt.Sprintf(" %s: %v", diskTag, val) } } if len(degradedVolString) > 0 { - return fmt.Errorf("the following volumes: %s attached to disk: %s are in degraded state; evict disk before proceeding", + return fmt.Errorf("the following tags with volumes:%s attached to disk: %s are in degraded state; evict disk before proceeding", degradedVolString, old.Spec.DevPath) } return nil @@ -215,21 +215,24 @@ func (v *Validator) validateFailedVMImages(old *diskv1.BlockDevice) error { if len(vmImageList) == 0 { return nil } - selectorFailedVMIMage := make(map[string]string) + selectorFailedVMIMage := make(map[string][]string) for _, vmImage := range vmImageList { + if vmImage.Status.Failed == 0 { + continue + } diskSelectorValue := vmImage.Spec.StorageClassParameters[utils.DiskSelectorKey] if vmImage.Status.Failed != 0 && len(diskSelectorValue) > 0 { - selectorFailedVMIMage[diskSelectorValue] = vmImage.Spec.DisplayName + selectorFailedVMIMage[diskSelectorValue] = append(selectorFailedVMIMage[diskSelectorValue], vmImage.Spec.DisplayName) } } failedVMImages := "" for _, diskTag := range old.Spec.Tags { if val, ok := selectorFailedVMIMage[diskTag]; ok { - failedVMImages = fmt.Sprintf("%s, %s", failedVMImages, val) + failedVMImages += fmt.Sprintf(" %s: %v", diskTag, val) } } if len(failedVMImages) > 0 { - return fmt.Errorf("the following virtualmachineimages: %s attached to disk: %s are in failed state; evict disk before proceeding", + return fmt.Errorf("the following tags referenced by virtualmachineimages: %s attached to disk: %s are in failed state; evict disk before proceeding", failedVMImages, old.Spec.DevPath) } return nil From 8b789a223f1552acbe31a9b5ce8628a7a7cb9788 Mon Sep 17 00:00:00 2001 From: Martin Dekov Date: Mon, 29 Sep 2025 16:35:06 +0300 Subject: [PATCH 07/12] Address feedback from Moritz Addressing feedback from Moritz including the following: * adding longhorn volume resource to the ndm clusterrole * adding virtualmachineimage resource to the clusterrole * remove redundant usage of slice after logic was changed Signed-off-by: Martin Dekov --- .../charts/harvester-node-disk-manager/templates/rbac.yaml | 5 ++++- pkg/webhook/blockdevice/validator.go | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/deploy/charts/harvester-node-disk-manager/templates/rbac.yaml b/deploy/charts/harvester-node-disk-manager/templates/rbac.yaml index 6cad3f63c..346a10c99 100644 --- a/deploy/charts/harvester-node-disk-manager/templates/rbac.yaml +++ b/deploy/charts/harvester-node-disk-manager/templates/rbac.yaml @@ -57,7 +57,7 @@ rules: resources: [ "storageclasses" ] verbs: [ "*" ] - apiGroups: [ "harvesterhci.io" ] - resources: [ "blockdevices", "lvmvolumegroups", "lvmvolumegroups/status" ] + resources: [ "blockdevices", "lvmvolumegroups", "lvmvolumegroups/status", "virtualmachineimages" ] verbs: [ "*" ] - apiGroups: [ "apiregistration.k8s.io" ] resources: [ "apiservices" ] @@ -71,6 +71,9 @@ rules: - apiGroups: [ "" ] resources: [ "nodes" ] verbs: [ "get", "watch", "list" ] + - apiGroups: ["longhorn.io"] + resources: ["volumes"] + verbs: [ "get", "watch", "list" ] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/pkg/webhook/blockdevice/validator.go b/pkg/webhook/blockdevice/validator.go index 3d7df4fb9..d83984b5c 100644 --- a/pkg/webhook/blockdevice/validator.go +++ b/pkg/webhook/blockdevice/validator.go @@ -171,17 +171,17 @@ func (v *Validator) validateDegradedVolumes(old *diskv1.BlockDevice) error { if len(volumeList) == 0 { return nil } - degradedVolumes := make(map[string]struct{}) + degradedVolumes := make([]string, 40) for _, vol := range volumeList { if vol.Status.Robustness == lhv1.VolumeRobustnessDegraded { - degradedVolumes[vol.Name] = struct{}{} + degradedVolumes = append(degradedVolumes, vol.Name) } } if len(degradedVolumes) == 0 { return nil } selectorDegradedVol := make(map[string][]string) - for name := range degradedVolumes { + for _, name := range degradedVolumes { pv, err := v.pvCache.Get(name) if err != nil { return err From 98f33184398fc644ab2adec7e41c68290b5b0b69 Mon Sep 17 00:00:00 2001 From: Martin Dekov Date: Mon, 29 Sep 2025 17:10:28 +0300 Subject: [PATCH 08/12] Vendor packages required for fakes Signed-off-by: Martin Dekov --- go.mod | 40 +- go.sum | 40 + .../emicklei/go-restful/v3/CHANGES.md | 5 +- .../emicklei/go-restful/v3/README.md | 2 +- .../emicklei/go-restful/v3/jsr311.go | 19 +- .../emicklei/go-restful/v3/route.go | 2 + vendor/github.com/fxamacker/cbor/v2/README.md | 408 +- .../fxamacker/cbor/v2/bytestring.go | 27 + vendor/github.com/fxamacker/cbor/v2/cache.go | 22 +- vendor/github.com/fxamacker/cbor/v2/decode.go | 102 +- vendor/github.com/fxamacker/cbor/v2/doc.go | 51 +- vendor/github.com/fxamacker/cbor/v2/encode.go | 191 +- .../fxamacker/cbor/v2/encode_map.go | 10 +- .../fxamacker/cbor/v2/encode_map_go117.go | 60 - .../fxamacker/cbor/v2/omitzero_go124.go | 8 + .../fxamacker/cbor/v2/omitzero_pre_go124.go | 8 + .../fxamacker/cbor/v2/simplevalue.go | 29 + vendor/github.com/fxamacker/cbor/v2/stream.go | 4 +- .../fxamacker/cbor/v2/structfields.go | 15 +- vendor/github.com/fxamacker/cbor/v2/tag.go | 35 +- .../go-openapi/jsonpointer/.golangci.yml | 31 +- .../go-openapi/jsonpointer/errors.go | 18 + .../go-openapi/jsonpointer/pointer.go | 49 +- .../github.com/go-openapi/swag/.golangci.yml | 34 +- vendor/github.com/go-openapi/swag/errors.go | 15 + vendor/github.com/go-openapi/swag/json.go | 3 +- vendor/github.com/go-openapi/swag/loading.go | 2 +- vendor/github.com/go-openapi/swag/yaml.go | 32 +- .../v1beta1/openapi_generated.go | 536 +- .../harvesterhci.io/v1beta1/supportbundle.go | 19 + .../v1beta1/zz_generated_deepcopy.go | 12 +- vendor/github.com/spf13/pflag/.editorconfig | 12 + vendor/github.com/spf13/pflag/.golangci.yaml | 4 + vendor/github.com/spf13/pflag/flag.go | 29 +- vendor/github.com/spf13/pflag/ip.go | 3 + vendor/github.com/spf13/pflag/ipnet_slice.go | 147 + vendor/github.com/spf13/pflag/string_array.go | 4 - vendor/golang.org/x/crypto/acme/acme.go | 4 + .../x/crypto/acme/autocert/listener.go | 32 +- vendor/golang.org/x/crypto/acme/http.go | 7 +- vendor/golang.org/x/crypto/acme/jws.go | 2 +- vendor/golang.org/x/crypto/acme/types.go | 4 + .../x/crypto/internal/poly1305/mac_noasm.go | 2 +- .../poly1305/{sum_amd64.go => sum_asm.go} | 2 +- .../x/crypto/internal/poly1305/sum_loong64.s | 123 + .../x/crypto/internal/poly1305/sum_ppc64x.go | 47 - vendor/golang.org/x/crypto/ssh/handshake.go | 3 +- .../x/crypto/ssh/knownhosts/knownhosts.go | 36 +- vendor/golang.org/x/crypto/ssh/mlkem.go | 187 + vendor/golang.org/x/oauth2/internal/doc.go | 2 +- vendor/golang.org/x/oauth2/internal/oauth2.go | 2 +- vendor/golang.org/x/oauth2/internal/token.go | 50 +- .../golang.org/x/oauth2/internal/transport.go | 4 +- vendor/golang.org/x/oauth2/oauth2.go | 63 +- vendor/golang.org/x/oauth2/pkce.go | 15 +- vendor/golang.org/x/oauth2/token.go | 17 +- vendor/golang.org/x/oauth2/transport.go | 24 +- vendor/golang.org/x/sync/errgroup/errgroup.go | 110 +- vendor/golang.org/x/sys/cpu/cpu.go | 23 + .../golang.org/x/sys/cpu/cpu_linux_loong64.go | 22 + .../golang.org/x/sys/cpu/cpu_linux_noinit.go | 2 +- .../golang.org/x/sys/cpu/cpu_linux_riscv64.go | 23 + vendor/golang.org/x/sys/cpu/cpu_loong64.go | 38 + vendor/golang.org/x/sys/cpu/cpu_loong64.s | 13 + vendor/golang.org/x/sys/cpu/cpu_riscv64.go | 12 + vendor/golang.org/x/sys/cpu/parse.go | 4 +- .../golang.org/x/sys/unix/syscall_darwin.go | 149 +- vendor/golang.org/x/sys/unix/syscall_linux.go | 42 +- .../x/sys/unix/zsyscall_darwin_amd64.go | 84 + .../x/sys/unix/zsyscall_darwin_amd64.s | 20 + .../x/sys/unix/zsyscall_darwin_arm64.go | 84 + .../x/sys/unix/zsyscall_darwin_arm64.s | 20 + .../x/sys/windows/security_windows.go | 49 +- .../x/sys/windows/syscall_windows.go | 6 +- .../golang.org/x/sys/windows/types_windows.go | 239 + .../x/sys/windows/zsyscall_windows.go | 9 + vendor/golang.org/x/term/terminal.go | 77 +- vendor/golang.org/x/time/rate/rate.go | 28 +- .../x/tools/go/ast/astutil/imports.go | 3 +- .../x/tools/go/ast/astutil/rewrite.go | 2 +- .../golang.org/x/tools/go/ast/astutil/util.go | 2 + .../x/tools/go/gcexportdata/gcexportdata.go | 5 +- .../x/tools/go/packages/external.go | 2 +- .../golang.org/x/tools/go/packages/golist.go | 2 - .../x/tools/go/packages/packages.go | 22 +- .../x/tools/go/types/typeutil/callee.go | 83 +- .../x/tools/go/types/typeutil/map.go | 9 +- .../x/tools/internal/event/keys/keys.go | 6 +- .../x/tools/internal/event/label/label.go | 13 +- .../x/tools/internal/gcimporter/bimport.go | 2 +- .../x/tools/internal/gcimporter/iexport.go | 40 +- .../x/tools/internal/gcimporter/iimport.go | 5 +- .../tools/internal/gcimporter/ureader_yes.go | 2 +- .../x/tools/internal/gocommand/invoke.go | 2 +- .../x/tools/internal/gopathwalk/walk.go | 11 +- .../x/tools/internal/imports/fix.go | 15 +- .../x/tools/internal/imports/mod.go | 5 +- .../x/tools/internal/imports/mod_cache.go | 4 +- .../x/tools/internal/imports/sortimports.go | 5 +- .../x/tools/internal/modindex/lookup.go | 4 +- .../internal/packagesinternal/packages.go | 3 - .../x/tools/internal/pkgbits/decoder.go | 2 +- .../x/tools/internal/stdlib/deps.go | 359 + .../x/tools/internal/stdlib/import.go | 89 + .../x/tools/internal/stdlib/manifest.go | 34624 ++++++++-------- .../x/tools/internal/stdlib/stdlib.go | 10 +- .../x/tools/internal/typeparams/free.go | 2 +- .../x/tools/internal/typeparams/normalize.go | 2 +- .../x/tools/internal/typeparams/termlist.go | 12 +- .../x/tools/internal/typeparams/typeterm.go | 3 + .../internal/typesinternal/classify_call.go | 135 + .../x/tools/internal/typesinternal/types.go | 21 +- .../editiondefaults/editions_defaults.binpb | Bin 138 -> 146 bytes .../protobuf/internal/filedesc/editions.go | 3 + .../protobuf/internal/genid/descriptor_gen.go | 16 + ...ings_unsafe_go121.go => strings_unsafe.go} | 2 - .../internal/strs/strings_unsafe_go120.go | 94 - .../protobuf/internal/version/version.go | 2 +- .../google.golang.org/protobuf/proto/merge.go | 6 + .../reflect/protoreflect/source_gen.go | 2 + ...{value_unsafe_go121.go => value_unsafe.go} | 2 - .../protoreflect/value_unsafe_go120.go | 98 - .../types/descriptorpb/descriptor.pb.go | 1439 +- .../types/gofeaturespb/go_features.pb.go | 80 +- .../protobuf/types/known/anypb/any.pb.go | 24 +- .../types/known/timestamppb/timestamp.pb.go | 25 +- vendor/k8s.io/utils/buffer/ring_growing.go | 10 + .../k8s.io/utils/clock/testing/fake_clock.go | 16 +- .../api/core/v1/deepcopy_generated.go | 83 + vendor/kubevirt.io/api/core/v1/schema.go | 31 +- .../api/core/v1/schema_swagger_generated.go | 11 +- vendor/kubevirt.io/api/core/v1/types.go | 84 +- .../api/core/v1/types_swagger_generated.go | 26 +- vendor/modules.txt | 52 +- .../v4/typed/validate.go | 4 +- .../v4/value/jsontagutil.go | 63 +- .../v4/value/reflectcache.go | 14 +- 137 files changed, 21619 insertions(+), 19782 deletions(-) delete mode 100644 vendor/github.com/fxamacker/cbor/v2/encode_map_go117.go create mode 100644 vendor/github.com/fxamacker/cbor/v2/omitzero_go124.go create mode 100644 vendor/github.com/fxamacker/cbor/v2/omitzero_pre_go124.go create mode 100644 vendor/github.com/go-openapi/jsonpointer/errors.go create mode 100644 vendor/github.com/go-openapi/swag/errors.go create mode 100644 vendor/github.com/spf13/pflag/.editorconfig create mode 100644 vendor/github.com/spf13/pflag/.golangci.yaml create mode 100644 vendor/github.com/spf13/pflag/ipnet_slice.go rename vendor/golang.org/x/crypto/internal/poly1305/{sum_amd64.go => sum_asm.go} (94%) create mode 100644 vendor/golang.org/x/crypto/internal/poly1305/sum_loong64.s delete mode 100644 vendor/golang.org/x/crypto/internal/poly1305/sum_ppc64x.go create mode 100644 vendor/golang.org/x/crypto/ssh/mlkem.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/cpu/cpu_loong64.s create mode 100644 vendor/golang.org/x/tools/internal/stdlib/deps.go create mode 100644 vendor/golang.org/x/tools/internal/stdlib/import.go create mode 100644 vendor/golang.org/x/tools/internal/typesinternal/classify_call.go rename vendor/google.golang.org/protobuf/internal/strs/{strings_unsafe_go121.go => strings_unsafe.go} (99%) delete mode 100644 vendor/google.golang.org/protobuf/internal/strs/strings_unsafe_go120.go rename vendor/google.golang.org/protobuf/reflect/protoreflect/{value_unsafe_go121.go => value_unsafe.go} (99%) delete mode 100644 vendor/google.golang.org/protobuf/reflect/protoreflect/value_unsafe_go120.go diff --git a/go.mod b/go.mod index 7fcfc2fce..8fbd2765d 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ replace ( require ( github.com/ehazlett/simplelog v0.0.0-20200226020431-d374894e92a4 github.com/harvester/go-common v0.0.0-20250109132713-e748ce72a7ba - github.com/harvester/harvester v1.5.1 + github.com/harvester/harvester v1.6.0 github.com/harvester/webhook v0.1.5 github.com/jaypipes/ghw v0.8.1-0.20210701154532-dd036bd38c40 github.com/kevinburke/ssh_config v1.2.0 @@ -52,11 +52,11 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.10.0 github.com/urfave/cli/v2 v2.3.0 - golang.org/x/crypto v0.36.0 + golang.org/x/crypto v0.38.0 k8s.io/api v0.33.1 k8s.io/apimachinery v0.33.1 k8s.io/client-go v12.0.0+incompatible - k8s.io/utils v0.0.0-20241210054802-24370beab758 + k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 ) require ( @@ -65,15 +65,15 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.8.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.21.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.0 // indirect + github.com/go-openapi/swag v0.23.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.7.0 // indirect @@ -97,20 +97,20 @@ require ( github.com/rancher/dynamiclistener v0.6.1 // indirect github.com/rancher/wrangler v1.1.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.3 // indirect - golang.org/x/mod v0.23.0 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/oauth2 v0.27.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect - golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.30.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.14.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.25.0 // indirect + golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.33.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect @@ -123,11 +123,11 @@ require ( k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-aggregator v0.33.1 // indirect k8s.io/kube-openapi v0.32.6 // indirect - kubevirt.io/api v1.4.0 // indirect + kubevirt.io/api v1.5.0 // indirect kubevirt.io/containerized-data-importer-api v1.61.0 // indirect kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index de27230f9..0fa25a684 100644 --- a/go.sum +++ b/go.sum @@ -32,12 +32,16 @@ github.com/emicklei/go-restful v2.15.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQm github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/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/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= +github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= @@ -55,6 +59,8 @@ github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34 github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +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.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= @@ -65,6 +71,8 @@ github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/e github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +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-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= @@ -119,6 +127,8 @@ github.com/harvester/go-common v0.0.0-20250109132713-e748ce72a7ba h1:U9Q4hwXeqni github.com/harvester/go-common v0.0.0-20250109132713-e748ce72a7ba/go.mod h1:VPPSa9KzRB1XPPkOAz4M4263UPm5bl/Xd3jEnALh3uo= github.com/harvester/harvester v1.5.1 h1:QXpTrHpk3zsK9WmSgMICKV+RPeT9fPgo43x+TMD6O00= github.com/harvester/harvester v1.5.1/go.mod h1:4exlBYpzXDasoMiYhqklwKEHrzd8+tDpg/vX5MVKKlA= +github.com/harvester/harvester v1.6.0 h1:SlC7YQyheSXIeoc/ys/eYZ00HRXJsocHxMUEoXHI+fw= +github.com/harvester/harvester v1.6.0/go.mod h1:ICKrTYyubLljyK2s/4tSyEFC+lJyeFB5OUF1/fS/CBc= github.com/harvester/webhook v0.1.5 h1:LSredtpCpr5n6XzGRW/qMeZDiSX2W206rI9UFFSHPdY= github.com/harvester/webhook v0.1.5/go.mod h1:3KHilfYv/vsGfL0QHaXmZ/o2lR6QGqpFpmO3UBOcV9Y= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -271,6 +281,8 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -323,6 +335,8 @@ golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn5 golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -343,6 +357,8 @@ golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -380,8 +396,12 @@ golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -397,6 +417,8 @@ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -447,6 +469,8 @@ golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -473,6 +497,8 @@ golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -493,9 +519,13 @@ golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -523,6 +553,8 @@ golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -542,6 +574,8 @@ google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHh google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -596,8 +630,12 @@ k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 h1:jgJW5IePPXLGB8e/1wvd0Ich9QE97RvvF3a8J3fP/Lg= +k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= kubevirt.io/api v1.4.0 h1:dDLyQLSp9obzsDrv3cyL1olIc/66IWVaGiR3gfPfgT0= kubevirt.io/api v1.4.0/go.mod h1:qcnumjJeOCo+qdYXf0OjpHGMhad0SAn4i0h6IAP+6Eg= +kubevirt.io/api v1.5.0 h1:3wjUf+xYZk6OThkHqDNI+AnvO5x4aPdyIkJT0xHJ2a0= +kubevirt.io/api v1.5.0/go.mod h1:B6OBbPKntPHOOtoxyRk5YKJHekWWOiwZDb2XncitcJ0= kubevirt.io/containerized-data-importer-api v1.61.0 h1:zqgn4/ftAPRK4ljZDZcgRiW25MMS7hLwAkGqRgVsng0= kubevirt.io/containerized-data-importer-api v1.61.0/go.mod h1:SDJjLGhbPyayDqAqawcGmVNapBp0KodOQvhKPLVGCQU= kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 h1:QMrd0nKP0BGbnxTqakhDZAUhGKxPiPiN5gSDqKUmGGc= @@ -612,6 +650,8 @@ sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxO sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= diff --git a/vendor/github.com/emicklei/go-restful/v3/CHANGES.md b/vendor/github.com/emicklei/go-restful/v3/CHANGES.md index 92b78048e..6f24dfff5 100644 --- a/vendor/github.com/emicklei/go-restful/v3/CHANGES.md +++ b/vendor/github.com/emicklei/go-restful/v3/CHANGES.md @@ -1,5 +1,8 @@ # Change history of go-restful +## [v3.12.2] - 2025-02-21 + +- allow empty payloads in post,put,patch, issue #580 ( thanks @liggitt, Jordan Liggitt) ## [v3.12.1] - 2024-05-28 @@ -18,7 +21,7 @@ - fix by restoring custom JSON handler functions (Mike Beaumont #540) -## [v3.12.0] - 2023-08-19 +## [v3.11.0] - 2023-08-19 - restored behavior as <= v3.9.0 with option to change path strategy using TrimRightSlashEnabled. diff --git a/vendor/github.com/emicklei/go-restful/v3/README.md b/vendor/github.com/emicklei/go-restful/v3/README.md index 7234604e4..3fb40d198 100644 --- a/vendor/github.com/emicklei/go-restful/v3/README.md +++ b/vendor/github.com/emicklei/go-restful/v3/README.md @@ -3,7 +3,7 @@ go-restful package for building REST-style Web Services using Google Go [![Go Report Card](https://goreportcard.com/badge/github.com/emicklei/go-restful)](https://goreportcard.com/report/github.com/emicklei/go-restful) -[![GoDoc](https://godoc.org/github.com/emicklei/go-restful?status.svg)](https://pkg.go.dev/github.com/emicklei/go-restful) +[![Go Reference](https://pkg.go.dev/badge/github.com/emicklei/go-restful.svg)](https://pkg.go.dev/github.com/emicklei/go-restful/v3) [![codecov](https://codecov.io/gh/emicklei/go-restful/branch/master/graph/badge.svg)](https://codecov.io/gh/emicklei/go-restful) - [Code examples use v3](https://github.com/emicklei/go-restful/tree/v3/examples) diff --git a/vendor/github.com/emicklei/go-restful/v3/jsr311.go b/vendor/github.com/emicklei/go-restful/v3/jsr311.go index a9b3faaa8..7f04bd905 100644 --- a/vendor/github.com/emicklei/go-restful/v3/jsr311.go +++ b/vendor/github.com/emicklei/go-restful/v3/jsr311.go @@ -65,7 +65,7 @@ func (RouterJSR311) extractParams(pathExpr *pathExpression, matches []string) ma return params } -// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 +// https://download.oracle.com/otndocs/jcp/jaxrs-1.1-mrel-eval-oth-JSpec/ func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*Route, error) { candidates := make([]*Route, 0, 8) for i, each := range routes { @@ -126,9 +126,7 @@ func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*R if trace { traceLogger.Printf("no Route found (from %d) that matches HTTP Content-Type: %s\n", len(previous), contentType) } - if httpRequest.ContentLength > 0 { - return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type") - } + return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type") } // accept @@ -151,20 +149,9 @@ func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*R for _, candidate := range previous { available = append(available, candidate.Produces...) } - // if POST,PUT,PATCH without body - method, length := httpRequest.Method, httpRequest.Header.Get("Content-Length") - if (method == http.MethodPost || - method == http.MethodPut || - method == http.MethodPatch) && (length == "" || length == "0") { - return nil, NewError( - http.StatusUnsupportedMediaType, - fmt.Sprintf("415: Unsupported Media Type\n\nAvailable representations: %s", strings.Join(available, ", ")), - ) - } return nil, NewError( http.StatusNotAcceptable, - fmt.Sprintf("406: Not Acceptable\n\nAvailable representations: %s", strings.Join(available, ", ")), - ) + fmt.Sprintf("406: Not Acceptable\n\nAvailable representations: %s", strings.Join(available, ", "))) } // return r.bestMatchByMedia(outputMediaOk, contentType, accept), nil return candidates[0], nil diff --git a/vendor/github.com/emicklei/go-restful/v3/route.go b/vendor/github.com/emicklei/go-restful/v3/route.go index 306c44be7..a2056e2ac 100644 --- a/vendor/github.com/emicklei/go-restful/v3/route.go +++ b/vendor/github.com/emicklei/go-restful/v3/route.go @@ -111,6 +111,8 @@ func (r Route) matchesAccept(mimeTypesWithQuality string) bool { } // Return whether this Route can consume content with a type specified by mimeTypes (can be empty). +// If the route does not specify Consumes then return true (*/*). +// If no content type is set then return true for GET,HEAD,OPTIONS,DELETE and TRACE. func (r Route) matchesContentType(mimeTypes string) bool { if len(r.Consumes) == 0 { diff --git a/vendor/github.com/fxamacker/cbor/v2/README.md b/vendor/github.com/fxamacker/cbor/v2/README.md index af0a79507..da9f9e6f0 100644 --- a/vendor/github.com/fxamacker/cbor/v2/README.md +++ b/vendor/github.com/fxamacker/cbor/v2/README.md @@ -1,6 +1,4 @@ -# CBOR Codec in Go - - +

CBOR Codec Go logo

[fxamacker/cbor](https://github.com/fxamacker/cbor) is a library for encoding and decoding [CBOR](https://www.rfc-editor.org/info/std94) and [CBOR Sequences](https://www.rfc-editor.org/rfc/rfc8742.html). @@ -8,23 +6,26 @@ CBOR is a [trusted alternative](https://www.rfc-editor.org/rfc/rfc8949.html#name `fxamacker/cbor` is used in projects by Arm Ltd., Cisco, EdgeX Foundry, Flow Foundation, Fraunhofer‑AISEC, Kubernetes, Let's Encrypt (ISRG), Linux Foundation, Microsoft, Mozilla, Oasis Protocol, Tailscale, Teleport, [etc](https://github.com/fxamacker/cbor#who-uses-fxamackercbor). -See [Quick Start](#quick-start) and [Releases](https://github.com/fxamacker/cbor/releases/). 🆕 `UnmarshalFirst` and `DiagnoseFirst` can decode CBOR Sequences. `cbor.MarshalToBuffer()` and `UserBufferEncMode` accepts user-specified buffer. +See [Quick Start](#quick-start) and [Releases](https://github.com/fxamacker/cbor/releases/). 🆕 `UnmarshalFirst` and `DiagnoseFirst` can decode CBOR Sequences. `MarshalToBuffer` and `UserBufferEncMode` accepts user-specified buffer. ## fxamacker/cbor [![](https://github.com/fxamacker/cbor/workflows/ci/badge.svg)](https://github.com/fxamacker/cbor/actions?query=workflow%3Aci) -[![](https://github.com/fxamacker/cbor/workflows/cover%20%E2%89%A596%25/badge.svg)](https://github.com/fxamacker/cbor/actions?query=workflow%3A%22cover+%E2%89%A596%25%22) +[![](https://github.com/fxamacker/cbor/workflows/cover%20%E2%89%A597%25/badge.svg)](https://github.com/fxamacker/cbor/actions?query=workflow%3A%22cover+%E2%89%A597%25%22) [![CodeQL](https://github.com/fxamacker/cbor/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/fxamacker/cbor/actions/workflows/codeql-analysis.yml) [![](https://img.shields.io/badge/fuzzing-passing-44c010)](#fuzzing-and-code-coverage) [![Go Report Card](https://goreportcard.com/badge/github.com/fxamacker/cbor)](https://goreportcard.com/report/github.com/fxamacker/cbor) +[![](https://img.shields.io/ossf-scorecard/github.com/fxamacker/cbor?label=openssf%20scorecard)](https://github.com/fxamacker/cbor#fuzzing-and-code-coverage) `fxamacker/cbor` is a CBOR codec in full conformance with [IETF STD 94 (RFC 8949)](https://www.rfc-editor.org/info/std94). It also supports CBOR Sequences ([RFC 8742](https://www.rfc-editor.org/rfc/rfc8742.html)) and Extended Diagnostic Notation ([Appendix G of RFC 8610](https://www.rfc-editor.org/rfc/rfc8610.html#appendix-G)). Features include full support for CBOR tags, [Core Deterministic Encoding](https://www.rfc-editor.org/rfc/rfc8949.html#name-core-deterministic-encoding), duplicate map key detection, etc. +API is mostly same as `encoding/json`, plus interfaces that simplify concurrency and CBOR options. + Design balances trade-offs between security, speed, concurrency, encoded data size, usability, etc. -
Highlights

+

🔎  Highlights

__🚀  Speed__ @@ -38,7 +39,7 @@ Codec passed multiple confidential security assessments in 2022. No vulnerabili __🗜️  Data Size__ -Struct tags (`toarray`, `keyasint`, `omitempty`) automatically reduce size of encoded structs. Encoding optionally shrinks float64→32→16 when values fit. +Struct tag options (`toarray`, `keyasint`, `omitempty`, `omitzero`) automatically reduce size of encoded structs. Encoding optionally shrinks float64→32→16 when values fit. __:jigsaw:  Usability__ @@ -58,164 +59,201 @@ Features include CBOR [extension points](https://www.rfc-editor.org/rfc/rfc8949. `fxamacker/cbor` has configurable limits, etc. that defend against malicious CBOR data. -By contrast, `encoding/gob` is [not designed to be hardened against adversarial inputs](https://pkg.go.dev/encoding/gob#hdr-Security). - -

Example decoding with encoding/gob 💥 fatal error (out of memory)

- -```Go -// Example of encoding/gob having "fatal error: runtime: out of memory" -// while decoding 181 bytes. -package main -import ( - "bytes" - "encoding/gob" - "encoding/hex" - "fmt" -) - -// Example data is from https://github.com/golang/go/issues/24446 -// (shortened to 181 bytes). -const data = "4dffb503010102303001ff30000109010130010800010130010800010130" + - "01ffb80001014a01ffb60001014b01ff860001013001ff860001013001ff" + - "860001013001ff860001013001ffb80000001eff850401010e3030303030" + - "30303030303030303001ff3000010c0104000016ffb70201010830303030" + - "3030303001ff3000010c000030ffb6040405fcff00303030303030303030" + - "303030303030303030303030303030303030303030303030303030303030" + - "30" - -type X struct { - J *X - K map[string]int -} - -func main() { - raw, _ := hex.DecodeString(data) - decoder := gob.NewDecoder(bytes.NewReader(raw)) - - var x X - decoder.Decode(&x) // fatal error: runtime: out of memory - fmt.Println("Decoding finished.") -} -``` - -


- -
- -`fxamacker/cbor` is fast at rejecting malformed CBOR data. E.g. attempts to -decode 10 bytes of malicious CBOR data to `[]byte` (with default settings): - -| Codec | Speed (ns/op) | Memory | Allocs | -| :---- | ------------: | -----: | -----: | -| fxamacker/cbor 2.5.0 | 44 ± 5% | 32 B/op | 2 allocs/op | -| ugorji/go 1.2.11 | 5353261 ± 4% | 67111321 B/op | 13 allocs/op | - -
Benchmark details

- -Latest comparison used: -- Input: `[]byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42}` -- go1.19.10, linux/amd64, i5-13600K (disabled all e-cores, DDR4 @2933) -- go test -bench=. -benchmem -count=20 - -#### Prior comparisons - -| Codec | Speed (ns/op) | Memory | Allocs | -| :---- | ------------: | -----: | -----: | -| fxamacker/cbor 2.5.0-beta2 | 44.33 ± 2% | 32 B/op | 2 allocs/op | -| fxamacker/cbor 0.1.0 - 2.4.0 | ~44.68 ± 6% | 32 B/op | 2 allocs/op | -| ugorji/go 1.2.10 | 5524792.50 ± 3% | 67110491 B/op | 12 allocs/op | -| ugorji/go 1.1.0 - 1.2.6 | 💥 runtime: | out of memory: | cannot allocate | - -- Input: `[]byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42}` -- go1.19.6, linux/amd64, i5-13600K (DDR4) -- go test -bench=. -benchmem -count=20 - -


- -
- -### Smaller Encodings with Struct Tags - -Struct tags (`toarray`, `keyasint`, `omitempty`) reduce encoded size of structs. - -
Example encoding 3-level nested Go struct to 1 byte CBOR

- -https://go.dev/play/p/YxwvfPdFQG2 - -```Go -// Example encoding nested struct (with omitempty tag) -// - encoding/json: 18 byte JSON -// - fxamacker/cbor: 1 byte CBOR -package main - -import ( - "encoding/hex" - "encoding/json" - "fmt" - - "github.com/fxamacker/cbor/v2" -) - -type GrandChild struct { - Quux int `json:",omitempty"` -} - -type Child struct { - Baz int `json:",omitempty"` - Qux GrandChild `json:",omitempty"` -} - -type Parent struct { - Foo Child `json:",omitempty"` - Bar int `json:",omitempty"` -} - -func cb() { - results, _ := cbor.Marshal(Parent{}) - fmt.Println("hex(CBOR): " + hex.EncodeToString(results)) - - text, _ := cbor.Diagnose(results) // Diagnostic Notation - fmt.Println("DN: " + text) -} - -func js() { - results, _ := json.Marshal(Parent{}) - fmt.Println("hex(JSON): " + hex.EncodeToString(results)) - - text := string(results) // JSON - fmt.Println("JSON: " + text) -} - -func main() { - cb() - fmt.Println("-------------") - js() -} -``` - -Output (DN is Diagnostic Notation): -``` -hex(CBOR): a0 -DN: {} -------------- -hex(JSON): 7b22466f6f223a7b22517578223a7b7d7d7d -JSON: {"Foo":{"Qux":{}}} -``` - -


- -
- -Example using different struct tags together: +Notably, `fxamacker/cbor` is fast at rejecting malformed CBOR data. + +> [!NOTE] +> Benchmarks rejecting 10 bytes of malicious CBOR data decoding to `[]byte`: +> +> | Codec | Speed (ns/op) | Memory | Allocs | +> | :---- | ------------: | -----: | -----: | +> | fxamacker/cbor 2.7.0 | 47 ± 7% | 32 B/op | 2 allocs/op | +> | ugorji/go 1.2.12 | 5878187 ± 3% | 67111556 B/op | 13 allocs/op | +> +> Faster hardware (overclocked DDR4 or DDR5) can reduce speed difference. +> +>
🔎  Benchmark details

+> +> Latest comparison for decoding CBOR data to Go `[]byte`: +> - Input: `[]byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42}` +> - go1.22.7, linux/amd64, i5-13600K (DDR4-2933, disabled e-cores) +> - go test -bench=. -benchmem -count=20 +> +> #### Prior comparisons +> +> | Codec | Speed (ns/op) | Memory | Allocs | +> | :---- | ------------: | -----: | -----: | +> | fxamacker/cbor 2.5.0-beta2 | 44.33 ± 2% | 32 B/op | 2 allocs/op | +> | fxamacker/cbor 0.1.0 - 2.4.0 | ~44.68 ± 6% | 32 B/op | 2 allocs/op | +> | ugorji/go 1.2.10 | 5524792.50 ± 3% | 67110491 B/op | 12 allocs/op | +> | ugorji/go 1.1.0 - 1.2.6 | 💥 runtime: | out of memory: | cannot allocate | +> +> - Input: `[]byte{0x9B, 0x00, 0x00, 0x42, 0xFA, 0x42, 0xFA, 0x42, 0xFA, 0x42}` +> - go1.19.6, linux/amd64, i5-13600K (DDR4) +> - go test -bench=. -benchmem -count=20 +> +>

+ +In contrast, some codecs can crash or use excessive resources while decoding bad data. + +> [!WARNING] +> Go's `encoding/gob` is [not designed to be hardened against adversarial inputs](https://pkg.go.dev/encoding/gob#hdr-Security). +> +>
🔎  gob fatal error (out of memory) 💥 decoding 181 bytes

+> +> ```Go +> // Example of encoding/gob having "fatal error: runtime: out of memory" +> // while decoding 181 bytes (all Go versions as of Dec. 8, 2024). +> package main +> import ( +> "bytes" +> "encoding/gob" +> "encoding/hex" +> "fmt" +> ) +> +> // Example data is from https://github.com/golang/go/issues/24446 +> // (shortened to 181 bytes). +> const data = "4dffb503010102303001ff30000109010130010800010130010800010130" + +> "01ffb80001014a01ffb60001014b01ff860001013001ff860001013001ff" + +> "860001013001ff860001013001ffb80000001eff850401010e3030303030" + +> "30303030303030303001ff3000010c0104000016ffb70201010830303030" + +> "3030303001ff3000010c000030ffb6040405fcff00303030303030303030" + +> "303030303030303030303030303030303030303030303030303030303030" + +> "30" +> +> type X struct { +> J *X +> K map[string]int +> } +> +> func main() { +> raw, _ := hex.DecodeString(data) +> decoder := gob.NewDecoder(bytes.NewReader(raw)) +> +> var x X +> decoder.Decode(&x) // fatal error: runtime: out of memory +> fmt.Println("Decoding finished.") +> } +> ``` +> +> +>

+ +### Smaller Encodings with Struct Tag Options + +Struct tags automatically reduce encoded size of structs and improve speed. + +We can write less code by using struct tag options: +- `toarray`: encode without field names (decode back to original struct) +- `keyasint`: encode field names as integers (decode back to original struct) +- `omitempty`: omit empty fields when encoding +- `omitzero`: omit zero-value fields when encoding ![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.3.0/cbor_struct_tags_api.svg?sanitize=1 "CBOR API and Go Struct Tags") -API is mostly same as `encoding/json`, plus interfaces that simplify concurrency for CBOR options. +> [!NOTE] +> `fxamacker/cbor` can encode a 3-level nested Go struct to 1 byte! +> - `encoding/json`: 18 bytes of JSON +> - `fxamacker/cbor`: 1 byte of CBOR +> +>
🔎  Encoding 3-level nested Go struct with omitempty

+> +> https://go.dev/play/p/YxwvfPdFQG2 +> +> ```Go +> // Example encoding nested struct (with omitempty tag) +> // - encoding/json: 18 byte JSON +> // - fxamacker/cbor: 1 byte CBOR +> +> package main +> +> import ( +> "encoding/hex" +> "encoding/json" +> "fmt" +> +> "github.com/fxamacker/cbor/v2" +> ) +> +> type GrandChild struct { +> Quux int `json:",omitempty"` +> } +> +> type Child struct { +> Baz int `json:",omitempty"` +> Qux GrandChild `json:",omitempty"` +> } +> +> type Parent struct { +> Foo Child `json:",omitempty"` +> Bar int `json:",omitempty"` +> } +> +> func cb() { +> results, _ := cbor.Marshal(Parent{}) +> fmt.Println("hex(CBOR): " + hex.EncodeToString(results)) +> +> text, _ := cbor.Diagnose(results) // Diagnostic Notation +> fmt.Println("DN: " + text) +> } +> +> func js() { +> results, _ := json.Marshal(Parent{}) +> fmt.Println("hex(JSON): " + hex.EncodeToString(results)) +> +> text := string(results) // JSON +> fmt.Println("JSON: " + text) +> } +> +> func main() { +> cb() +> fmt.Println("-------------") +> js() +> } +> ``` +> +> Output (DN is Diagnostic Notation): +> ``` +> hex(CBOR): a0 +> DN: {} +> ------------- +> hex(JSON): 7b22466f6f223a7b22517578223a7b7d7d7d +> JSON: {"Foo":{"Qux":{}}} +> ``` +> +>

+ ## Quick Start __Install__: `go get github.com/fxamacker/cbor/v2` and `import "github.com/fxamacker/cbor/v2"`. +> [!TIP] +> +> Tinygo users can try beta/experimental branch [feature/cbor-tinygo-beta](https://github.com/fxamacker/cbor/tree/feature/cbor-tinygo-beta). +> +>
🔎  More about tinygo feature branch +> +> ### Tinygo +> +> Branch [feature/cbor-tinygo-beta](https://github.com/fxamacker/cbor/tree/feature/cbor-tinygo-beta) is based on fxamacker/cbor v2.7.0 and it can be compiled using tinygo v0.33 (also compiles with golang/go). +> +> It passes unit tests (with both go1.22 and tinygo v0.33) and is considered beta/experimental for tinygo. +> +> :warning: The `feature/cbor-tinygo-beta` branch does not get fuzz tested yet. +> +> Changes in this feature branch only affect tinygo compiled software. Summary of changes: +> - default `DecOptions.MaxNestedLevels` is reduced to 16 (was 32). User can specify higher limit but 24+ crashes tests when compiled with tinygo v0.33. +> - disabled decoding CBOR tag data to Go interface because tinygo v0.33 is missing needed feature. +> - encoding error message can be different when encoding function type. +> +> Related tinygo issues: +> - https://github.com/tinygo-org/tinygo/issues/4277 +> - https://github.com/tinygo-org/tinygo/issues/4458 +> +>
+ + ### Key Points This library can encode and decode CBOR (RFC 8949) and CBOR Sequences (RFC 8742). @@ -252,16 +290,17 @@ rest, err = cbor.UnmarshalFirst(b, &v) // decode []byte b to v // DiagnoseFirst translates first CBOR data item to text and returns remaining bytes. text, rest, err = cbor.DiagnoseFirst(b) // decode []byte b to Diagnostic Notation text -// NOTE: Unmarshal returns ExtraneousDataError if there are remaining bytes, -// but new funcs UnmarshalFirst and DiagnoseFirst do not. +// NOTE: Unmarshal() returns ExtraneousDataError if there are remaining bytes, but +// UnmarshalFirst() and DiagnoseFirst() allow trailing bytes. ``` -__IMPORTANT__: 👉 CBOR settings allow trade-offs between speed, security, encoding size, etc. - -- Different CBOR libraries may use different default settings. -- CBOR-based formats or protocols usually require specific settings. - -For example, WebAuthn uses "CTAP2 Canonical CBOR" which is available as a preset. +> [!IMPORTANT] +> CBOR settings allow trade-offs between speed, security, encoding size, etc. +> +> - Different CBOR libraries may use different default settings. +> - CBOR-based formats or protocols usually require specific settings. +> +> For example, WebAuthn uses "CTAP2 Canonical CBOR" which is available as a preset. ### Presets @@ -312,9 +351,9 @@ err = em.MarshalToBuffer(v, &buf) // encode v to provided buf ### Struct Tags -Struct tags (`toarray`, `keyasint`, `omitempty`) reduce encoded size of structs. +Struct tag options (`toarray`, `keyasint`, `omitempty`, `omitzero`) reduce encoded size of structs. -
Example encoding 3-level nested Go struct to 1 byte CBOR

+

🔎  Example encoding 3-level nested Go struct to 1 byte CBOR

https://go.dev/play/p/YxwvfPdFQG2 @@ -382,13 +421,13 @@ JSON: {"Foo":{"Qux":{}}}

-
Example using several struct tags

+

🔎  Example using struct tag options

![alt text](https://github.com/fxamacker/images/raw/master/cbor/v2.3.0/cbor_struct_tags_api.svg?sanitize=1 "CBOR API and Go Struct Tags")

-Struct tags simplify use of CBOR-based protocols that require CBOR arrays or maps with integer keys. +Struct tag options simplify use of CBOR-based protocols that require CBOR arrays or maps with integer keys. ### CBOR Tags @@ -404,7 +443,7 @@ em, err := opts.EncModeWithSharedTags(ts) // mutable shared CBOR tags `TagSet` and modes using it are safe for concurrent use. Equivalent API is available for `DecMode`. -
Example using TagSet and TagOptions

+

🔎  Example using TagSet and TagOptions

```go // Use signedCWT struct defined in "Decoding CWT" example. @@ -430,7 +469,7 @@ if err := dm.Unmarshal(data, &v); err != nil { em, _ := cbor.EncOptions{}.EncModeWithTags(tags) // Marshal signedCWT with tag number. -if data, err := cbor.Marshal(v); err != nil { +if data, err := em.Marshal(v); err != nil { return err } ``` @@ -439,7 +478,7 @@ if data, err := cbor.Marshal(v); err != nil { ### Functions and Interfaces -

Functions and interfaces at a glance

+

🔎  Functions and interfaces at a glance

Common functions with same API as `encoding/json`: - `Marshal`, `Unmarshal` @@ -472,11 +511,24 @@ Default limits may need to be increased for systems handling very large data (e. ## Status -v2.7.0 (June 23, 2024) adds features and improvements that help large projects (e.g. Kubernetes) use CBOR as an alternative to JSON and Protocol Buffers. Other improvements include speedups, improved memory use, bug fixes, new serialization options, etc. It passed fuzz tests (5+ billion executions) and is production quality. +v2.8.0 (March 30, 2025) is a small release primarily to add `omitzero` option to struct field tags and fix bugs. It passed fuzz tests (billions of executions) and is production quality. + +v2.8.0 and v2.7.1 fixes these 3 functions (when called directly by user apps) to use same error handling on bad inputs as `cbor.Unmarshal()`: +- `ByteString.UnmarshalCBOR()` +- `RawTag.UnmarshalCBOR()` +- `SimpleValue.UnmarshalCBOR()` + +The above 3 `UnmarshalCBOR()` functions were initially created for internal use and are deprecated now, so please use `Unmarshal()` or `UnmarshalFirst()` instead. To preserve backward compatibility, these deprecated functions were added to fuzz tests and will not be removed in v2. + +The minimum version of Go required to build: +- v2.8.0 requires go 1.20. +- v2.7.1 and older releases require go 1.17. For more details, see [release notes](https://github.com/fxamacker/cbor/releases). -### Prior Release +### Prior Releases + +v2.7.0 (June 23, 2024) adds features and improvements that help large projects (e.g. Kubernetes) use CBOR as an alternative to JSON and Protocol Buffers. Other improvements include speedups, improved memory use, bug fixes, new serialization options, etc. It passed fuzz tests (5+ billion executions) and is production quality. [v2.6.0](https://github.com/fxamacker/cbor/releases/tag/v2.6.0) (February 2024) adds important new features, optimizations, and bug fixes. It is especially useful to systems that need to convert data between CBOR and JSON. New options and optimizations improve handling of bignum, integers, maps, and strings. @@ -489,7 +541,7 @@ See [v2.5.0 release notes](https://github.com/fxamacker/cbor/releases/tag/v2.5.0 See ["Version and API Changes"](https://github.com/fxamacker/cbor#versions-and-api-changes) section for more info about version numbering, etc.