From d61d880f95c2b8020f5bfcd42aa9c59cadab5a69 Mon Sep 17 00:00:00 2001 From: Brian Kanya Date: Tue, 25 Nov 2025 13:46:19 +0100 Subject: [PATCH 01/26] Re-mount volumes on a restart or update #72 --- pkg/rclone/driver.go | 26 ++++++- pkg/rclone/nodeserver.go | 158 +++++++++++++++++++++++++++++++++++++++ pkg/rclone/rclone.go | 9 ++- 3 files changed, 187 insertions(+), 6 deletions(-) diff --git a/pkg/rclone/driver.go b/pkg/rclone/driver.go index 60fd38ba..e4f771ee 100644 --- a/pkg/rclone/driver.go +++ b/pkg/rclone/driver.go @@ -78,14 +78,26 @@ func NewNodeServer(csiDriver *csicommon.CSIDriver, cacheDir string, cacheSize st } rcloneOps := NewRclone(kubeClient, rclonePort, cacheDir, cacheSize) - return &nodeServer{ + // Use kubelet plugin directory for state persistence + stateFile := "/var/lib/kubelet/plugins/csi-rclone/mounted_volumes.json" + + ns := &nodeServer{ DefaultNodeServer: csicommon.NewDefaultNodeServer(csiDriver), mounter: &mount.SafeFormatAndMount{ Interface: mount.New(""), Exec: utilexec.New(), }, - RcloneOps: rcloneOps, - }, nil + RcloneOps: rcloneOps, + mountedVolumes: make(map[string]*MountedVolume), + stateFile: stateFile, + } + + // Load persisted state on startup + if err := ns.loadState(); err != nil { + klog.Warningf("Failed to load persisted volume state: %v", err) + } + + return ns, nil } func NewControllerServer(csiDriver *csicommon.CSIDriver) *controllerServer { @@ -139,7 +151,13 @@ func (d *Driver) Run() error { ) d.server = s if d.ns != nil && d.ns.RcloneOps != nil { - return d.ns.RcloneOps.Run() + onDaemonReady := func() error { + if d.ns != nil { + return d.ns.remountTrackedVolumes(context.Background()) + } + return nil + } + return d.ns.RcloneOps.Run(onDaemonReady) } s.Wait() return nil diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index ef035016..b31def14 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -7,10 +7,13 @@ package rclone import ( "bytes" + "encoding/json" "errors" "fmt" "os" + "path/filepath" "strings" + "sync" "time" "gopkg.in/ini.v1" @@ -37,6 +40,23 @@ type nodeServer struct { *csicommon.DefaultNodeServer mounter *mount.SafeFormatAndMount RcloneOps Operations + + // Track mounted volumes for automatic remounting + mountedVolumes map[string]*MountedVolume + mutex sync.RWMutex + stateFile string +} + +type MountedVolume struct { + VolumeId string + TargetPath string + Remote string + RemotePath string + ConfigData string + ReadOnly bool + Parameters map[string]string + SecretName string + SecretNamespace string } // Mounting Volume (Preparation) @@ -141,6 +161,10 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis } return nil, status.Error(codes.Internal, err.Error()) } + + // Track the mounted volume for automatic remounting + ns.trackMountedVolume(volumeId, targetPath, remote, remotePath, configData, readOnly, flags, secretName, secretNamespace) + // err = ns.WaitForMountAvailable(targetPath) // if err != nil { // return nil, status.Error(codes.Internal, err.Error()) @@ -323,6 +347,10 @@ func (ns *nodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpu if err := ns.RcloneOps.Unmount(ctx, req.GetVolumeId(), targetPath); err != nil { klog.Warningf("Unmounting volume failed: %s", err) } + + // Remove the volume from tracking + ns.removeTrackedVolume(req.GetVolumeId()) + mount.CleanupMountPoint(req.GetTargetPath(), ns.mounter, false) return &csi.NodeUnpublishVolumeResponse{}, nil } @@ -344,6 +372,82 @@ func (*nodeServer) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolu return nil, status.Errorf(codes.Unimplemented, "method NodeExpandVolume not implemented") } +// Track mounted volume for automatic remounting +func (ns *nodeServer) trackMountedVolume(volumeId, targetPath, remote, remotePath, configData string, readOnly bool, parameters map[string]string, secretName, secretNamespace string) { + ns.mutex.Lock() + defer ns.mutex.Unlock() + + ns.mountedVolumes[volumeId] = &MountedVolume{ + VolumeId: volumeId, + TargetPath: targetPath, + Remote: remote, + RemotePath: remotePath, + ConfigData: configData, + ReadOnly: readOnly, + Parameters: parameters, + SecretName: secretName, + SecretNamespace: secretNamespace, + } + klog.Infof("Tracked mounted volume %s at path %s", volumeId, targetPath) + + if err := ns.persistState(); err != nil { + klog.Errorf("Failed to persist volume state: %v", err) + } +} + +// Remove tracked volume when unmounted +func (ns *nodeServer) removeTrackedVolume(volumeId string) { + ns.mutex.Lock() + defer ns.mutex.Unlock() + + delete(ns.mountedVolumes, volumeId) + klog.Infof("Removed tracked volume %s", volumeId) + + if err := ns.persistState(); err != nil { + klog.Errorf("Failed to persist volume state: %v", err) + } +} + +// Automatically remount all tracked volumes after daemon restart +func (ns *nodeServer) remountTrackedVolumes(ctx context.Context) error { + ns.mutex.RLock() + defer ns.mutex.RUnlock() + + if len(ns.mountedVolumes) == 0 { + klog.Info("No tracked volumes to remount") + return nil + } + + klog.Infof("Remounting %d tracked volumes", len(ns.mountedVolumes)) + + for volumeId, mv := range ns.mountedVolumes { + klog.Infof("Remounting volume %s to %s", volumeId, mv.TargetPath) + + // Create the mount directory if it doesn't exist + if err := os.MkdirAll(mv.TargetPath, 0750); err != nil { + klog.Errorf("Failed to create mount directory %s: %v", mv.TargetPath, err) + continue + } + + // Remount the volume + rcloneVol := &RcloneVolume{ + ID: mv.VolumeId, + Remote: mv.Remote, + RemotePath: mv.RemotePath, + } + + err := ns.RcloneOps.Mount(ctx, rcloneVol, mv.TargetPath, mv.ConfigData, mv.ReadOnly, mv.Parameters) + if err != nil { + klog.Errorf("Failed to remount volume %s: %v", volumeId, err) + // Don't return error here - continue with other volumes + } else { + klog.Infof("Successfully remounted volume %s", volumeId) + } + } + + return nil +} + func (ns *nodeServer) WaitForMountAvailable(mountpoint string) error { for { select { @@ -357,3 +461,57 @@ func (ns *nodeServer) WaitForMountAvailable(mountpoint string) error { } } } + +// Persist volume state to disk +func (ns *nodeServer) persistState() error { + ns.mutex.RLock() + defer ns.mutex.RUnlock() + + if ns.stateFile == "" { + return nil + } + + data, err := json.Marshal(ns.mountedVolumes) + if err != nil { + return fmt.Errorf("failed to marshal volume state: %v", err) + } + + if err := os.MkdirAll(filepath.Dir(ns.stateFile), 0755); err != nil { + return fmt.Errorf("failed to create state directory: %v", err) + } + + if err := os.WriteFile(ns.stateFile, data, 0600); err != nil { + return fmt.Errorf("failed to write state file: %v", err) + } + + klog.Infof("Persisted volume state to %s", ns.stateFile) + return nil +} + +// Load volume state from disk +func (ns *nodeServer) loadState() error { + ns.mutex.Lock() + defer ns.mutex.Unlock() + + if ns.stateFile == "" { + return nil + } + + data, err := os.ReadFile(ns.stateFile) + if err != nil { + if os.IsNotExist(err) { + klog.Info("No persisted volume state found, starting fresh") + return nil + } + return fmt.Errorf("failed to read state file: %v", err) + } + + var volumes map[string]*MountedVolume + if err := json.Unmarshal(data, &volumes); err != nil { + return fmt.Errorf("failed to unmarshal volume state: %v", err) + } + + ns.mountedVolumes = volumes + klog.Infof("Loaded %d tracked volumes from %s", len(ns.mountedVolumes), ns.stateFile) + return nil +} diff --git a/pkg/rclone/rclone.go b/pkg/rclone/rclone.go index 9b325084..264b2719 100644 --- a/pkg/rclone/rclone.go +++ b/pkg/rclone/rclone.go @@ -34,7 +34,7 @@ type Operations interface { Unmount(ctx context.Context, volumeId string, targetPath string) error GetVolumeById(ctx context.Context, volumeId string) (*RcloneVolume, error) Cleanup() error - Run() error + Run(onDaemonReady func() error) error } type Rclone struct { @@ -472,11 +472,16 @@ func (r *Rclone) start_daemon() error { return nil } -func (r *Rclone) Run() error { +func (r *Rclone) Run(onDaemonReady func() error) error { err := r.start_daemon() if err != nil { return err } + if onDaemonReady != nil { + if err := onDaemonReady(); err != nil { + klog.Warningf("Error in onDaemonReady callback: %v", err) + } + } // blocks until the rclone daemon is stopped return r.daemonCmd.Wait() } From 2a46ac7fa7a29bb65c94ac97020345e1d550480b Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Mon, 1 Dec 2025 05:47:40 +0000 Subject: [PATCH 02/26] fix: address comments and apply fixes (#77) * Ensure the mutex is not copied, even when the nodeServer is copied by storing a pointer to the mutex, instead of the mutex itself. * Use Mutex instead of RWMutex, as having two readers of the variable at the same time means we are going to write the state at the same time, corrupting the state file on storage. * Mutex / RWMutex are not recursive / re-entrant in Go, so in two cases we do not call `Unlock()` through `defer` as `persistState()` also takes the lock. * As a rule of thumb, Locking a Mutex should be as close as possible to the resource requiring it, to minimize the size of the critical section / the time spent holding the lock. * Remount each volume in a goroutine, with a rate limits of the number of active routine to prevent contention, and keep under control startup times. --- .devcontainer/rclone/install.sh | 3 + pkg/rclone/driver.go | 17 +++- pkg/rclone/nodeserver.go | 168 +++++++++++++++++++------------- 3 files changed, 116 insertions(+), 72 deletions(-) diff --git a/.devcontainer/rclone/install.sh b/.devcontainer/rclone/install.sh index c9a86242..a972f212 100644 --- a/.devcontainer/rclone/install.sh +++ b/.devcontainer/rclone/install.sh @@ -19,3 +19,6 @@ rm -rf /tmp/rclone # Fix the $GOPATH folder chown -R "${USERNAME}:golang" /go chmod -R g+r+w /go + +# Make sure the default folders exists +mkdir -p /var/lib/kubelet/plugins/csi-rclone/ \ No newline at end of file diff --git a/pkg/rclone/driver.go b/pkg/rclone/driver.go index e4f771ee..49423a78 100644 --- a/pkg/rclone/driver.go +++ b/pkg/rclone/driver.go @@ -1,9 +1,11 @@ package rclone import ( + "context" "fmt" "net" "os" + "path/filepath" "sync" "github.com/SwissDataScienceCenter/csi-rclone/pkg/kube" @@ -87,13 +89,22 @@ func NewNodeServer(csiDriver *csicommon.CSIDriver, cacheDir string, cacheSize st Interface: mount.New(""), Exec: utilexec.New(), }, - RcloneOps: rcloneOps, - mountedVolumes: make(map[string]*MountedVolume), + RcloneOps: rcloneOps, + mountedVolumes: make(map[string]MountedVolume), + mutex: &sync.Mutex{}, stateFile: stateFile, } + // Ensure the folder exists + if err = os.MkdirAll(filepath.Dir(ns.stateFile), 0755); err != nil { + return nil, fmt.Errorf("failed to create state directory: %v", err) + } + // Load persisted state on startup - if err := ns.loadState(); err != nil { + ns.mutex.Lock() + defer ns.mutex.Unlock() + + if ns.mountedVolumes, err = readVolumeMap(ns.stateFile); err != nil { klog.Warningf("Failed to load persisted volume state: %v", err) } diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index b31def14..0d26791a 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -11,7 +11,7 @@ import ( "errors" "fmt" "os" - "path/filepath" + "runtime" "strings" "sync" "time" @@ -42,20 +42,20 @@ type nodeServer struct { RcloneOps Operations // Track mounted volumes for automatic remounting - mountedVolumes map[string]*MountedVolume - mutex sync.RWMutex + mountedVolumes map[string]MountedVolume + mutex *sync.Mutex stateFile string } type MountedVolume struct { - VolumeId string - TargetPath string - Remote string - RemotePath string - ConfigData string - ReadOnly bool - Parameters map[string]string - SecretName string + VolumeId string + TargetPath string + Remote string + RemotePath string + ConfigData string + ReadOnly bool + Parameters map[string]string + SecretName string SecretNamespace string } @@ -377,20 +377,20 @@ func (ns *nodeServer) trackMountedVolume(volumeId, targetPath, remote, remotePat ns.mutex.Lock() defer ns.mutex.Unlock() - ns.mountedVolumes[volumeId] = &MountedVolume{ - VolumeId: volumeId, - TargetPath: targetPath, - Remote: remote, - RemotePath: remotePath, - ConfigData: configData, - ReadOnly: readOnly, - Parameters: parameters, - SecretName: secretName, + ns.mountedVolumes[volumeId] = MountedVolume{ + VolumeId: volumeId, + TargetPath: targetPath, + Remote: remote, + RemotePath: remotePath, + ConfigData: configData, + ReadOnly: readOnly, + Parameters: parameters, + SecretName: secretName, SecretNamespace: secretNamespace, } klog.Infof("Tracked mounted volume %s at path %s", volumeId, targetPath) - if err := ns.persistState(); err != nil { + if err := writeVolumeMap(ns.stateFile, ns.mountedVolumes); err != nil { klog.Errorf("Failed to persist volume state: %v", err) } } @@ -403,15 +403,20 @@ func (ns *nodeServer) removeTrackedVolume(volumeId string) { delete(ns.mountedVolumes, volumeId) klog.Infof("Removed tracked volume %s", volumeId) - if err := ns.persistState(); err != nil { + if err := writeVolumeMap(ns.stateFile, ns.mountedVolumes); err != nil { klog.Errorf("Failed to persist volume state: %v", err) } } // Automatically remount all tracked volumes after daemon restart func (ns *nodeServer) remountTrackedVolumes(ctx context.Context) error { - ns.mutex.RLock() - defer ns.mutex.RUnlock() + type mountResult struct { + volumeID string + err error + } + + ns.mutex.Lock() + defer ns.mutex.Unlock() if len(ns.mountedVolumes) == 0 { klog.Info("No tracked volumes to remount") @@ -420,32 +425,67 @@ func (ns *nodeServer) remountTrackedVolumes(ctx context.Context) error { klog.Infof("Remounting %d tracked volumes", len(ns.mountedVolumes)) + // Limit the number of active workers to the number of CPU threads (arbitrarily chosen) + limits := make(chan bool, runtime.GOMAXPROCS(0)) + defer close(limits) + + volumesCount := len(ns.mountedVolumes) + results := make(chan mountResult, volumesCount) + defer close(results) + + ctxWithTimeout, cancel := context.WithTimeout(ctx, 60*time.Second) + defer cancel() + for volumeId, mv := range ns.mountedVolumes { - klog.Infof("Remounting volume %s to %s", volumeId, mv.TargetPath) + go func() { + limits <- true // block until there is a free slot in the queue + defer func() { + <-limits // free a slot in the queue when we exit + }() - // Create the mount directory if it doesn't exist - if err := os.MkdirAll(mv.TargetPath, 0750); err != nil { - klog.Errorf("Failed to create mount directory %s: %v", mv.TargetPath, err) - continue - } + ctxWithMountTimeout, cancel := context.WithTimeout(ctxWithTimeout, 30*time.Second) + defer cancel() - // Remount the volume - rcloneVol := &RcloneVolume{ - ID: mv.VolumeId, - Remote: mv.Remote, - RemotePath: mv.RemotePath, - } + klog.Infof("Remounting volume %s to %s", volumeId, mv.TargetPath) - err := ns.RcloneOps.Mount(ctx, rcloneVol, mv.TargetPath, mv.ConfigData, mv.ReadOnly, mv.Parameters) - if err != nil { - klog.Errorf("Failed to remount volume %s: %v", volumeId, err) - // Don't return error here - continue with other volumes - } else { - klog.Infof("Successfully remounted volume %s", volumeId) - } + // Create the mount directory if it doesn't exist + var err error + if err = os.MkdirAll(mv.TargetPath, 0750); err != nil { + klog.Errorf("Failed to create mount directory %s: %v", mv.TargetPath, err) + } else { + // Remount the volume + rcloneVol := &RcloneVolume{ + ID: mv.VolumeId, + Remote: mv.Remote, + RemotePath: mv.RemotePath, + } + + err = ns.RcloneOps.Mount(ctxWithMountTimeout, rcloneVol, mv.TargetPath, mv.ConfigData, mv.ReadOnly, mv.Parameters) + } + + results <- mountResult{volumeId, err} + }() } - return nil + for { + select { + case result := <-results: + volumesCount-- + if result.err != nil { + klog.Errorf("Failed to remount volume %s: %v", result.volumeID, result.err) + // Don't return error here, continue with other volumes not to block all users because of a failed mount. + delete(ns.mountedVolumes, result.volumeID) + // Should we keep it on disk? This will be lost on the first new mount which will override the file. + } else { + klog.Infof("Successfully remounted volume %s", result.volumeID) + } + if volumesCount == 0 { + return nil + } + case <-ctxWithTimeout.Done(): + return ctxWithTimeout.Err() + } + } } func (ns *nodeServer) WaitForMountAvailable(mountpoint string) error { @@ -463,55 +503,45 @@ func (ns *nodeServer) WaitForMountAvailable(mountpoint string) error { } // Persist volume state to disk -func (ns *nodeServer) persistState() error { - ns.mutex.RLock() - defer ns.mutex.RUnlock() - - if ns.stateFile == "" { +func writeVolumeMap(filename string, volumes map[string]MountedVolume) error { + if filename == "" { return nil } - data, err := json.Marshal(ns.mountedVolumes) + data, err := json.Marshal(volumes) if err != nil { return fmt.Errorf("failed to marshal volume state: %v", err) } - if err := os.MkdirAll(filepath.Dir(ns.stateFile), 0755); err != nil { - return fmt.Errorf("failed to create state directory: %v", err) - } - - if err := os.WriteFile(ns.stateFile, data, 0600); err != nil { + if err := os.WriteFile(filename, data, 0600); err != nil { return fmt.Errorf("failed to write state file: %v", err) } - klog.Infof("Persisted volume state to %s", ns.stateFile) + klog.Infof("Persisted volume state to %s", filename) return nil } // Load volume state from disk -func (ns *nodeServer) loadState() error { - ns.mutex.Lock() - defer ns.mutex.Unlock() +func readVolumeMap(filename string) (map[string]MountedVolume, error) { + volumes := make(map[string]MountedVolume) - if ns.stateFile == "" { - return nil + if filename == "" { + return volumes, nil } - data, err := os.ReadFile(ns.stateFile) + data, err := os.ReadFile(filename) if err != nil { if os.IsNotExist(err) { klog.Info("No persisted volume state found, starting fresh") - return nil + return volumes, nil } - return fmt.Errorf("failed to read state file: %v", err) + return volumes, fmt.Errorf("failed to read state file: %v", err) } - var volumes map[string]*MountedVolume if err := json.Unmarshal(data, &volumes); err != nil { - return fmt.Errorf("failed to unmarshal volume state: %v", err) + return nil, fmt.Errorf("failed to unmarshal volume state: %v", err) } - ns.mountedVolumes = volumes - klog.Infof("Loaded %d tracked volumes from %s", len(ns.mountedVolumes), ns.stateFile) - return nil + klog.Infof("Loaded %d tracked volumes from %s", len(volumes), filename) + return volumes, nil } From 9023632f1df55cb9b84b5d43eb8adc7bb1c8daae Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Tue, 9 Dec 2025 11:05:41 +0100 Subject: [PATCH 03/26] sambuc/feat merge restart pr 2 (#78) * Refactor * CLI code * servers configurations * ControllerServer * NodeServer * Cleanup warnings in: * controllerserver.go * main.go * Use tags to specify json field names on permanent storage * Make DriverVersion a constant * Protect activeVolume while reading for the metrics * Compute len(ns.mountedVolumes) once --- cmd/csi-rclone-plugin/main.go | 109 +++-------------- pkg/rclone/controllerserver.go | 105 ++++++++++++---- pkg/rclone/driver.go | 183 +++++----------------------- pkg/rclone/nodeserver.go | 216 ++++++++++++++++++++++++++++----- test/sanity_test.go | 27 +++-- 5 files changed, 335 insertions(+), 305 deletions(-) diff --git a/cmd/csi-rclone-plugin/main.go b/cmd/csi-rclone-plugin/main.go index 2a238a7d..73160e11 100644 --- a/cmd/csi-rclone-plugin/main.go +++ b/cmd/csi-rclone-plugin/main.go @@ -13,22 +13,21 @@ import ( "github.com/SwissDataScienceCenter/csi-rclone/pkg/rclone" "github.com/spf13/cobra" "k8s.io/klog" - mountUtils "k8s.io/mount-utils" ) -var ( - endpoint string - nodeID string - cacheDir string - cacheSize string - meters []metrics.Observable -) +func exitOnError(err error) { + if err != nil { + klog.Error(err.Error()) + os.Exit(1) + } +} func init() { - flag.Set("logtostderr", "true") + exitOnError(flag.Set("logtostderr", "true")) } func main() { + var meters []metrics.Observable metricsServerConfig := metrics.ServerConfig{ Host: "localhost", Port: 9090, @@ -37,6 +36,8 @@ func main() { ShutdownTimeout: 5 * time.Second, Enabled: false, } + nodeServerConfig := rclone.NodeServerConfig{} + controllerServerConfig := rclone.ControllerServerConfig{} root := &cobra.Command{ Use: "rclone", @@ -48,34 +49,10 @@ func main() { Use: "run", Short: "Start the CSI driver.", } - root.AddCommand(runCmd) + exitOnError(nodeServerConfig.CommandLineParameters(runCmd, &meters)) + exitOnError(controllerServerConfig.CommandLineParameters(runCmd, &meters)) - runNode := &cobra.Command{ - Use: "node", - Short: "Start the CSI driver node service - expected to run in a daemonset on every node.", - Run: func(cmd *cobra.Command, args []string) { - handleNode() - }, - } - runNode.PersistentFlags().StringVar(&nodeID, "nodeid", "", "node id") - runNode.MarkPersistentFlagRequired("nodeid") - runNode.PersistentFlags().StringVar(&endpoint, "endpoint", "", "CSI endpoint") - runNode.MarkPersistentFlagRequired("endpoint") - runNode.PersistentFlags().StringVar(&cacheDir, "cachedir", "", "cache dir") - runNode.PersistentFlags().StringVar(&cacheSize, "cachesize", "", "cache size") - runCmd.AddCommand(runNode) - runController := &cobra.Command{ - Use: "controller", - Short: "Start the CSI driver controller.", - Run: func(cmd *cobra.Command, args []string) { - handleController() - }, - } - runController.PersistentFlags().StringVar(&nodeID, "nodeid", "", "node id") - runController.MarkPersistentFlagRequired("nodeid") - runController.PersistentFlags().StringVar(&endpoint, "endpoint", "", "CSI endpoint") - runController.MarkPersistentFlagRequired("endpoint") - runCmd.AddCommand(runController) + root.AddCommand(runCmd) versionCmd := &cobra.Command{ Use: "version", @@ -86,7 +63,7 @@ func main() { } root.AddCommand(versionCmd) - root.ParseFlags(os.Args[1:]) + exitOnError(root.ParseFlags(os.Args[1:])) if metricsServerConfig.Enabled { // Gracefully exit the metrics background servers @@ -97,63 +74,7 @@ func main() { go metricsServer.ListenAndServe() } - if err := root.Execute(); err != nil { - fmt.Fprintf(os.Stderr, "%s", err.Error()) - os.Exit(1) - } + exitOnError(root.Execute()) os.Exit(0) } - -func handleNode() { - err := unmountOldVols() - if err != nil { - klog.Warningf("There was an error when trying to unmount old volumes: %v", err) - } - d := rclone.NewDriver(nodeID, endpoint) - ns, err := rclone.NewNodeServer(d.CSIDriver, cacheDir, cacheSize) - if err != nil { - panic(err) - } - meters = append(meters, ns.Metrics()...) - d.WithNodeServer(ns) - err = d.Run() - if err != nil { - panic(err) - } -} - -func handleController() { - d := rclone.NewDriver(nodeID, endpoint) - cs := rclone.NewControllerServer(d.CSIDriver) - meters = append(meters, cs.Metrics()...) - d.WithControllerServer(cs) - err := d.Run() - if err != nil { - panic(err) - } -} - -// unmountOldVols is used to unmount volumes after a restart on a node -func unmountOldVols() error { - const mountType = "fuse.rclone" - const unmountTimeout = time.Second * 5 - klog.Info("Checking for existing mounts") - mounter := mountUtils.Mounter{} - mounts, err := mounter.List() - if err != nil { - return err - } - for _, mount := range mounts { - if mount.Type != mountType { - continue - } - err := mounter.UnmountWithForce(mount.Path, unmountTimeout) - if err != nil { - klog.Warningf("Failed to unmount %s because of %v.", mount.Path, err) - continue - } - klog.Infof("Sucessfully unmounted %s", mount.Path) - } - return nil -} diff --git a/pkg/rclone/controllerserver.go b/pkg/rclone/controllerserver.go index 4e00dd79..1ae3f06a 100644 --- a/pkg/rclone/controllerserver.go +++ b/pkg/rclone/controllerserver.go @@ -3,10 +3,13 @@ package rclone import ( + "context" "sync" + "github.com/SwissDataScienceCenter/csi-rclone/pkg/metrics" "github.com/container-storage-interface/spec/lib/go/csi" - "golang.org/x/net/context" + "github.com/prometheus/client_golang/prometheus" + "github.com/spf13/cobra" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "k8s.io/klog" @@ -16,13 +19,66 @@ import ( const secretAnnotationName = "csi-rclone.dev/secretName" -type controllerServer struct { +type ControllerServerConfig struct{ DriverConfig } + +type ControllerServer struct { *csicommon.DefaultControllerServer - active_volumes map[string]int64 - mutex sync.RWMutex + activeVolumes map[string]int64 + mutex *sync.RWMutex +} + +func NewControllerServer(csiDriver *csicommon.CSIDriver) *ControllerServer { + return &ControllerServer{ + DefaultControllerServer: csicommon.NewDefaultControllerServer(csiDriver), + activeVolumes: map[string]int64{}, + mutex: &sync.RWMutex{}, + } +} + +func (cs *ControllerServer) metrics() []metrics.Observable { + var meters []metrics.Observable + + meter := prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "csi_rclone_active_volume_count", + Help: "Number of active (Mounted) volumes.", + }) + meters = append(meters, + func() { + cs.mutex.RLock() + defer cs.mutex.RUnlock() + meter.Set(float64(len(cs.activeVolumes))) + }, + ) + prometheus.MustRegister(meter) + + return meters } -func (cs *controllerServer) ValidateVolumeCapabilities(ctx context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) { +func (config *ControllerServerConfig) CommandLineParameters(runCmd *cobra.Command, meters *[]metrics.Observable) error { + runController := &cobra.Command{ + Use: "controller", + Short: "Start the CSI driver controller.", + RunE: func(cmd *cobra.Command, args []string) error { + return Run(context.Background(), + &config.DriverConfig, + func(csiDriver *csicommon.CSIDriver) (csi.ControllerServer, csi.NodeServer, error) { + cs := NewControllerServer(csiDriver) + *meters = append(*meters, cs.metrics()...) + return cs, nil, nil + }, + func(_ context.Context) error { return nil }, + ) + }, + } + if err := config.DriverConfig.CommandLineParameters(runController); err != nil { + return err + } + + runCmd.AddCommand(runController) + return nil +} + +func (cs *ControllerServer) ValidateVolumeCapabilities(_ context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) { volId := req.GetVolumeId() if len(volId) == 0 { return nil, status.Error(codes.InvalidArgument, "ValidateVolumeCapabilities must be provided volume id") @@ -31,9 +87,9 @@ func (cs *controllerServer) ValidateVolumeCapabilities(ctx context.Context, req return nil, status.Error(codes.InvalidArgument, "ValidateVolumeCapabilities without capabilities") } - cs.mutex.Lock() - defer cs.mutex.Unlock() - if _, ok := cs.active_volumes[volId]; !ok { + cs.mutex.RLock() + defer cs.mutex.RUnlock() + if _, ok := cs.activeVolumes[volId]; !ok { return nil, status.Errorf(codes.NotFound, "Volume %s not found", volId) } return &csi.ValidateVolumeCapabilitiesResponse{ @@ -45,18 +101,18 @@ func (cs *controllerServer) ValidateVolumeCapabilities(ctx context.Context, req }, nil } -// Attaching Volume -func (cs *controllerServer) ControllerPublishVolume(ctx context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { +// ControllerPublishVolume Attaching Volume +func (cs *ControllerServer) ControllerPublishVolume(_ context.Context, _ *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ControllerPublishVolume not implemented") } -// Detaching Volume -func (cs *controllerServer) ControllerUnpublishVolume(ctx context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) { +// ControllerUnpublishVolume Detaching Volume +func (cs *ControllerServer) ControllerUnpublishVolume(_ context.Context, _ *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ControllerUnpublishVolume not implemented") } -// Provisioning Volumes -func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { +// CreateVolume Provisioning Volumes +func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { klog.Infof("ControllerCreateVolume: called with args %+v", *req) volumeName := req.GetName() if len(volumeName) == 0 { @@ -70,18 +126,18 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol // we don't use the size as it makes no sense for rclone. but csi drivers should succeed if // called twice with the same capacity for the same volume and fail if called twice with // differing capacity, so we need to remember it - volSizeBytes := int64(req.GetCapacityRange().GetRequiredBytes()) + volSizeBytes := req.GetCapacityRange().GetRequiredBytes() cs.mutex.Lock() defer cs.mutex.Unlock() - if val, ok := cs.active_volumes[volumeName]; ok && val != volSizeBytes { + if val, ok := cs.activeVolumes[volumeName]; ok && val != volSizeBytes { return nil, status.Errorf(codes.AlreadyExists, "Volume operation already exists for volume %s", volumeName) } - cs.active_volumes[volumeName] = volSizeBytes + cs.activeVolumes[volumeName] = volSizeBytes // See https://github.com/kubernetes-csi/external-provisioner/blob/v5.1.0/pkg/controller/controller.go#L75 // on how parameters from the persistent volume are parsed // We have to pass the secret name and namespace into the context so that the node server can use them - // The external provisioner uses the secret name and namespace but it does not pass them into the request, + // The external provisioner uses the secret name and namespace, but it does not pass them into the request, // so we read the PVC here to extract them ourselves because we may need them in the node server for decoding secrets. pvcName, pvcNameFound := req.Parameters["csi.storage.k8s.io/pvc/name"] pvcNamespace, pvcNamespaceFound := req.Parameters["csi.storage.k8s.io/pvc/namespace"] @@ -114,29 +170,28 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol } -// Delete Volume -func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) { +func (cs *ControllerServer) DeleteVolume(_ context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) { volId := req.GetVolumeId() if len(volId) == 0 { - return nil, status.Error(codes.InvalidArgument, "DeteleVolume must be provided volume id") + return nil, status.Error(codes.InvalidArgument, "DeleteVolume must be provided volume id") } cs.mutex.Lock() defer cs.mutex.Unlock() - delete(cs.active_volumes, volId) + delete(cs.activeVolumes, volId) return &csi.DeleteVolumeResponse{}, nil } -func (*controllerServer) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) { +func (*ControllerServer) ControllerExpandVolume(_ context.Context, _ *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ControllerExpandVolume not implemented") } -func (cs *controllerServer) ControllerGetVolume(ctx context.Context, req *csi.ControllerGetVolumeRequest) (*csi.ControllerGetVolumeResponse, error) { +func (cs *ControllerServer) ControllerGetVolume(_ context.Context, req *csi.ControllerGetVolumeRequest) (*csi.ControllerGetVolumeResponse, error) { return &csi.ControllerGetVolumeResponse{Volume: &csi.Volume{ VolumeId: req.VolumeId, }}, nil } -func (cs *controllerServer) ControllerModifyVolume(ctx context.Context, req *csi.ControllerModifyVolumeRequest) (*csi.ControllerModifyVolumeResponse, error) { +func (cs *ControllerServer) ControllerModifyVolume(_ context.Context, _ *csi.ControllerModifyVolumeRequest) (*csi.ControllerModifyVolumeResponse, error) { return &csi.ControllerModifyVolumeResponse{}, nil } diff --git a/pkg/rclone/driver.go b/pkg/rclone/driver.go index 49423a78..1fad5a5b 100644 --- a/pkg/rclone/driver.go +++ b/pkg/rclone/driver.go @@ -2,185 +2,68 @@ package rclone import ( "context" - "fmt" - "net" + "errors" "os" - "path/filepath" - "sync" - "github.com/SwissDataScienceCenter/csi-rclone/pkg/kube" - "github.com/SwissDataScienceCenter/csi-rclone/pkg/metrics" "github.com/container-storage-interface/spec/lib/go/csi" csicommon "github.com/kubernetes-csi/drivers/pkg/csi-common" - "github.com/prometheus/client_golang/prometheus" + "github.com/spf13/cobra" "k8s.io/klog" - "k8s.io/utils/mount" - - utilexec "k8s.io/utils/exec" ) -type Driver struct { - CSIDriver *csicommon.CSIDriver - endpoint string +const DriverVersion = "SwissDataScienceCenter" - ns *nodeServer - cs *controllerServer - cap []*csi.VolumeCapability_AccessMode - cscap []*csi.ControllerServiceCapability - server csicommon.NonBlockingGRPCServer -} +type DriverSetup func(csiDriver *csicommon.CSIDriver) (csi.ControllerServer, csi.NodeServer, error) -var ( - DriverVersion = "SwissDataScienceCenter" -) +type DriverServe func(ctx context.Context) error -func getFreePort() (port int, err error) { - var a *net.TCPAddr - if a, err = net.ResolveTCPAddr("tcp", "localhost:0"); err == nil { - var l *net.TCPListener - if l, err = net.ListenTCP("tcp", a); err == nil { - defer l.Close() - return l.Addr().(*net.TCPAddr).Port, nil - } +type DriverConfig struct { + Endpoint string + NodeID string +} + +func (config *DriverConfig) CommandLineParameters(runCmd *cobra.Command) error { + runCmd.PersistentFlags().StringVar(&config.NodeID, "nodeid", config.NodeID, "node id") + if err := runCmd.MarkPersistentFlagRequired("nodeid"); err != nil { + return err + } + runCmd.PersistentFlags().StringVar(&config.Endpoint, "endpoint", config.Endpoint, "CSI endpoint") + if err := runCmd.MarkPersistentFlagRequired("endpoint"); err != nil { + return err } - return + return nil } -func NewDriver(nodeID, endpoint string) *Driver { +func Run(ctx context.Context, config *DriverConfig, setup DriverSetup, serve DriverServe) error { driverName := os.Getenv("DRIVER_NAME") if driverName == "" { - panic("DriverName env var not set!") + return errors.New("DRIVER_NAME env variable not set or empty") } klog.Infof("Starting new %s RcloneDriver in version %s", driverName, DriverVersion) - d := &Driver{} - d.endpoint = endpoint - - d.CSIDriver = csicommon.NewCSIDriver(driverName, DriverVersion, nodeID) - d.CSIDriver.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{ + driver := csicommon.NewCSIDriver(driverName, DriverVersion, config.NodeID) + driver.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{ csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER, }) - d.CSIDriver.AddControllerServiceCapabilities( + driver.AddControllerServiceCapabilities( []csi.ControllerServiceCapability_RPC_Type{ csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, }) - return d -} - -func NewNodeServer(csiDriver *csicommon.CSIDriver, cacheDir string, cacheSize string) (*nodeServer, error) { - kubeClient, err := kube.GetK8sClient() - if err != nil { - return nil, err + is := csicommon.NewDefaultIdentityServer(driver) + cs, ns, setupErr := setup(driver) + if setupErr != nil { + return setupErr } - rclonePort, err := getFreePort() - if err != nil { - return nil, fmt.Errorf("Cannot get a free TCP port to run rclone") - } - rcloneOps := NewRclone(kubeClient, rclonePort, cacheDir, cacheSize) - - // Use kubelet plugin directory for state persistence - stateFile := "/var/lib/kubelet/plugins/csi-rclone/mounted_volumes.json" - - ns := &nodeServer{ - DefaultNodeServer: csicommon.NewDefaultNodeServer(csiDriver), - mounter: &mount.SafeFormatAndMount{ - Interface: mount.New(""), - Exec: utilexec.New(), - }, - RcloneOps: rcloneOps, - mountedVolumes: make(map[string]MountedVolume), - mutex: &sync.Mutex{}, - stateFile: stateFile, - } - - // Ensure the folder exists - if err = os.MkdirAll(filepath.Dir(ns.stateFile), 0755); err != nil { - return nil, fmt.Errorf("failed to create state directory: %v", err) - } - - // Load persisted state on startup - ns.mutex.Lock() - defer ns.mutex.Unlock() - - if ns.mountedVolumes, err = readVolumeMap(ns.stateFile); err != nil { - klog.Warningf("Failed to load persisted volume state: %v", err) - } - - return ns, nil -} + s := csicommon.NewNonBlockingGRPCServer() + defer s.Stop() + s.Start(config.Endpoint, is, cs, ns) -func NewControllerServer(csiDriver *csicommon.CSIDriver) *controllerServer { - return &controllerServer{ - DefaultControllerServer: csicommon.NewDefaultControllerServer(csiDriver), - active_volumes: map[string]int64{}, - mutex: sync.RWMutex{}, + if err := serve(ctx); err != nil { + return err } -} - -func (ns *nodeServer) Metrics() []metrics.Observable { - var meters []metrics.Observable - - // What should we meter? - - return meters -} - -func (cs *controllerServer) Metrics() []metrics.Observable { - var meters []metrics.Observable - - meter := prometheus.NewGauge(prometheus.GaugeOpts{ - Name: "csi_rclone_active_volume_count", - Help: "Number of active (Mounted) volumes.", - }) - meters = append(meters, - func() { meter.Set(float64(len(cs.active_volumes))) }, - ) - prometheus.MustRegister(meter) - - return meters -} -func (d *Driver) WithNodeServer(ns *nodeServer) *Driver { - d.ns = ns - return d -} - -func (d *Driver) WithControllerServer(cs *controllerServer) *Driver { - d.cs = cs - return d -} - -func (d *Driver) Run() error { - s := csicommon.NewNonBlockingGRPCServer() - s.Start( - d.endpoint, - csicommon.NewDefaultIdentityServer(d.CSIDriver), - d.cs, - d.ns, - ) - d.server = s - if d.ns != nil && d.ns.RcloneOps != nil { - onDaemonReady := func() error { - if d.ns != nil { - return d.ns.remountTrackedVolumes(context.Background()) - } - return nil - } - return d.ns.RcloneOps.Run(onDaemonReady) - } s.Wait() return nil } - -func (d *Driver) Stop() error { - var err error - if d.ns != nil && d.ns.RcloneOps != nil { - err = d.ns.RcloneOps.Cleanup() - } - if d.server != nil { - d.server.Stop() - } - return err -} diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index 0d26791a..46a6b6ad 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -10,24 +10,30 @@ import ( "encoding/json" "errors" "fmt" + "net" "os" + "path/filepath" "runtime" "strings" "sync" "time" - "gopkg.in/ini.v1" - v1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/klog" - "github.com/SwissDataScienceCenter/csi-rclone/pkg/kube" + "github.com/SwissDataScienceCenter/csi-rclone/pkg/metrics" "github.com/container-storage-interface/spec/lib/go/csi" "github.com/fernet/fernet-go" + "github.com/prometheus/client_golang/prometheus" + "github.com/spf13/cobra" "golang.org/x/net/context" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "gopkg.in/ini.v1" + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/klog" + mountutils "k8s.io/mount-utils" + "k8s.io/utils/exec" "k8s.io/utils/mount" csicommon "github.com/kubernetes-csi/drivers/pkg/csi-common" @@ -36,7 +42,7 @@ import ( const CSI_ANNOTATION_PREFIX = "csi-rclone.dev" const pvcSecretNameAnnotation = CSI_ANNOTATION_PREFIX + "/secretName" -type nodeServer struct { +type NodeServer struct { *csicommon.DefaultNodeServer mounter *mount.SafeFormatAndMount RcloneOps Operations @@ -47,29 +53,182 @@ type nodeServer struct { stateFile string } +// unmountOldVols is used to unmount volumes after a restart on a node +func unmountOldVols() error { + const mountType = "fuse.rclone" + const unmountTimeout = time.Second * 5 + klog.Info("Checking for existing mounts") + mounter := mountutils.Mounter{} + mounts, err := mounter.List() + if err != nil { + return err + } + for _, mount := range mounts { + if mount.Type != mountType { + continue + } + err := mounter.UnmountWithForce(mount.Path, unmountTimeout) + if err != nil { + klog.Warningf("Failed to unmount %s because of %v.", mount.Path, err) + continue + } + klog.Infof("Sucessfully unmounted %s", mount.Path) + } + return nil +} + +func getFreePort() (port int, err error) { + var a *net.TCPAddr + if a, err = net.ResolveTCPAddr("tcp", "localhost:0"); err == nil { + var l *net.TCPListener + if l, err = net.ListenTCP("tcp", a); err == nil { + defer l.Close() + return l.Addr().(*net.TCPAddr).Port, nil + } + } + return +} + +func NewNodeServer(csiDriver *csicommon.CSIDriver, cacheDir string, cacheSize string) (*NodeServer, error) { + err := unmountOldVols() + if err != nil { + klog.Warningf("There was an error when trying to unmount old volumes: %v", err) + return nil, err + } + + kubeClient, err := kube.GetK8sClient() + if err != nil { + return nil, err + } + + rclonePort, err := getFreePort() + if err != nil { + return nil, fmt.Errorf("Cannot get a free TCP port to run rclone") + } + + ns := &NodeServer{ + DefaultNodeServer: csicommon.NewDefaultNodeServer(csiDriver), + mounter: &mount.SafeFormatAndMount{ + Interface: mount.New(""), + Exec: exec.New(), + }, + RcloneOps: NewRclone(kubeClient, rclonePort, cacheDir, cacheSize), + mountedVolumes: make(map[string]MountedVolume), + mutex: &sync.Mutex{}, + // Use kubelet plugin directory for state persistence + stateFile: "/var/lib/kubelet/plugins/csi-rclone/mounted_volumes.json", + } + + // Ensure the folder exists + if err = os.MkdirAll(filepath.Dir(ns.stateFile), 0755); err != nil { + return nil, fmt.Errorf("failed to create state directory: %v", err) + } + + // Load persisted state on startup + ns.mutex.Lock() + defer ns.mutex.Unlock() + + if ns.mountedVolumes, err = readVolumeMap(ns.stateFile); err != nil { + klog.Warningf("Failed to load persisted volume state: %v", err) + } + + return ns, nil +} + +func (ns *NodeServer) Run(ctx context.Context) error { + defer ns.Stop() + return ns.RcloneOps.Run(func() error { + return ns.remountTrackedVolumes(ctx) + }) +} + +func (ns *NodeServer) metrics() []metrics.Observable { + var meters []metrics.Observable + + meter := prometheus.NewGauge(prometheus.GaugeOpts{ + Name: "csi_rclone_active_volume_count", + Help: "Number of active (Mounted) volumes.", + }) + meters = append(meters, + func() { meter.Set(float64(len(ns.mountedVolumes))) }, + ) + prometheus.MustRegister(meter) + + return meters +} + +func (ns *NodeServer) Stop() { + if err := ns.RcloneOps.Cleanup(); err != nil { + klog.Errorf("Failed to cleanup rclone ops: %v", err) + } +} + +type NodeServerConfig struct { + DriverConfig + CacheDir string + CacheSize string + ns *NodeServer +} + +func (config *NodeServerConfig) CommandLineParameters(runCmd *cobra.Command, meters *[]metrics.Observable) error { + runNode := &cobra.Command{ + Use: "node", + Short: "Start the CSI driver node service - expected to run in a daemonset on every node.", + RunE: func(cmd *cobra.Command, args []string) error { + return Run(context.Background(), &config.DriverConfig, + func(csiDriver *csicommon.CSIDriver) (csi.ControllerServer, csi.NodeServer, error) { + ns, err := NewNodeServer(csiDriver, config.CacheDir, config.CacheSize) + if err != nil { + return nil, nil, err + } + // We go through a temporary variable to ensure that config.ns is only set with a correct NodeServer. + config.ns = ns + *meters = append(*meters, config.ns.metrics()...) + return nil, config.ns, err + }, + func(ctx context.Context) error { + if config.ns == nil { + return errors.New("node server uninitialized") + } + return config.ns.Run(ctx) + }, + ) + }, + } + if err := config.DriverConfig.CommandLineParameters(runNode); err != nil { + return err + } + + runNode.PersistentFlags().StringVar(&config.CacheDir, "cachedir", config.CacheDir, "cache dir") + runNode.PersistentFlags().StringVar(&config.CacheSize, "cachesize", config.CacheSize, "cache size") + + runCmd.AddCommand(runNode) + return nil +} + type MountedVolume struct { - VolumeId string - TargetPath string - Remote string - RemotePath string - ConfigData string - ReadOnly bool - Parameters map[string]string - SecretName string - SecretNamespace string + VolumeId string `json:"volume_id"` + TargetPath string `json:"target_path"` + Remote string `json:"remote"` + RemotePath string `json:"remote_path"` + ConfigData string `json:"config_data"` + ReadOnly bool `json:"read_only"` + Parameters map[string]string `json:"parameters"` + SecretName string `json:"secret_name"` + SecretNamespace string `json:"secret_namespace"` } // Mounting Volume (Preparation) -func (ns *nodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { +func (ns *NodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method NodeStageVolume not implemented") } -func (ns *nodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) { +func (ns *NodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method NodeUnstageVolume not implemented") } // Mounting Volume (Actual Mounting) -func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { +func (ns *NodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { if err := validatePublishVolumeRequest(req); err != nil { return nil, err } @@ -327,7 +486,7 @@ func extractConfigData(parameters map[string]string) (string, map[string]string) } // Unmounting Volumes -func (ns *nodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) { +func (ns *NodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) { klog.Infof("NodeUnpublishVolume called with: %s", req) if err := validateUnPublishVolumeRequest(req); err != nil { return nil, err @@ -368,12 +527,12 @@ func validateUnPublishVolumeRequest(req *csi.NodeUnpublishVolumeRequest) error { } // Resizing Volume -func (*nodeServer) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolumeRequest) (*csi.NodeExpandVolumeResponse, error) { +func (*NodeServer) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolumeRequest) (*csi.NodeExpandVolumeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method NodeExpandVolume not implemented") } // Track mounted volume for automatic remounting -func (ns *nodeServer) trackMountedVolume(volumeId, targetPath, remote, remotePath, configData string, readOnly bool, parameters map[string]string, secretName, secretNamespace string) { +func (ns *NodeServer) trackMountedVolume(volumeId, targetPath, remote, remotePath, configData string, readOnly bool, parameters map[string]string, secretName, secretNamespace string) { ns.mutex.Lock() defer ns.mutex.Unlock() @@ -396,7 +555,7 @@ func (ns *nodeServer) trackMountedVolume(volumeId, targetPath, remote, remotePat } // Remove tracked volume when unmounted -func (ns *nodeServer) removeTrackedVolume(volumeId string) { +func (ns *NodeServer) removeTrackedVolume(volumeId string) { ns.mutex.Lock() defer ns.mutex.Unlock() @@ -409,7 +568,7 @@ func (ns *nodeServer) removeTrackedVolume(volumeId string) { } // Automatically remount all tracked volumes after daemon restart -func (ns *nodeServer) remountTrackedVolumes(ctx context.Context) error { +func (ns *NodeServer) remountTrackedVolumes(ctx context.Context) error { type mountResult struct { volumeID string err error @@ -418,18 +577,19 @@ func (ns *nodeServer) remountTrackedVolumes(ctx context.Context) error { ns.mutex.Lock() defer ns.mutex.Unlock() - if len(ns.mountedVolumes) == 0 { + volumesCount := len(ns.mountedVolumes) + + if volumesCount == 0 { klog.Info("No tracked volumes to remount") return nil } - klog.Infof("Remounting %d tracked volumes", len(ns.mountedVolumes)) + klog.Infof("Remounting %d tracked volumes", volumesCount) // Limit the number of active workers to the number of CPU threads (arbitrarily chosen) limits := make(chan bool, runtime.GOMAXPROCS(0)) defer close(limits) - volumesCount := len(ns.mountedVolumes) results := make(chan mountResult, volumesCount) defer close(results) @@ -488,7 +648,7 @@ func (ns *nodeServer) remountTrackedVolumes(ctx context.Context) error { } } -func (ns *nodeServer) WaitForMountAvailable(mountpoint string) error { +func (ns *NodeServer) WaitForMountAvailable(mountpoint string) error { for { select { case <-time.After(100 * time.Millisecond): diff --git a/test/sanity_test.go b/test/sanity_test.go index 0527cd41..6e400d97 100644 --- a/test/sanity_test.go +++ b/test/sanity_test.go @@ -9,9 +9,11 @@ import ( "github.com/SwissDataScienceCenter/csi-rclone/pkg/kube" "github.com/SwissDataScienceCenter/csi-rclone/pkg/rclone" + "github.com/container-storage-interface/spec/lib/go/csi" "github.com/google/uuid" "github.com/kubernetes-csi/csi-test/v5/pkg/sanity" "github.com/kubernetes-csi/csi-test/v5/utils" + csicommon "github.com/kubernetes-csi/drivers/pkg/csi-common" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "google.golang.org/grpc" @@ -45,25 +47,35 @@ func createSocketDir() (string, error) { var _ = Describe("Sanity CSI checks", Ordered, func() { var err error var kubeClient *kubernetes.Clientset = &kubernetes.Clientset{} + var nodeID string var endpoint string - var driver *rclone.Driver = &rclone.Driver{} var socketDir string BeforeAll(func() { socketDir, err = createSocketDir() Expect(err).ShouldNot(HaveOccurred()) + nodeID = "hostname" endpoint = fmt.Sprintf("unix://%s/csi.sock", socketDir) kubeClient, err = kube.GetK8sClient() Expect(err).ShouldNot(HaveOccurred()) os.Setenv("DRIVER_NAME", "csi-rclone") - driver = rclone.NewDriver("hostname", endpoint) - cs := rclone.NewControllerServer(driver.CSIDriver) - ns, err := rclone.NewNodeServer(driver.CSIDriver, "", "") - Expect(err).ShouldNot(HaveOccurred()) - driver.WithControllerServer(cs).WithNodeServer(ns) go func() { defer GinkgoRecover() - err := driver.Run() + var nsDoublePointer **rclone.NodeServer + err := rclone.Run(context.Background(), &nodeID, &endpoint, + func(csiDriver *csicommon.CSIDriver) (csi.ControllerServer, csi.NodeServer, error) { + cs := rclone.NewControllerServer(csiDriver) + ns, err := rclone.NewNodeServer(csiDriver, "", "") + if err != nil { + return nil, nil, err + } + nsDoublePointer = &ns + return cs, ns, nil + }, + func(ctx context.Context) error { + return (*nsDoublePointer).Run(ctx) + }, + ) Expect(err).ShouldNot(HaveOccurred()) }() _, err = utils.Connect(endpoint, grpc.WithTransportCredentials(insecure.NewCredentials())) @@ -71,7 +83,6 @@ var _ = Describe("Sanity CSI checks", Ordered, func() { }) AfterAll(func() { - driver.Stop() os.RemoveAll(socketDir) os.Unsetenv("DRIVER_NAME") }) From 29814fb52bbf39d72479fc174a69c11b7b92b9c9 Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Tue, 9 Dec 2025 13:36:30 +0100 Subject: [PATCH 04/26] fix tests after introduction of DriverConfig (#80) Adapt function prototypes --- pkg/rclone/controllerserver.go | 4 ++-- pkg/rclone/driver.go | 6 +++--- pkg/rclone/nodeserver.go | 15 ++++++--------- test/sanity_test.go | 23 +++++++++-------------- 4 files changed, 20 insertions(+), 28 deletions(-) diff --git a/pkg/rclone/controllerserver.go b/pkg/rclone/controllerserver.go index 1ae3f06a..4b39a848 100644 --- a/pkg/rclone/controllerserver.go +++ b/pkg/rclone/controllerserver.go @@ -61,12 +61,12 @@ func (config *ControllerServerConfig) CommandLineParameters(runCmd *cobra.Comman RunE: func(cmd *cobra.Command, args []string) error { return Run(context.Background(), &config.DriverConfig, - func(csiDriver *csicommon.CSIDriver) (csi.ControllerServer, csi.NodeServer, error) { + func(csiDriver *csicommon.CSIDriver) (*ControllerServer, *NodeServer, error) { cs := NewControllerServer(csiDriver) *meters = append(*meters, cs.metrics()...) return cs, nil, nil }, - func(_ context.Context) error { return nil }, + func(_ context.Context, cs *ControllerServer, ns *NodeServer) error { return nil }, ) }, } diff --git a/pkg/rclone/driver.go b/pkg/rclone/driver.go index 1fad5a5b..e0b7e53d 100644 --- a/pkg/rclone/driver.go +++ b/pkg/rclone/driver.go @@ -13,9 +13,9 @@ import ( const DriverVersion = "SwissDataScienceCenter" -type DriverSetup func(csiDriver *csicommon.CSIDriver) (csi.ControllerServer, csi.NodeServer, error) +type DriverSetup func(csiDriver *csicommon.CSIDriver) (*ControllerServer, *NodeServer, error) -type DriverServe func(ctx context.Context) error +type DriverServe func(ctx context.Context, cs *ControllerServer, ns *NodeServer) error type DriverConfig struct { Endpoint string @@ -60,7 +60,7 @@ func Run(ctx context.Context, config *DriverConfig, setup DriverSetup, serve Dri defer s.Stop() s.Start(config.Endpoint, is, cs, ns) - if err := serve(ctx); err != nil { + if err := serve(ctx, cs, ns); err != nil { return err } diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index 46a6b6ad..620b1292 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -167,7 +167,6 @@ type NodeServerConfig struct { DriverConfig CacheDir string CacheSize string - ns *NodeServer } func (config *NodeServerConfig) CommandLineParameters(runCmd *cobra.Command, meters *[]metrics.Observable) error { @@ -176,21 +175,19 @@ func (config *NodeServerConfig) CommandLineParameters(runCmd *cobra.Command, met Short: "Start the CSI driver node service - expected to run in a daemonset on every node.", RunE: func(cmd *cobra.Command, args []string) error { return Run(context.Background(), &config.DriverConfig, - func(csiDriver *csicommon.CSIDriver) (csi.ControllerServer, csi.NodeServer, error) { + func(csiDriver *csicommon.CSIDriver) (*ControllerServer, *NodeServer, error) { ns, err := NewNodeServer(csiDriver, config.CacheDir, config.CacheSize) if err != nil { return nil, nil, err } - // We go through a temporary variable to ensure that config.ns is only set with a correct NodeServer. - config.ns = ns - *meters = append(*meters, config.ns.metrics()...) - return nil, config.ns, err + *meters = append(*meters, ns.metrics()...) + return nil, ns, err }, - func(ctx context.Context) error { - if config.ns == nil { + func(ctx context.Context, cs *ControllerServer, ns *NodeServer) error { + if ns == nil { return errors.New("node server uninitialized") } - return config.ns.Run(ctx) + return ns.Run(ctx) }, ) }, diff --git a/test/sanity_test.go b/test/sanity_test.go index 6e400d97..3a1044de 100644 --- a/test/sanity_test.go +++ b/test/sanity_test.go @@ -9,7 +9,6 @@ import ( "github.com/SwissDataScienceCenter/csi-rclone/pkg/kube" "github.com/SwissDataScienceCenter/csi-rclone/pkg/rclone" - "github.com/container-storage-interface/spec/lib/go/csi" "github.com/google/uuid" "github.com/kubernetes-csi/csi-test/v5/pkg/sanity" "github.com/kubernetes-csi/csi-test/v5/utils" @@ -47,33 +46,29 @@ func createSocketDir() (string, error) { var _ = Describe("Sanity CSI checks", Ordered, func() { var err error var kubeClient *kubernetes.Clientset = &kubernetes.Clientset{} - var nodeID string var endpoint string var socketDir string BeforeAll(func() { socketDir, err = createSocketDir() Expect(err).ShouldNot(HaveOccurred()) - nodeID = "hostname" endpoint = fmt.Sprintf("unix://%s/csi.sock", socketDir) + config := rclone.NodeServerConfig{DriverConfig: rclone.DriverConfig{Endpoint: endpoint, NodeID: "hostname"}} kubeClient, err = kube.GetK8sClient() Expect(err).ShouldNot(HaveOccurred()) os.Setenv("DRIVER_NAME", "csi-rclone") go func() { defer GinkgoRecover() - var nsDoublePointer **rclone.NodeServer - err := rclone.Run(context.Background(), &nodeID, &endpoint, - func(csiDriver *csicommon.CSIDriver) (csi.ControllerServer, csi.NodeServer, error) { + err := rclone.Run(context.Background(), &config.DriverConfig, + func(csiDriver *csicommon.CSIDriver) (*rclone.ControllerServer, *rclone.NodeServer, error) { cs := rclone.NewControllerServer(csiDriver) - ns, err := rclone.NewNodeServer(csiDriver, "", "") - if err != nil { - return nil, nil, err - } - nsDoublePointer = &ns - return cs, ns, nil + ns, err := rclone.NewNodeServer(csiDriver, config.CacheDir, config.CacheSize) + Expect(err).ShouldNot(HaveOccurred()) + return cs, ns, err }, - func(ctx context.Context) error { - return (*nsDoublePointer).Run(ctx) + func(ctx context.Context, cs *rclone.ControllerServer, ns *rclone.NodeServer) error { + Expect(ns).ShouldNot(BeNil()) + return ns.Run(ctx) }, ) Expect(err).ShouldNot(HaveOccurred()) From 9292bc42a9f952aa284eecea0e4889b607c13562 Mon Sep 17 00:00:00 2001 From: Tasko Olevski Date: Tue, 9 Dec 2025 14:41:35 +0100 Subject: [PATCH 05/26] fix: propagate context more thoroughly (#79) --- cmd/csi-rclone-plugin/main.go | 4 +-- pkg/common/constants.go | 11 +++++++ pkg/rclone/controllerserver.go | 3 +- pkg/rclone/nodeserver.go | 5 ++-- pkg/rclone/rclone.go | 52 +++++++++++++++++++++++++--------- test/sanity_test.go | 2 +- 6 files changed, 58 insertions(+), 19 deletions(-) create mode 100644 pkg/common/constants.go diff --git a/cmd/csi-rclone-plugin/main.go b/cmd/csi-rclone-plugin/main.go index 73160e11..854bb964 100644 --- a/cmd/csi-rclone-plugin/main.go +++ b/cmd/csi-rclone-plugin/main.go @@ -6,9 +6,9 @@ import ( "fmt" "os" "os/signal" - "syscall" "time" + "github.com/SwissDataScienceCenter/csi-rclone/pkg/common" "github.com/SwissDataScienceCenter/csi-rclone/pkg/metrics" "github.com/SwissDataScienceCenter/csi-rclone/pkg/rclone" "github.com/spf13/cobra" @@ -67,7 +67,7 @@ func main() { if metricsServerConfig.Enabled { // Gracefully exit the metrics background servers - ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT) + ctx, stop := signal.NotifyContext(context.Background(), common.InterruptSignals...) defer stop() metricsServer := metricsServerConfig.NewServer(ctx, &meters) diff --git a/pkg/common/constants.go b/pkg/common/constants.go new file mode 100644 index 00000000..a591954e --- /dev/null +++ b/pkg/common/constants.go @@ -0,0 +1,11 @@ +package common + +import ( + "os" + "syscall" +) + +// Signals to listen to: +// 1. os.Interrup -> allows devs to easily run a server locally +// 2. syscall.SIGTERM -> sent by kubernetes when stopping a server gracefully +var InterruptSignals = []os.Signal{os.Interrupt, syscall.SIGTERM} diff --git a/pkg/rclone/controllerserver.go b/pkg/rclone/controllerserver.go index 4b39a848..152011f8 100644 --- a/pkg/rclone/controllerserver.go +++ b/pkg/rclone/controllerserver.go @@ -59,7 +59,8 @@ func (config *ControllerServerConfig) CommandLineParameters(runCmd *cobra.Comman Use: "controller", Short: "Start the CSI driver controller.", RunE: func(cmd *cobra.Command, args []string) error { - return Run(context.Background(), + ctx := cmd.Context() + return Run(ctx, &config.DriverConfig, func(csiDriver *csicommon.CSIDriver) (*ControllerServer, *NodeServer, error) { cs := NewControllerServer(csiDriver) diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index 620b1292..615e22f0 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -137,7 +137,7 @@ func NewNodeServer(csiDriver *csicommon.CSIDriver, cacheDir string, cacheSize st func (ns *NodeServer) Run(ctx context.Context) error { defer ns.Stop() - return ns.RcloneOps.Run(func() error { + return ns.RcloneOps.Run(ctx, func() error { return ns.remountTrackedVolumes(ctx) }) } @@ -174,7 +174,8 @@ func (config *NodeServerConfig) CommandLineParameters(runCmd *cobra.Command, met Use: "node", Short: "Start the CSI driver node service - expected to run in a daemonset on every node.", RunE: func(cmd *cobra.Command, args []string) error { - return Run(context.Background(), &config.DriverConfig, + ctx := cmd.Context() + return Run(ctx, &config.DriverConfig, func(csiDriver *csicommon.CSIDriver) (*ControllerServer, *NodeServer, error) { ns, err := NewNodeServer(csiDriver, config.CacheDir, config.CacheSize) if err != nil { diff --git a/pkg/rclone/rclone.go b/pkg/rclone/rclone.go index 264b2719..12617e4c 100644 --- a/pkg/rclone/rclone.go +++ b/pkg/rclone/rclone.go @@ -34,7 +34,7 @@ type Operations interface { Unmount(ctx context.Context, volumeId string, targetPath string) error GetVolumeById(ctx context.Context, volumeId string) (*RcloneVolume, error) Cleanup() error - Run(onDaemonReady func() error) error + Run(ctx context.Context, onDaemonReady func() error) error } type Rclone struct { @@ -160,7 +160,11 @@ func (r *Rclone) Mount(ctx context.Context, rcloneVolume *RcloneVolume, targetPa return fmt.Errorf("mounting failed: couldn't create request body: %s", err) } requestBody := bytes.NewBuffer(postBody) - resp, err := http.Post(fmt.Sprintf("http://localhost:%d/config/create", r.port), "application/json", requestBody) + req, err := createRcloneRequest(ctx, http.MethodPost, requestBody, "/config/create", r.port) + if err != nil { + return fmt.Errorf("mounting failed: cannot create a request for rclone config creation: %w", err) + } + resp, err := http.DefaultClient.Do(req) if err != nil { return fmt.Errorf("mounting failed: couldn't send HTTP request to create config: %w", err) } @@ -218,7 +222,11 @@ func (r *Rclone) Mount(ctx context.Context, rcloneVolume *RcloneVolume, targetPa } klog.Infof("executing mount command args=%s", string(postBody)) requestBody = bytes.NewBuffer(postBody) - resp, err = http.Post(fmt.Sprintf("http://localhost:%d/mount/mount", r.port), "application/json", requestBody) + req, err = createRcloneRequest(ctx, http.MethodPost, requestBody, "/mount/mount", r.port) + if err != nil { + return fmt.Errorf("mounting failed: cannot create a request for rclone mounting: %w", err) + } + resp, err = http.DefaultClient.Do(req) if err != nil { return fmt.Errorf("mounting failed: couldn't send HTTP request to create mount: %w", err) } @@ -249,7 +257,7 @@ func (r *Rclone) CreateVol(ctx context.Context, volumeName, remote, remotePath, } flags["config"] = rcloneConfigPath - return r.command("mkdir", remote, path, flags) + return r.command(ctx, "mkdir", remote, path, flags) } func (r Rclone) DeleteVol(ctx context.Context, rcloneVolume *RcloneVolume, rcloneConfigPath string, parameters map[string]string) error { @@ -258,7 +266,7 @@ func (r Rclone) DeleteVol(ctx context.Context, rcloneVolume *RcloneVolume, rclon flags[key] = value } flags["config"] = rcloneConfigPath - return r.command("purge", rcloneVolume.Remote, rcloneVolume.RemotePath, flags) + return r.command(ctx, "purge", rcloneVolume.Remote, rcloneVolume.RemotePath, flags) } func (r Rclone) Unmount(ctx context.Context, volumeId string, targetPath string) error { @@ -273,7 +281,11 @@ func (r Rclone) Unmount(ctx context.Context, volumeId string, targetPath string) return fmt.Errorf("unmounting failed: couldn't create request body: %s", err) } requestBody := bytes.NewBuffer(postBody) - resp, err := http.Post(fmt.Sprintf("http://localhost:%d/mount/unmount", r.port), "application/json", requestBody) + req, err := createRcloneRequest(ctx, http.MethodPost, requestBody, "/mount/unmount", r.port) + if err != nil { + return fmt.Errorf("unmounting failed: couldn't create a request for rclone: %w", err) + } + resp, err := http.DefaultClient.Do(req) if err != nil { return fmt.Errorf("unmounting failed: couldn't send HTTP request: %w", err) } @@ -291,7 +303,11 @@ func (r Rclone) Unmount(ctx context.Context, volumeId string, targetPath string) return fmt.Errorf("deleting config failed: couldn't create request body: %s", err) } requestBody = bytes.NewBuffer(postBody) - resp, err = http.Post(fmt.Sprintf("http://localhost:%d/config/delete", r.port), "application/json", requestBody) + req, err = createRcloneRequest(ctx, http.MethodPost, requestBody, "/config/delete", r.port) + if err != nil { + return fmt.Errorf("unmounting failed: couldn't create a request for rclone configuration deletion: %w", err) + } + resp, err = http.DefaultClient.Do(req) if err != nil { klog.Errorf("deleting config failed: couldn't send HTTP request: %v", err) return nil @@ -409,7 +425,7 @@ func checkResponse(resp *http.Response) error { return fmt.Errorf("received error from the rclone server: %s", result.String()) } -func (r *Rclone) start_daemon() error { +func (r *Rclone) start_daemon(ctx context.Context) error { f, err := os.CreateTemp("", "rclone.conf") if err != nil { return err @@ -449,7 +465,7 @@ func (r *Rclone) start_daemon() error { klog.Infof("running rclone remote control daemon cmd=%s, args=%s", rclone_cmd, rclone_args) env := os.Environ() - cmd := os_exec.Command(rclone_cmd, rclone_args...) + cmd := os_exec.CommandContext(ctx, rclone_cmd, rclone_args...) cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} stdout, err := cmd.StdoutPipe() cmd.Stderr = cmd.Stdout @@ -472,8 +488,8 @@ func (r *Rclone) start_daemon() error { return nil } -func (r *Rclone) Run(onDaemonReady func() error) error { - err := r.start_daemon() +func (r *Rclone) Run(ctx context.Context, onDaemonReady func() error) error { + err := r.start_daemon(ctx) if err != nil { return err } @@ -494,7 +510,7 @@ func (r *Rclone) Cleanup() error { return r.daemonCmd.Process.Kill() } -func (r *Rclone) command(cmd, remote, remotePath string, flags map[string]string) error { +func (r *Rclone) command(ctx context.Context, cmd, remote, remotePath string, flags map[string]string) error { // rclone remote:path [flag] args := append( []string{}, @@ -508,7 +524,7 @@ func (r *Rclone) command(cmd, remote, remotePath string, flags map[string]string } klog.Infof("executing %s command cmd=rclone, remote=%s:%s", cmd, remote, remotePath) - out, err := r.execute.Command("rclone", args...).CombinedOutput() + out, err := r.execute.CommandContext(ctx, "rclone", args...).CombinedOutput() if err != nil { return fmt.Errorf("%s failed: %v cmd: 'rclone' remote: '%s' remotePath:'%s' args:'%s' output: %q", cmd, err, remote, remotePath, args, string(out)) @@ -516,3 +532,13 @@ func (r *Rclone) command(cmd, remote, remotePath string, flags map[string]string return nil } + +func createRcloneRequest(ctx context.Context, method string, body io.Reader, path string, rcloneServerPort int) (*http.Request, error) { + rcloneServerURL := fmt.Sprintf("http://localhost:%d/%s", rcloneServerPort, strings.TrimLeft(path, "/")) + req, err := http.NewRequestWithContext(ctx, method, rcloneServerURL, body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + return req, nil +} diff --git a/test/sanity_test.go b/test/sanity_test.go index 3a1044de..a74beb6a 100644 --- a/test/sanity_test.go +++ b/test/sanity_test.go @@ -49,7 +49,7 @@ var _ = Describe("Sanity CSI checks", Ordered, func() { var endpoint string var socketDir string - BeforeAll(func() { + BeforeAll(func(ctx SpecContext) { socketDir, err = createSocketDir() Expect(err).ShouldNot(HaveOccurred()) endpoint = fmt.Sprintf("unix://%s/csi.sock", socketDir) From 2675531ed98efaa2f1429af94898509b9f719bbc Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Fri, 12 Dec 2025 14:03:13 +0100 Subject: [PATCH 06/26] fix: the error handling was creating issues ignored previously (#81) --- cmd/csi-rclone-plugin/main.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cmd/csi-rclone-plugin/main.go b/cmd/csi-rclone-plugin/main.go index 854bb964..7f2b37fb 100644 --- a/cmd/csi-rclone-plugin/main.go +++ b/cmd/csi-rclone-plugin/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "errors" "flag" "fmt" "os" @@ -12,11 +13,13 @@ import ( "github.com/SwissDataScienceCenter/csi-rclone/pkg/metrics" "github.com/SwissDataScienceCenter/csi-rclone/pkg/rclone" "github.com/spf13/cobra" + "github.com/spf13/pflag" "k8s.io/klog" ) func exitOnError(err error) { - if err != nil { + // ParseFlags uses errors to return some status information, ignore it here. + if err != nil && !errors.Is(err, pflag.ErrHelp) { klog.Error(err.Error()) os.Exit(1) } @@ -43,6 +46,10 @@ func main() { Use: "rclone", Short: "CSI based rclone driver", } + // Allow flags to be defined in subcommands, they will be reported at the Execute() step, with the help printed + // before exiting. + root.FParseErrWhitelist.UnknownFlags = true + metricsServerConfig.CommandLineParameters(root) runCmd := &cobra.Command{ @@ -58,7 +65,7 @@ func main() { Use: "version", Short: "Prints information about this version of csi rclone plugin", Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("csi-rclone plugin Version: %s", rclone.DriverVersion) + fmt.Printf("csi-rclone plugin Version: %s\n", rclone.DriverVersion) }, } root.AddCommand(versionCmd) From 6372a5df32b95c8f164c366fcca49e6310aaab4e Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Mon, 15 Dec 2025 15:42:44 +0100 Subject: [PATCH 07/26] fix: Use node tmp folder for the mounts recovery state (#82) - Use a folder name on the host which contains the deployment, to prevent conflicts in case of multiple deployment on the same host. - Cleaned up a bit the templates to make it easier to compare. - Use a folder under /tmp so that state is cleaned on node reboot, but kept between pod/container restarts --------- Co-authored-by: Tasko Olevski --- .devcontainer/rclone/install.sh | 2 +- .../templates/csi-controller-rclone.yaml | 8 +++---- .../templates/csi-nodeplugin-rclone.yaml | 21 ++++++++++++------- pkg/rclone/nodeserver.go | 3 +-- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/.devcontainer/rclone/install.sh b/.devcontainer/rclone/install.sh index a972f212..8628ce9c 100644 --- a/.devcontainer/rclone/install.sh +++ b/.devcontainer/rclone/install.sh @@ -21,4 +21,4 @@ chown -R "${USERNAME}:golang" /go chmod -R g+r+w /go # Make sure the default folders exists -mkdir -p /var/lib/kubelet/plugins/csi-rclone/ \ No newline at end of file +mkdir -p /run/csi-rclone \ No newline at end of file diff --git a/deploy/csi-rclone/templates/csi-controller-rclone.yaml b/deploy/csi-rclone/templates/csi-controller-rclone.yaml index dd13c437..4f65cd82 100644 --- a/deploy/csi-rclone/templates/csi-controller-rclone.yaml +++ b/deploy/csi-rclone/templates/csi-controller-rclone.yaml @@ -54,8 +54,8 @@ spec: image: {{ .Values.csiControllerRclone.csiProvisioner.image.repository }}:{{ .Values.csiControllerRclone.csiProvisioner.image.tag | default .Chart.AppVersion }} imagePullPolicy: {{ .Values.csiControllerRclone.csiProvisioner.imagePullPolicy }} volumeMounts: - - name: socket-dir - mountPath: /csi + - mountPath: /csi + name: socket-dir - name: rclone args: - run @@ -85,7 +85,7 @@ spec: fieldRef: fieldPath: spec.nodeName - name: CSI_ENDPOINT - value: "unix://plugin/csi.sock" + value: "unix://csi/csi.sock" - name: KUBERNETES_CLUSTER_DOMAIN value: {{ quote .Values.kubernetesClusterDomain }} {{- if .Values.csiControllerRclone.rclone.goMemLimit }} @@ -114,7 +114,7 @@ spec: timeoutSeconds: 3 periodSeconds: 2 volumeMounts: - - mountPath: /plugin + - mountPath: /csi name: socket-dir - name: liveness-probe imagePullPolicy: Always diff --git a/deploy/csi-rclone/templates/csi-nodeplugin-rclone.yaml b/deploy/csi-rclone/templates/csi-nodeplugin-rclone.yaml index 670badc5..be27eb09 100644 --- a/deploy/csi-rclone/templates/csi-nodeplugin-rclone.yaml +++ b/deploy/csi-rclone/templates/csi-nodeplugin-rclone.yaml @@ -22,7 +22,7 @@ spec: - name: node-driver-registrar args: - --v=5 - - --csi-address=/plugin/csi.sock + - --csi-address=/csi/csi.sock - --kubelet-registration-path=/var/lib/kubelet/plugins/{{ .Values.storageClassName }}/csi.sock env: - name: KUBE_NODE_NAME @@ -45,7 +45,7 @@ spec: resources: {{- toYaml .Values.csiNodepluginRclone.rclone.resources | nindent 12 }} volumeMounts: - - mountPath: /plugin + - mountPath: /csi name: plugin-dir - mountPath: /registration name: registration-dir @@ -53,9 +53,9 @@ spec: imagePullPolicy: Always image: registry.k8s.io/sig-storage/livenessprobe:v2.15.0 args: - - --csi-address=/plugin/csi.sock + - --csi-address=/csi/csi.sock volumeMounts: - - mountPath: /plugin + - mountPath: /csi name: plugin-dir - name: rclone args: @@ -86,7 +86,7 @@ spec: fieldRef: fieldPath: spec.nodeName - name: CSI_ENDPOINT - value: "unix://plugin/csi.sock" + value: "unix://csi/csi.sock" - name: KUBERNETES_CLUSTER_DOMAIN value: {{ quote .Values.kubernetesClusterDomain }} - name: DRIVER_NAME @@ -134,8 +134,10 @@ spec: timeoutSeconds: 10 periodSeconds: 30 volumeMounts: - - mountPath: /plugin + - mountPath: /csi name: plugin-dir + - mountPath: /run/csi-rclone + name: node-temp-dir - mountPath: /var/lib/kubelet/pods mountPropagation: Bidirectional name: pods-mount-dir @@ -154,6 +156,11 @@ spec: {{ toYaml . | nindent 8 }} {{- end }} volumes: + - hostPath: + # NOTE: We mount on /tmp because we want the saved configuration to not survive a whole node restart. + path: /tmp/{{.Release.Namespace}}-{{.Release.Name}}-{{.Release.Revision}} + type: DirectoryOrCreate + name: node-temp-dir - hostPath: path: {{ .Values.kubeletDir }}/plugins/{{ .Values.storageClassName }} type: DirectoryOrCreate @@ -167,4 +174,4 @@ spec: type: DirectoryOrCreate name: registration-dir - name: cache-dir - emptyDir: + emptyDir: {} diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index 615e22f0..830f232e 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -115,8 +115,7 @@ func NewNodeServer(csiDriver *csicommon.CSIDriver, cacheDir string, cacheSize st RcloneOps: NewRclone(kubeClient, rclonePort, cacheDir, cacheSize), mountedVolumes: make(map[string]MountedVolume), mutex: &sync.Mutex{}, - // Use kubelet plugin directory for state persistence - stateFile: "/var/lib/kubelet/plugins/csi-rclone/mounted_volumes.json", + stateFile: "/run/csi-rclone/mounted_volumes.json", } // Ensure the folder exists From f2a8e20c838f30589de08d668faadf3a255bdc11 Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Tue, 16 Dec 2025 11:33:29 +0100 Subject: [PATCH 08/26] fix: Wait for the deamon to be ready (#83) --- pkg/rclone/rclone.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/pkg/rclone/rclone.go b/pkg/rclone/rclone.go index 12617e4c..31a35a58 100644 --- a/pkg/rclone/rclone.go +++ b/pkg/rclone/rclone.go @@ -11,6 +11,7 @@ import ( "os" os_exec "os/exec" "syscall" + "time" "strings" @@ -425,6 +426,25 @@ func checkResponse(resp *http.Response) error { return fmt.Errorf("received error from the rclone server: %s", result.String()) } +func waitForDaemon(ctx context.Context, port int) error { + // Wait for the daemon to have started + ctxWaitRcloneStart, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + + req, err := createRcloneRequest(ctxWaitRcloneStart, http.MethodPost, nil, "/core/version", port) + if err != nil { + return err + } + + for { + _, err = http.DefaultClient.Do(req) + // Keep trying until we retrieve a response, or we hit the deadline + if err == nil || errors.Is(err, context.DeadlineExceeded) { + return err + } + } +} + func (r *Rclone) start_daemon(ctx context.Context) error { f, err := os.CreateTemp("", "rclone.conf") if err != nil { @@ -477,6 +497,11 @@ func (r *Rclone) start_daemon(ctx context.Context) error { if err := cmd.Start(); err != nil { return err } + + if err := waitForDaemon(ctx, r.port); err != nil { + return err + } + go func() { output := "" for scanner.Scan() { From b3df4a43b3305b73a185eff6dafd98765729f57b Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Wed, 17 Dec 2025 14:41:21 +0100 Subject: [PATCH 09/26] fix: empty json body & memory unit in yaml (#85) --- deploy/csi-rclone/values.yaml | 6 +++--- pkg/rclone/rclone.go | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/deploy/csi-rclone/values.yaml b/deploy/csi-rclone/values.yaml index 75606c1e..15db99f4 100644 --- a/deploy/csi-rclone/values.yaml +++ b/deploy/csi-rclone/values.yaml @@ -22,10 +22,10 @@ csiControllerRclone: # memory: 128Mi # requests: # cpu: 100m - # memory: 128M + # memory: 128Mi # If set, used to set GOMEMLIMIT, it should be strictly lower than # limits.memory to prevent OOMkills - goMemLimit: # 115Mi + goMemLimit: # 115MiB # Prometheus metrics metrics: enabled: true @@ -68,7 +68,7 @@ csiNodepluginRclone: # memory: 128Mi # If set, used to set GOMEMLIMIT, it should be strictly lower than # limits.memory to prevent OOMkills - goMemLimit: # 115Mi + goMemLimit: # 115MiB # Prometheus metrics metrics: enabled: true diff --git a/pkg/rclone/rclone.go b/pkg/rclone/rclone.go index 31a35a58..4fe15120 100644 --- a/pkg/rclone/rclone.go +++ b/pkg/rclone/rclone.go @@ -430,8 +430,7 @@ func waitForDaemon(ctx context.Context, port int) error { // Wait for the daemon to have started ctxWaitRcloneStart, cancel := context.WithTimeout(ctx, 1*time.Second) defer cancel() - - req, err := createRcloneRequest(ctxWaitRcloneStart, http.MethodPost, nil, "/core/version", port) + req, err := createRcloneRequest(ctxWaitRcloneStart, http.MethodPost, bytes.NewBufferString("{}"), "/core/version", port) if err != nil { return err } From 1587f6d54702288b3c25655a30a7d5feab143ac8 Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Mon, 19 Jan 2026 11:11:35 +0100 Subject: [PATCH 10/26] fix: handle pod annotations for metrics scraping (#87) --- deploy/csi-rclone/templates/csi-controller-rclone.yaml | 2 ++ deploy/csi-rclone/templates/csi-nodeplugin-rclone.yaml | 2 ++ deploy/csi-rclone/values.yaml | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/deploy/csi-rclone/templates/csi-controller-rclone.yaml b/deploy/csi-rclone/templates/csi-controller-rclone.yaml index 4f65cd82..827cd5ed 100644 --- a/deploy/csi-rclone/templates/csi-controller-rclone.yaml +++ b/deploy/csi-rclone/templates/csi-controller-rclone.yaml @@ -15,6 +15,8 @@ spec: metadata: labels: app: csi-controller-rclone + annotations: + {{- toYaml .Values.csiControllerRclone.podAnnotations | nindent 8 }} spec: serviceAccountName: {{ include "chart.fullname" . }}-controller containers: diff --git a/deploy/csi-rclone/templates/csi-nodeplugin-rclone.yaml b/deploy/csi-rclone/templates/csi-nodeplugin-rclone.yaml index be27eb09..0e333cdb 100644 --- a/deploy/csi-rclone/templates/csi-nodeplugin-rclone.yaml +++ b/deploy/csi-rclone/templates/csi-nodeplugin-rclone.yaml @@ -15,6 +15,8 @@ spec: labels: app: csi-nodeplugin-rclone {{- include "chart.selectorLabels" . | nindent 8 }} + annotations: + {{- toYaml .Values.csiNodepluginRclone.podAnnotations | nindent 8 }} spec: serviceAccountName: {{ include "chart.fullname" . }}-nodeplugin dnsPolicy: ClusterFirstWithHostNet diff --git a/deploy/csi-rclone/values.yaml b/deploy/csi-rclone/values.yaml index 15db99f4..479f128a 100644 --- a/deploy/csi-rclone/values.yaml +++ b/deploy/csi-rclone/values.yaml @@ -1,5 +1,8 @@ storageClassName: csi-rclone csiControllerRclone: + podAnnotations: + prometheus.io/scrape: true + prometheus.io/port: 9090 csiAttacher: image: repository: registry.k8s.io/sig-storage/csi-attacher @@ -38,6 +41,9 @@ csiControllerRclone: annotations: {} csiNodepluginRclone: + podAnnotations: + prometheus.io/scrape: true + prometheus.io/port: 9090 nodeDriverRegistrar: image: repository: registry.k8s.io/sig-storage/csi-node-driver-registrar From 7ef93242bf0012e1ed6a4dcc97e4090b290c95a1 Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Thu, 22 Jan 2026 10:46:30 +0100 Subject: [PATCH 11/26] fix: pod annotations should be a map of string to strings --- deploy/csi-rclone/values.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deploy/csi-rclone/values.yaml b/deploy/csi-rclone/values.yaml index 479f128a..37b43f2f 100644 --- a/deploy/csi-rclone/values.yaml +++ b/deploy/csi-rclone/values.yaml @@ -1,8 +1,8 @@ storageClassName: csi-rclone csiControllerRclone: podAnnotations: - prometheus.io/scrape: true - prometheus.io/port: 9090 + prometheus.io/scrape: "true" + prometheus.io/port: "9090" csiAttacher: image: repository: registry.k8s.io/sig-storage/csi-attacher @@ -42,8 +42,8 @@ csiControllerRclone: csiNodepluginRclone: podAnnotations: - prometheus.io/scrape: true - prometheus.io/port: 9090 + prometheus.io/scrape: "true" + prometheus.io/port: "9090" nodeDriverRegistrar: image: repository: registry.k8s.io/sig-storage/csi-node-driver-registrar From bb93654213da4791812a2d46daa17fa19cb3d863 Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Wed, 14 Jan 2026 09:30:30 +0100 Subject: [PATCH 12/26] feat: Split stage & publish operations --- .devcontainer/devcontainer.json | 2 + pkg/rclone/nodeserver.go | 280 ++++++++++++++++++++------------ test/sanity_test.go | 6 +- 3 files changed, 178 insertions(+), 110 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 44569175..e6680a16 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,8 @@ { "name": "CSI rclone devcontainer", "image": "mcr.microsoft.com/devcontainers/base:bookworm", + "remoteUser": "root", + "containerUser": "root", "features": { "ghcr.io/devcontainers/features/git:1": {}, "ghcr.io/devcontainers/features/go:1": {}, diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index 830f232e..0cf7ff0d 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -115,7 +115,7 @@ func NewNodeServer(csiDriver *csicommon.CSIDriver, cacheDir string, cacheSize st RcloneOps: NewRclone(kubeClient, rclonePort, cacheDir, cacheSize), mountedVolumes: make(map[string]MountedVolume), mutex: &sync.Mutex{}, - stateFile: "/run/csi-rclone/mounted_volumes.json", + stateFile: "/run/csi-rclone/mounted_volumes.json", } // Ensure the folder exists @@ -134,6 +134,20 @@ func NewNodeServer(csiDriver *csicommon.CSIDriver, cacheDir string, cacheSize st return ns, nil } +func (ns *NodeServer) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) { + return &csi.NodeGetCapabilitiesResponse{ + Capabilities: []*csi.NodeServiceCapability{ + { + Type: &csi.NodeServiceCapability_Rpc{ + Rpc: &csi.NodeServiceCapability_RPC{ + Type: csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME, + }, + }, + }, + }, + }, nil +} + func (ns *NodeServer) Run(ctx context.Context) error { defer ns.Stop() return ns.RcloneOps.Run(ctx, func() error { @@ -216,24 +230,38 @@ type MountedVolume struct { } // Mounting Volume (Preparation) -func (ns *NodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method NodeStageVolume not implemented") -} +func validateNodeStageVolumeRequest(req *csi.NodeStageVolumeRequest) error { + if req.GetVolumeId() == "" { + return status.Error(codes.InvalidArgument, "empty volume id") + } -func (ns *NodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method NodeUnstageVolume not implemented") -} + if req.GetStagingTargetPath() == "" { + return status.Error(codes.InvalidArgument, "empty staging path") + } -// Mounting Volume (Actual Mounting) -func (ns *NodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { - if err := validatePublishVolumeRequest(req); err != nil { - return nil, err + capability := req.GetVolumeCapability() + if capability == nil { + return status.Error(codes.InvalidArgument, "no volume capability set") + } + + switch capability.GetAccessMode().GetMode() { + case csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER: + return nil + case csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER: + return nil + case csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY: + return nil + default: + return status.Errorf(codes.FailedPrecondition, "Volume access mode not supported %v", capability.GetAccessMode().GetMode()) } +} - targetPath := req.GetTargetPath() - volumeId := req.GetVolumeId() +func isNodeStageReqReadOnly(req *csi.NodeStageVolumeRequest) bool { + return req.GetVolumeCapability().GetAccessMode().GetMode() == csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY +} + +func getVolumeConfig(ctx context.Context, req *csi.NodeStageVolumeRequest) (*MountedVolume, error) { volumeContext := req.GetVolumeContext() - readOnly := req.GetReadonly() secretName, foundSecret := volumeContext["secretName"] secretNamespace, foundSecretNamespace := volumeContext["secretNamespace"] // For backwards compatibility - prior to the change in #20 this field held the namespace @@ -271,43 +299,44 @@ func (ns *NodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis remote, remotePath, configData, flags, e := extractFlags(req.GetVolumeContext(), req.GetSecrets(), pvcSecret, savedPvcSecret) delete(flags, "secretName") delete(flags, "namespace") - if e != nil { - klog.Warningf("storage parameter error: %s", e) - return nil, e - } - notMnt, err := ns.mounter.IsLikelyNotMountPoint(targetPath) - if err != nil { - if os.IsNotExist(err) { - if err := os.MkdirAll(targetPath, 0750); err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - notMnt = true - } else { - return nil, status.Error(codes.Internal, err.Error()) - } + + return &MountedVolume{ + VolumeId: req.GetVolumeId(), + TargetPath: req.GetStagingTargetPath(), + Remote: remote, + RemotePath: remotePath, + ConfigData: configData, + ReadOnly: isNodeStageReqReadOnly(req), + Parameters: flags, + SecretName: secretName, + SecretNamespace: secretNamespace, + }, e +} + +func (ns *NodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { + if err := validateNodeStageVolumeRequest(req); err != nil { + return nil, err } - if !notMnt { - // testing original mount point, make sure the mount link is valid - if _, err := os.ReadDir(targetPath); err == nil { - klog.Infof("already mounted to target %s", targetPath) - return &csi.NodePublishVolumeResponse{}, nil + // Already staged ? + if volume, ok := ns.mountedVolumes[req.GetVolumeId()]; ok { + if volume.TargetPath == req.GetStagingTargetPath() && volume.ReadOnly == isNodeStageReqReadOnly(req) { + return &csi.NodeStageVolumeResponse{}, nil } - // todo: mount link is invalid, now unmount and remount later (built-in functionality) - klog.Warningf("ReadDir %s failed with %v, unmount this directory", targetPath, err) + return nil, status.Error(codes.AlreadyExists, "Requested Volume capability incompatible with currently staged volume") + } - if err := ns.mounter.Unmount(targetPath); err != nil { - klog.Errorf("Unmount directory %s failed with %v", targetPath, err) - return nil, err - } + volume, err := getVolumeConfig(ctx, req) + if err != nil { + return nil, err } rcloneVol := &RcloneVolume{ - ID: volumeId, - Remote: remote, - RemotePath: remotePath, + ID: volume.VolumeId, + Remote: volume.Remote, + RemotePath: volume.RemotePath, } - err = ns.RcloneOps.Mount(ctx, rcloneVol, targetPath, configData, readOnly, flags) + err = ns.RcloneOps.Mount(ctx, rcloneVol, volume.TargetPath, volume.ConfigData, volume.ReadOnly, volume.Parameters) if err != nil { if os.IsPermission(err) { return nil, status.Error(codes.PermissionDenied, err.Error()) @@ -319,12 +348,100 @@ func (ns *NodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis } // Track the mounted volume for automatic remounting - ns.trackMountedVolume(volumeId, targetPath, remote, remotePath, configData, readOnly, flags, secretName, secretNamespace) + ns.trackMountedVolume(volume) + + return &csi.NodeStageVolumeResponse{}, nil +} + +func validateNodeUnstageVolumeRequest(req *csi.NodeUnstageVolumeRequest) error { + if req.GetVolumeId() == "" { + return status.Error(codes.InvalidArgument, "empty volume id") + } + if req.GetStagingTargetPath() == "" { + return status.Error(codes.InvalidArgument, "empty staging path") + } + + return nil +} + +func (ns *NodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) { + if err := validateNodeUnstageVolumeRequest(req); err != nil { + return nil, err + } + + if volume, ok := ns.mountedVolumes[req.GetVolumeId()]; ok { + if err := ns.RcloneOps.Unmount(ctx, volume.VolumeId, volume.TargetPath); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + // Remove the volume from tracking + ns.removeTrackedVolume(req.GetVolumeId()) + } else { + return nil, status.Error(codes.NotFound, "volume not found") + } + + return &csi.NodeUnstageVolumeResponse{}, nil +} + +func validateNodePublishVolumeRequest(req *csi.NodePublishVolumeRequest) error { + if req.GetVolumeId() == "" { + return status.Error(codes.InvalidArgument, "empty volume id") + } + + if req.GetStagingTargetPath() == "" { + return status.Error(codes.InvalidArgument, "empty staging path") + } + + if req.GetTargetPath() == "" { + return status.Error(codes.InvalidArgument, "empty target path") + } + + capability := req.GetVolumeCapability() + if capability == nil { + return status.Error(codes.InvalidArgument, "no volume capability set") + } + + switch capability.GetAccessMode().GetMode() { + case csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER: + return nil + case csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER: + return nil + case csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY: + return nil + default: + return status.Errorf(codes.FailedPrecondition, "Volume access mode not supported %v", capability.GetAccessMode().GetMode()) + } +} + +func (ns *NodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { + if err := validateNodePublishVolumeRequest(req); err != nil { + return nil, err + } + + volume, ok := ns.mountedVolumes[req.GetVolumeId()] + if !ok { + return nil, status.Error(codes.NotFound, "Volume not found") + } + if volume.ReadOnly && !req.GetReadonly() { + return nil, status.Error(codes.AlreadyExists, "Volume is already published") + } + + if mounts, err := ns.mounter.GetMountRefs(req.GetTargetPath()); err == nil && len(mounts) > 0 { + return &csi.NodePublishVolumeResponse{}, nil + } + + if err := os.MkdirAll(req.GetTargetPath(), 0755); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + options := []string{"bind"} + if req.GetReadonly() { + options = append(options, "remount", "ro") + } + if err := ns.mounter.Mount(req.GetStagingTargetPath(), req.GetTargetPath(), "", options); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } - // err = ns.WaitForMountAvailable(targetPath) - // if err != nil { - // return nil, status.Error(codes.Internal, err.Error()) - // } return &csi.NodePublishVolumeResponse{}, nil } @@ -356,21 +473,6 @@ func getPVC(ctx context.Context, namespace, name string) (*v1.PersistentVolumeCl return cs.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, name, metav1.GetOptions{}) } -func validatePublishVolumeRequest(req *csi.NodePublishVolumeRequest) error { - if req.GetVolumeId() == "" { - return status.Error(codes.InvalidArgument, "empty volume id") - } - - if req.GetTargetPath() == "" { - return status.Error(codes.InvalidArgument, "empty target path") - } - - if req.GetVolumeCapability() == nil { - return status.Error(codes.InvalidArgument, "no volume capability set") - } - return nil -} - func extractFlags(volumeContext map[string]string, secret map[string]string, pvcSecret *v1.Secret, savedPvcSecret *v1.Secret) (string, string, string, map[string]string, error) { // Empty argument list @@ -488,26 +590,14 @@ func (ns *NodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpu if err := validateUnPublishVolumeRequest(req); err != nil { return nil, err } - targetPath := req.GetTargetPath() - if len(targetPath) == 0 { - klog.Warning("no target path provided for NodeUnpublishVolume") - return nil, status.Error(codes.InvalidArgument, "NodeUnpublishVolume Target Path must be provided") - } - - if _, err := ns.RcloneOps.GetVolumeById(ctx, req.GetVolumeId()); err == ErrVolumeNotFound { - klog.Warning("VolumeId not found for NodeUnpublishVolume") - mount.CleanupMountPoint(req.GetTargetPath(), ns.mounter, false) - return &csi.NodeUnpublishVolumeResponse{}, nil - } - if err := ns.RcloneOps.Unmount(ctx, req.GetVolumeId(), targetPath); err != nil { - klog.Warningf("Unmounting volume failed: %s", err) + if err := mount.CleanupMountPoint(req.GetTargetPath(), ns.mounter, true); err != nil { + if mounts, err := ns.mounter.GetMountRefs(req.GetTargetPath()); err == nil && len(mounts) == 0 { + return &csi.NodeUnpublishVolumeResponse{}, nil + } + return nil, status.Error(codes.Internal, err.Error()) } - // Remove the volume from tracking - ns.removeTrackedVolume(req.GetVolumeId()) - - mount.CleanupMountPoint(req.GetTargetPath(), ns.mounter, false) return &csi.NodeUnpublishVolumeResponse{}, nil } @@ -529,22 +619,12 @@ func (*NodeServer) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolu } // Track mounted volume for automatic remounting -func (ns *NodeServer) trackMountedVolume(volumeId, targetPath, remote, remotePath, configData string, readOnly bool, parameters map[string]string, secretName, secretNamespace string) { +func (ns *NodeServer) trackMountedVolume(volume *MountedVolume) { ns.mutex.Lock() defer ns.mutex.Unlock() - ns.mountedVolumes[volumeId] = MountedVolume{ - VolumeId: volumeId, - TargetPath: targetPath, - Remote: remote, - RemotePath: remotePath, - ConfigData: configData, - ReadOnly: readOnly, - Parameters: parameters, - SecretName: secretName, - SecretNamespace: secretNamespace, - } - klog.Infof("Tracked mounted volume %s at path %s", volumeId, targetPath) + ns.mountedVolumes[volume.VolumeId] = *volume + klog.Infof("Tracked mounted volume %s at path %s", volume.VolumeId, volume.TargetPath) if err := writeVolumeMap(ns.stateFile, ns.mountedVolumes); err != nil { klog.Errorf("Failed to persist volume state: %v", err) @@ -645,20 +725,6 @@ func (ns *NodeServer) remountTrackedVolumes(ctx context.Context) error { } } -func (ns *NodeServer) WaitForMountAvailable(mountpoint string) error { - for { - select { - case <-time.After(100 * time.Millisecond): - notMnt, _ := ns.mounter.IsLikelyNotMountPoint(mountpoint) - if !notMnt { - return nil - } - case <-time.After(3 * time.Second): - return errors.New("wait for mount available timeout") - } - } -} - // Persist volume state to disk func writeVolumeMap(filename string, volumes map[string]MountedVolume) error { if filename == "" { diff --git a/test/sanity_test.go b/test/sanity_test.go index a74beb6a..bb6d1dfc 100644 --- a/test/sanity_test.go +++ b/test/sanity_test.go @@ -189,8 +189,8 @@ provider=AWS`}, "remotePath": "giab/", "secretKey": "cw_0x689RpI-jtRR7oE8h_eQsKImvJapLeSbXpwF4e4=", "configData": `[my-s3] -type= -provider=AWS`}, +type=s3 +provider=AWS`}, // The type has to be set to something valid or rclone fails to find the proper backend plugin. Type: "Opaque", }, metav1.CreateOptions{}) className := "csi-rclone-secret-annotation" @@ -227,7 +227,7 @@ provider=AWS`}, }) }) - Context("Serets from annotations with decryption", Ordered, func() { + Context("Secrets from annotations with decryption", Ordered, func() { var cfg *sanity.TestConfig = &sanity.TestConfig{} var testCtx *sanity.TestContext = &sanity.TestContext{} From 411f3aac6a5b5d488f585848a6a6827ccb5cf24f Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Wed, 14 Jan 2026 09:42:33 +0100 Subject: [PATCH 13/26] fix: Cleanup some warnings --- pkg/rclone/nodeserver.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index 0cf7ff0d..d33a5568 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -39,9 +39,6 @@ import ( csicommon "github.com/kubernetes-csi/drivers/pkg/csi-common" ) -const CSI_ANNOTATION_PREFIX = "csi-rclone.dev" -const pvcSecretNameAnnotation = CSI_ANNOTATION_PREFIX + "/secretName" - type NodeServer struct { *csicommon.DefaultNodeServer mounter *mount.SafeFormatAndMount @@ -134,7 +131,7 @@ func NewNodeServer(csiDriver *csicommon.CSIDriver, cacheDir string, cacheSize st return ns, nil } -func (ns *NodeServer) NodeGetCapabilities(ctx context.Context, req *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) { +func (ns *NodeServer) NodeGetCapabilities(_ context.Context, _ *csi.NodeGetCapabilitiesRequest) (*csi.NodeGetCapabilitiesResponse, error) { return &csi.NodeGetCapabilitiesResponse{ Capabilities: []*csi.NodeServiceCapability{ { @@ -413,7 +410,7 @@ func validateNodePublishVolumeRequest(req *csi.NodePublishVolumeRequest) error { } } -func (ns *NodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { +func (ns *NodeServer) NodePublishVolume(_ context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { if err := validateNodePublishVolumeRequest(req); err != nil { return nil, err } @@ -482,7 +479,7 @@ func extractFlags(volumeContext map[string]string, secret map[string]string, pvc if len(secret) > 0 { // Needs byte to string casting for map values for k, v := range secret { - flags[k] = string(v) + flags[k] = v } } else { klog.Infof("No csi-rclone connection defaults secret found.") @@ -543,7 +540,7 @@ func decryptSecrets(flags map[string]string, savedPvcSecret *v1.Secret) (map[str if len(savedPvcSecret.Data) > 0 { for k, v := range savedPvcSecret.Data { - savedSecrets[k] = string(fernet.VerifyAndDecrypt([]byte(v), 0, []*fernet.Key{fernetKey})) + savedSecrets[k] = string(fernet.VerifyAndDecrypt(v, 0, []*fernet.Key{fernetKey})) } } @@ -585,7 +582,7 @@ func extractConfigData(parameters map[string]string) (string, map[string]string) } // Unmounting Volumes -func (ns *NodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) { +func (ns *NodeServer) NodeUnpublishVolume(_ context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) { klog.Infof("NodeUnpublishVolume called with: %s", req) if err := validateUnPublishVolumeRequest(req); err != nil { return nil, err @@ -614,7 +611,7 @@ func validateUnPublishVolumeRequest(req *csi.NodeUnpublishVolumeRequest) error { } // Resizing Volume -func (*NodeServer) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandVolumeRequest) (*csi.NodeExpandVolumeResponse, error) { +func (*NodeServer) NodeExpandVolume(_ context.Context, _ *csi.NodeExpandVolumeRequest) (*csi.NodeExpandVolumeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method NodeExpandVolume not implemented") } From 29cdaedff844bce50ee9c932fbe0915b572e51d2 Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Wed, 14 Jan 2026 10:56:36 +0100 Subject: [PATCH 14/26] fix: Add explicit support MULTI_READER_ONLY --- pkg/rclone/driver.go | 2 ++ pkg/rclone/nodeserver.go | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/pkg/rclone/driver.go b/pkg/rclone/driver.go index e0b7e53d..8f89d3e6 100644 --- a/pkg/rclone/driver.go +++ b/pkg/rclone/driver.go @@ -44,6 +44,8 @@ func Run(ctx context.Context, config *DriverConfig, setup DriverSetup, serve Dri driver := csicommon.NewCSIDriver(driverName, DriverVersion, config.NodeID) driver.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{ csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER, + csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY, + csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY, }) driver.AddControllerServiceCapabilities( []csi.ControllerServiceCapability_RPC_Type{ diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index d33a5568..0b496142 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -248,13 +248,22 @@ func validateNodeStageVolumeRequest(req *csi.NodeStageVolumeRequest) error { return nil case csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY: return nil + case csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY: + return nil default: return status.Errorf(codes.FailedPrecondition, "Volume access mode not supported %v", capability.GetAccessMode().GetMode()) } } func isNodeStageReqReadOnly(req *csi.NodeStageVolumeRequest) bool { - return req.GetVolumeCapability().GetAccessMode().GetMode() == csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY + switch req.GetVolumeCapability().GetAccessMode().GetMode() { + case csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY: + return true + case csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY: + return true + default: + return false + } } func getVolumeConfig(ctx context.Context, req *csi.NodeStageVolumeRequest) (*MountedVolume, error) { @@ -405,6 +414,8 @@ func validateNodePublishVolumeRequest(req *csi.NodePublishVolumeRequest) error { return nil case csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY: return nil + case csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY: + return nil default: return status.Errorf(codes.FailedPrecondition, "Volume access mode not supported %v", capability.GetAccessMode().GetMode()) } From 5c8ba5357369833f56180d45b7428fb2edb44bd2 Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Wed, 21 Jan 2026 12:11:36 +0100 Subject: [PATCH 15/26] fix: Add standardized logs to gRPC methods --- pkg/rclone/controllerserver.go | 13 ++++++++++--- pkg/rclone/nodeserver.go | 9 ++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/pkg/rclone/controllerserver.go b/pkg/rclone/controllerserver.go index 152011f8..824448c8 100644 --- a/pkg/rclone/controllerserver.go +++ b/pkg/rclone/controllerserver.go @@ -103,18 +103,23 @@ func (cs *ControllerServer) ValidateVolumeCapabilities(_ context.Context, req *c } // ControllerPublishVolume Attaching Volume -func (cs *ControllerServer) ControllerPublishVolume(_ context.Context, _ *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { +func (cs *ControllerServer) ControllerPublishVolume(_ context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { + klog.Infof("ControllerPublishVolume called with: %v", *req) + return nil, status.Errorf(codes.Unimplemented, "method ControllerPublishVolume not implemented") } // ControllerUnpublishVolume Detaching Volume -func (cs *ControllerServer) ControllerUnpublishVolume(_ context.Context, _ *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) { +func (cs *ControllerServer) ControllerUnpublishVolume(_ context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) { + klog.Infof("ControllerUnpublishVolume called with: %v", *req) + return nil, status.Errorf(codes.Unimplemented, "method ControllerUnpublishVolume not implemented") } // CreateVolume Provisioning Volumes func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { - klog.Infof("ControllerCreateVolume: called with args %+v", *req) + klog.Infof("CreateVolume called with: %v", *req) + volumeName := req.GetName() if len(volumeName) == 0 { return nil, status.Error(codes.InvalidArgument, "CreateVolume name must be provided") @@ -172,6 +177,8 @@ func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol } func (cs *ControllerServer) DeleteVolume(_ context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) { + klog.Infof("DeleteVolume called with: %v", *req) + volId := req.GetVolumeId() if len(volId) == 0 { return nil, status.Error(codes.InvalidArgument, "DeleteVolume must be provided volume id") diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index 0b496142..f423e2e2 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -320,6 +320,8 @@ func getVolumeConfig(ctx context.Context, req *csi.NodeStageVolumeRequest) (*Mou } func (ns *NodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { + klog.Infof("NodeStageVolume called with: %v", *req) + if err := validateNodeStageVolumeRequest(req); err != nil { return nil, err } @@ -371,6 +373,8 @@ func validateNodeUnstageVolumeRequest(req *csi.NodeUnstageVolumeRequest) error { } func (ns *NodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstageVolumeRequest) (*csi.NodeUnstageVolumeResponse, error) { + klog.Infof("NodeUnstageVolume called with: %v", *req) + if err := validateNodeUnstageVolumeRequest(req); err != nil { return nil, err } @@ -422,6 +426,8 @@ func validateNodePublishVolumeRequest(req *csi.NodePublishVolumeRequest) error { } func (ns *NodeServer) NodePublishVolume(_ context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { + klog.Infof("NodePublishVolume called with: %v", *req) + if err := validateNodePublishVolumeRequest(req); err != nil { return nil, err } @@ -594,7 +600,8 @@ func extractConfigData(parameters map[string]string) (string, map[string]string) // Unmounting Volumes func (ns *NodeServer) NodeUnpublishVolume(_ context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) { - klog.Infof("NodeUnpublishVolume called with: %s", req) + klog.Infof("NodeUnpublishVolume called with: %v", *req) + if err := validateUnPublishVolumeRequest(req); err != nil { return nil, err } From d797259f2e97a38d2171d6187be86bc3700d1f60 Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Wed, 21 Jan 2026 14:38:02 +0100 Subject: [PATCH 16/26] fix: use a tmpfs as a fixed point, review unmount process --- pkg/rclone/nodeserver.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index f423e2e2..abeeaefc 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -448,6 +448,11 @@ func (ns *NodeServer) NodePublishVolume(_ context.Context, req *csi.NodePublishV return nil, status.Error(codes.Internal, err.Error()) } + // Mount a tmpfs which is going to serve as a fixed point to allow rebinding fuse whenever necessary + if err := ns.mounter.Mount("tmpfs", req.GetTargetPath(), "tmpfs", []string{"size=1M"}); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + options := []string{"bind"} if req.GetReadonly() { options = append(options, "remount", "ro") @@ -606,14 +611,19 @@ func (ns *NodeServer) NodeUnpublishVolume(_ context.Context, req *csi.NodeUnpubl return nil, err } - if err := mount.CleanupMountPoint(req.GetTargetPath(), ns.mounter, true); err != nil { - if mounts, err := ns.mounter.GetMountRefs(req.GetTargetPath()); err == nil && len(mounts) == 0 { - return &csi.NodeUnpublishVolumeResponse{}, nil + for { + if err := ns.mounter.Unmount(req.GetTargetPath()); err != nil { + // keep unmounting whatever is on the folder until we can't + break } + } + + if err := os.Remove(req.GetTargetPath()); err != nil && !errors.Is(err, os.ErrNotExist) { return nil, status.Error(codes.Internal, err.Error()) } return &csi.NodeUnpublishVolumeResponse{}, nil + } func validateUnPublishVolumeRequest(req *csi.NodeUnpublishVolumeRequest) error { From 5cc4bc525c00be50097c7bc2d45d5a973214fc95 Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Wed, 21 Jan 2026 14:21:30 +0100 Subject: [PATCH 17/26] fix: Add csi.NodeServiceCapability_RPC_UNKNOWN in the list, just in case, as it is there by default. --- pkg/rclone/nodeserver.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index abeeaefc..f42eeec1 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -141,6 +141,13 @@ func (ns *NodeServer) NodeGetCapabilities(_ context.Context, _ *csi.NodeGetCapab }, }, }, + { + Type: &csi.NodeServiceCapability_Rpc{ + Rpc: &csi.NodeServiceCapability_RPC{ + Type: csi.NodeServiceCapability_RPC_UNKNOWN, + }, + }, + }, }, }, nil } From d4023ab6e89508ebc4cfd0be3fa891ac8115475f Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Fri, 23 Jan 2026 08:26:25 +0100 Subject: [PATCH 18/26] chore: upgrade to Go 1.25 --- .devcontainer/devcontainer.json | 4 +++- Dockerfile | 4 ++-- go.mod | 4 +--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index e6680a16..712bc346 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,9 @@ "containerUser": "root", "features": { "ghcr.io/devcontainers/features/git:1": {}, - "ghcr.io/devcontainers/features/go:1": {}, + "ghcr.io/devcontainers/features/go:1": { + "version": "latest" + }, "ghcr.io/devcontainers-extra/features/apt-packages:1": { "packages": "fuse3" }, diff --git a/Dockerfile b/Dockerfile index 26b8a135..dfbca30d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ ARG RCLONE_IMAGE_REPOSITORY="ghcr.io/swissdatasciencecenter/rclone" ARG RCLONE_IMAGE_TAG="sha-308067c" FROM ${RCLONE_IMAGE_REPOSITORY}:${RCLONE_IMAGE_TAG} AS rclone -FROM golang:1.23.8-bookworm AS build +FROM golang:1.25.6-bookworm AS build COPY go.mod go.sum ./ RUN --mount=type=cache,target=/go/pkg/mod \ go mod download @@ -23,4 +23,4 @@ EOT COPY --from=build /csi-rclone /csi-rclone COPY --from=rclone --chmod=755 /rclone /usr/bin/ -ENTRYPOINT ["/csi-rclone"] \ No newline at end of file +ENTRYPOINT ["/csi-rclone"] diff --git a/go.mod b/go.mod index f27cc4f3..36f73103 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/SwissDataScienceCenter/csi-rclone -go 1.23.0 - -toolchain go1.23.4 +go 1.25.0 require ( github.com/container-storage-interface/spec v1.9.0 From 941dea5f494d240a9ec82ccf3568b47f562bc274 Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Fri, 23 Jan 2026 08:38:32 +0100 Subject: [PATCH 19/26] fix: Cleanup some warnings --- pkg/kube/client.go | 4 +++- pkg/rclone/controllerserver.go | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 364709d4..16b431aa 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -1,6 +1,8 @@ package kube import ( + "errors" + "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -31,7 +33,7 @@ func loadKubeConfig() (*rest.Config, error) { return config, nil } - if err != rest.ErrNotInCluster { + if !errors.Is(err, rest.ErrNotInCluster) { return nil, err } diff --git a/pkg/rclone/controllerserver.go b/pkg/rclone/controllerserver.go index 824448c8..01660b41 100644 --- a/pkg/rclone/controllerserver.go +++ b/pkg/rclone/controllerserver.go @@ -104,21 +104,21 @@ func (cs *ControllerServer) ValidateVolumeCapabilities(_ context.Context, req *c // ControllerPublishVolume Attaching Volume func (cs *ControllerServer) ControllerPublishVolume(_ context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { - klog.Infof("ControllerPublishVolume called with: %v", *req) + klog.Infof("ControllerPublishVolume called with: %v", req) return nil, status.Errorf(codes.Unimplemented, "method ControllerPublishVolume not implemented") } // ControllerUnpublishVolume Detaching Volume func (cs *ControllerServer) ControllerUnpublishVolume(_ context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) { - klog.Infof("ControllerUnpublishVolume called with: %v", *req) + klog.Infof("ControllerUnpublishVolume called with: %v", req) return nil, status.Errorf(codes.Unimplemented, "method ControllerUnpublishVolume not implemented") } // CreateVolume Provisioning Volumes func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { - klog.Infof("CreateVolume called with: %v", *req) + klog.Infof("CreateVolume called with: %v", req) volumeName := req.GetName() if len(volumeName) == 0 { @@ -177,7 +177,7 @@ func (cs *ControllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol } func (cs *ControllerServer) DeleteVolume(_ context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) { - klog.Infof("DeleteVolume called with: %v", *req) + klog.Infof("DeleteVolume called with: %v", req) volId := req.GetVolumeId() if len(volId) == 0 { From e6f05bafb3505185be5813ec53fd4a6ef82db905 Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Fri, 23 Jan 2026 08:03:12 +0000 Subject: [PATCH 20/26] chore: update most libraries, except csi ones --- go.mod | 107 +++--- go.sum | 770 +++++++++++--------------------------------- test/sanity_test.go | 4 +- 3 files changed, 240 insertions(+), 641 deletions(-) diff --git a/go.mod b/go.mod index 36f73103..602ae986 100644 --- a/go.mod +++ b/go.mod @@ -5,68 +5,75 @@ go 1.25.0 require ( github.com/container-storage-interface/spec v1.9.0 github.com/fernet/fernet-go v0.0.0-20240119011108-303da6aec611 - github.com/google/uuid v1.4.0 + github.com/google/uuid v1.6.0 github.com/kubernetes-csi/csi-test/v5 v5.2.0 github.com/kubernetes-csi/drivers v1.0.2 - github.com/onsi/ginkgo/v2 v2.13.1 - github.com/onsi/gomega v1.30.0 - github.com/prometheus/client_golang v0.9.3 - github.com/spf13/cobra v1.1.1 - golang.org/x/net v0.17.0 - google.golang.org/grpc v1.58.1 - gopkg.in/ini.v1 v1.67.0 - k8s.io/api v0.20.4 - k8s.io/apimachinery v0.20.4 - k8s.io/client-go v0.20.4 + github.com/onsi/ginkgo/v2 v2.27.5 + github.com/onsi/gomega v1.39.0 + github.com/prometheus/client_golang v1.23.2 + github.com/spf13/cobra v1.10.2 + github.com/spf13/pflag v1.0.10 + golang.org/x/net v0.49.0 + google.golang.org/grpc v1.78.0 + gopkg.in/ini.v1 v1.67.1 + k8s.io/api v0.35.0 + k8s.io/apimachinery v0.35.0 + k8s.io/client-go v0.35.0 k8s.io/klog v1.0.0 - k8s.io/mount-utils v0.32.1 - k8s.io/utils v0.0.0-20241210054802-24370beab758 + k8s.io/mount-utils v0.35.0 + k8s.io/utils v0.0.0-20260108192941-914a6e750570 ) require ( - github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect - github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect - github.com/beorn7/perks v1.0.0 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect - github.com/gogo/protobuf v1.3.1 // indirect - github.com/golang/glog v1.1.0 // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/google/gofuzz v1.1.0 // indirect - github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect - github.com/googleapis/gnostic v0.4.1 // indirect - github.com/imdario/mergo v0.3.7 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/json-iterator/go v1.1.10 // indirect - github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/golang/glog v1.2.5 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/kubernetes-csi/csi-lib-utils v0.3.1 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/moby/sys/mountinfo v0.7.2 // indirect - github.com/moby/sys/userns v0.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect - github.com/prometheus/common v0.4.0 // indirect - github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 // indirect - github.com/sirupsen/logrus v1.2.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/crypto v0.14.0 // indirect - golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.14.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect - google.golang.org/protobuf v1.31.0 // indirect - gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/oauth2 v0.32.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/text v0.33.0 // indirect + golang.org/x/time v0.9.0 // indirect + golang.org/x/tools v0.40.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.0.2 // indirect - sigs.k8s.io/yaml v1.2.0 // indirect + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 99033f00..13fe750c 100644 --- a/go.sum +++ b/go.sum @@ -1,631 +1,223 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= -github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= -github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/container-storage-interface/spec v1.9.0 h1:zKtX4STsq31Knz3gciCYCi1SXtO2HJDecIjDVboYavY= github.com/container-storage-interface/spec v1.9.0/go.mod h1:ZfDu+3ZRyeVqxZM0Ds19MVLkN2d1XJ5MAfi1L3VjlT0= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +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/fernet/fernet-go v0.0.0-20240119011108-303da6aec611 h1:JwYtKJ/DVEoIA5dH45OEU7uoryZY/gjd/BQiwwAOImM= github.com/fernet/fernet-go v0.0.0-20240119011108-303da6aec611/go.mod h1:zHMNeYgqrTpKyjawjitDg0Osd1P/FmeA0SZLYK3RfLQ= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -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/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= +github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -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= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-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/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +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-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= +github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= -github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.4.1 h1:DLJCy1n/vrD4HPjOvYcT8aYQXpPIzoRZONaYwyycI+I= -github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= -github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kubernetes-csi/csi-lib-utils v0.3.1 h1:EPE7WgaMx8XwfBIdxJns3B87V0x8TN1mWoZOVNliUaM= github.com/kubernetes-csi/csi-lib-utils v0.3.1/go.mod h1:GVmlUmxZ+SUjVLXicRFjqWUUvWez0g0Y78zNV9t7KfQ= github.com/kubernetes-csi/csi-test/v5 v5.2.0 h1:Z+sdARWC6VrONrxB24clCLCmnqCnZF7dzXtzx8eM35o= github.com/kubernetes-csi/csi-test/v5 v5.2.0/go.mod h1:o/c5w+NU3RUNE+DbVRhEUTmkQVBGk+tFOB2yPXT8teo= github.com/kubernetes-csi/drivers v1.0.2 h1:kaEAMfo+W5YFr23yedBIY+NGnNjr6/PbPzx7N4GYgiQ= github.com/kubernetes-csi/drivers v1.0.2/go.mod h1:V6rHbbSLCZGaQoIZ8MkyDtoXtcKXZM0F7N3bkloDCOY= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= -github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= -github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo/v2 v2.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU= -github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= -github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.27.5 h1:ZeVgZMx2PDMdJm/+w5fE/OyG6ILo1Y3e+QX4zSR0zTE= +github.com/onsi/ginkgo/v2 v2.27.5/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= +github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= -github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/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/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= -golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= -golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 h1:eSaPbMR4T7WfH9FvABk36NBMacoTUKdWCvV0dx+KfOg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5/go.mod h1:zBEcrKX2ZOcEkHWxBPAIvYUWOKKMIhYcmNiUIu2ji3I= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.58.1 h1:OL+Vz23DTtrrldqHK49FUOPHyY75rvFqJfXC84NYW58= -google.golang.org/grpc v1.58.1/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +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/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda h1:i/Q+bfisr7gq6feoJnS/DlpdwEL4ihp41fvRiM3Ork0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251029180050-ab9386a59fda/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/ini.v1 v1.67.1 h1:tVBILHy0R6e4wkYOn3XmiITt/hEVH4TFMYvAX2Ytz6k= +gopkg.in/ini.v1 v1.67.1/go.mod h1:x/cyOwCgZqOkJoDIJ3c1KNHMo10+nLGAhh+kn3Zizss= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.20.4 h1:xZjKidCirayzX6tHONRQyTNDVIR55TYVqgATqo6ZULY= -k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= -k8s.io/apimachinery v0.20.4 h1:vhxQ0PPUUU2Ns1b9r4/UFp13UPs8cw2iOoTjnY9faa0= -k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/client-go v0.20.4 h1:85crgh1IotNkLpKYKZHVNI1JT86nr/iDCvq2iWKsql4= -k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY= +k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA= +k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8= +k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= +k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE= +k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= -k8s.io/mount-utils v0.32.1 h1:RJOD6xXzEJT/OOJoG1KstfVa8ZXJJPlHb+t2MoulPHM= -k8s.io/mount-utils v0.32.1/go.mod h1:Kun5c2svjAPx0nnvJKYQWhfeNW+O0EpzHgRhDcYoSY0= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= -k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2 h1:YHQV7Dajm86OuqnIR6zAelnDWBRjo+YhYV9PmGrh1s8= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/mount-utils v0.35.0 h1:UDE8RDeqmQh1u/yRd+GZC2EpDibiyAfmMEsm43lKNQI= +k8s.io/mount-utils v0.35.0/go.mod h1:ppC4d+mUpfbAJr/V2E8vvxeCEckNM+S5b0kQBQjd3Pw= +k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY= +k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/test/sanity_test.go b/test/sanity_test.go index bb6d1dfc..5199b539 100644 --- a/test/sanity_test.go +++ b/test/sanity_test.go @@ -201,7 +201,7 @@ provider=AWS`}, // The type has to be set to something valid or rclone fails to }, Spec: v1.PersistentVolumeClaimSpec{ AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, - Resources: v1.ResourceRequirements{ + Resources: v1.VolumeResourceRequirements{ Requests: v1.ResourceList{"storage": resource.MustParse("1Gi")}, }, StorageClassName: &className, @@ -257,7 +257,7 @@ provider=AWS`}, }, Spec: v1.PersistentVolumeClaimSpec{ AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, - Resources: v1.ResourceRequirements{ + Resources: v1.VolumeResourceRequirements{ Requests: v1.ResourceList{"storage": resource.MustParse("1Gi")}, }, StorageClassName: &className, From 38afe2bd0f8d7bda5ceacf83fc5cd7baa62eb494 Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Mon, 26 Jan 2026 08:30:38 +0000 Subject: [PATCH 21/26] test: Try if using the tmpfs during staging works better --- pkg/rclone/nodeserver.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index f42eeec1..3c54ad29 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -341,6 +341,11 @@ func (ns *NodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol return nil, status.Error(codes.AlreadyExists, "Requested Volume capability incompatible with currently staged volume") } + // Mount a tmpfs which is going to serve as a fixed point to allow rebinding fuse whenever necessary + if err := ns.mounter.Mount("tmpfs", req.GetStagingTargetPath(), "tmpfs", []string{"size=1M"}); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + volume, err := getVolumeConfig(ctx, req) if err != nil { return nil, err @@ -393,6 +398,13 @@ func (ns *NodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstag // Remove the volume from tracking ns.removeTrackedVolume(req.GetVolumeId()) + + for { + if err := ns.mounter.Unmount(volume.TargetPath); err != nil { + // keep unmounting whatever is on the folder until we can't + break + } + } } else { return nil, status.Error(codes.NotFound, "volume not found") } @@ -455,10 +467,10 @@ func (ns *NodeServer) NodePublishVolume(_ context.Context, req *csi.NodePublishV return nil, status.Error(codes.Internal, err.Error()) } - // Mount a tmpfs which is going to serve as a fixed point to allow rebinding fuse whenever necessary - if err := ns.mounter.Mount("tmpfs", req.GetTargetPath(), "tmpfs", []string{"size=1M"}); err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } + //// Mount a tmpfs which is going to serve as a fixed point to allow rebinding fuse whenever necessary + //if err := ns.mounter.Mount("tmpfs", req.GetTargetPath(), "tmpfs", []string{"size=1M"}); err != nil { + // return nil, status.Error(codes.Internal, err.Error()) + //} options := []string{"bind"} if req.GetReadonly() { @@ -630,7 +642,6 @@ func (ns *NodeServer) NodeUnpublishVolume(_ context.Context, req *csi.NodeUnpubl } return &csi.NodeUnpublishVolumeResponse{}, nil - } func validateUnPublishVolumeRequest(req *csi.NodeUnpublishVolumeRequest) error { From 0283ba0d1ea27a51463ca58bb7f97a103ef213ef Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Mon, 26 Jan 2026 14:43:34 +0000 Subject: [PATCH 22/26] fix: remove dependency on deprectaed utils/mount --- pkg/rclone/nodeserver.go | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index 3c54ad29..640b84b9 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -22,6 +22,7 @@ import ( "github.com/SwissDataScienceCenter/csi-rclone/pkg/metrics" "github.com/container-storage-interface/spec/lib/go/csi" "github.com/fernet/fernet-go" + csicommon "github.com/kubernetes-csi/drivers/pkg/csi-common" "github.com/prometheus/client_golang/prometheus" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -33,15 +34,11 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/klog" mountutils "k8s.io/mount-utils" - "k8s.io/utils/exec" - "k8s.io/utils/mount" - - csicommon "github.com/kubernetes-csi/drivers/pkg/csi-common" ) type NodeServer struct { *csicommon.DefaultNodeServer - mounter *mount.SafeFormatAndMount + mounter mountutils.Interface RcloneOps Operations // Track mounted volumes for automatic remounting @@ -105,14 +102,11 @@ func NewNodeServer(csiDriver *csicommon.CSIDriver, cacheDir string, cacheSize st ns := &NodeServer{ DefaultNodeServer: csicommon.NewDefaultNodeServer(csiDriver), - mounter: &mount.SafeFormatAndMount{ - Interface: mount.New(""), - Exec: exec.New(), - }, - RcloneOps: NewRclone(kubeClient, rclonePort, cacheDir, cacheSize), - mountedVolumes: make(map[string]MountedVolume), - mutex: &sync.Mutex{}, - stateFile: "/run/csi-rclone/mounted_volumes.json", + mounter: &mountutils.Mounter{}, + RcloneOps: NewRclone(kubeClient, rclonePort, cacheDir, cacheSize), + mountedVolumes: make(map[string]MountedVolume), + mutex: &sync.Mutex{}, + stateFile: "/run/csi-rclone/mounted_volumes.json", } // Ensure the folder exists From d168ee2b9b7e89e3bb3c276e2696dd88c6c45550 Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Mon, 26 Jan 2026 16:07:14 +0100 Subject: [PATCH 23/26] fix: switch to the default go context library --- go.mod | 2 +- pkg/rclone/nodeserver.go | 2 +- pkg/rclone/rclone.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 602ae986..a8029c6a 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/prometheus/client_golang v1.23.2 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 - golang.org/x/net v0.49.0 google.golang.org/grpc v1.78.0 gopkg.in/ini.v1 v1.67.1 k8s.io/api v0.35.0 @@ -57,6 +56,7 @@ require ( go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.49.0 // indirect golang.org/x/oauth2 v0.32.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.40.0 // indirect diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index 640b84b9..a6e0e701 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -7,6 +7,7 @@ package rclone import ( "bytes" + "context" "encoding/json" "errors" "fmt" @@ -25,7 +26,6 @@ import ( csicommon "github.com/kubernetes-csi/drivers/pkg/csi-common" "github.com/prometheus/client_golang/prometheus" "github.com/spf13/cobra" - "golang.org/x/net/context" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "gopkg.in/ini.v1" diff --git a/pkg/rclone/rclone.go b/pkg/rclone/rclone.go index 4fe15120..490d5c50 100644 --- a/pkg/rclone/rclone.go +++ b/pkg/rclone/rclone.go @@ -3,6 +3,7 @@ package rclone import ( "bufio" "bytes" + "context" "encoding/json" "errors" "fmt" @@ -15,7 +16,6 @@ import ( "strings" - "golang.org/x/net/context" "gopkg.in/ini.v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" From 2842e9479f69cb7841d5b2e333ddf34f50da641e Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Mon, 26 Jan 2026 16:07:49 +0100 Subject: [PATCH 24/26] fix: ignore retry error codes --- pkg/rclone/nodeserver.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index a6e0e701..d8ce4f8e 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -17,6 +17,7 @@ import ( "runtime" "strings" "sync" + "syscall" "time" "github.com/SwissDataScienceCenter/csi-rclone/pkg/kube" @@ -394,7 +395,7 @@ func (ns *NodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstag ns.removeTrackedVolume(req.GetVolumeId()) for { - if err := ns.mounter.Unmount(volume.TargetPath); err != nil { + if err := ns.mounter.Unmount(volume.TargetPath); err != nil && !(errors.Is(err, syscall.EAGAIN) || errors.Is(err, syscall.EBUSY)) { // keep unmounting whatever is on the folder until we can't break } @@ -625,7 +626,7 @@ func (ns *NodeServer) NodeUnpublishVolume(_ context.Context, req *csi.NodeUnpubl } for { - if err := ns.mounter.Unmount(req.GetTargetPath()); err != nil { + if err := ns.mounter.Unmount(req.GetTargetPath()); err != nil && !(errors.Is(err, syscall.EAGAIN) || errors.Is(err, syscall.EBUSY)) { // keep unmounting whatever is on the folder until we can't break } From 7faeb81a38dbf3fff253a941f32514ff774c276a Mon Sep 17 00:00:00 2001 From: Lionel Sambuc Date: Fri, 30 Jan 2026 11:33:12 +0100 Subject: [PATCH 25/26] Revert "test: Try if using the tmpfs during staging works better" This reverts commit 38afe2bd0f8d7bda5ceacf83fc5cd7baa62eb494. --- pkg/rclone/nodeserver.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index d8ce4f8e..85edb3cc 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -336,11 +336,6 @@ func (ns *NodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol return nil, status.Error(codes.AlreadyExists, "Requested Volume capability incompatible with currently staged volume") } - // Mount a tmpfs which is going to serve as a fixed point to allow rebinding fuse whenever necessary - if err := ns.mounter.Mount("tmpfs", req.GetStagingTargetPath(), "tmpfs", []string{"size=1M"}); err != nil { - return nil, status.Error(codes.Internal, err.Error()) - } - volume, err := getVolumeConfig(ctx, req) if err != nil { return nil, err @@ -462,10 +457,10 @@ func (ns *NodeServer) NodePublishVolume(_ context.Context, req *csi.NodePublishV return nil, status.Error(codes.Internal, err.Error()) } - //// Mount a tmpfs which is going to serve as a fixed point to allow rebinding fuse whenever necessary - //if err := ns.mounter.Mount("tmpfs", req.GetTargetPath(), "tmpfs", []string{"size=1M"}); err != nil { - // return nil, status.Error(codes.Internal, err.Error()) - //} + // Mount a tmpfs which is going to serve as a fixed point to allow rebinding fuse whenever necessary + if err := ns.mounter.Mount("tmpfs", req.GetTargetPath(), "tmpfs", []string{"size=1M"}); err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } options := []string{"bind"} if req.GetReadonly() { @@ -637,6 +632,7 @@ func (ns *NodeServer) NodeUnpublishVolume(_ context.Context, req *csi.NodeUnpubl } return &csi.NodeUnpublishVolumeResponse{}, nil + } func validateUnPublishVolumeRequest(req *csi.NodeUnpublishVolumeRequest) error { From 5aded7fccc10c1420195a8f8a76f23496a978871 Mon Sep 17 00:00:00 2001 From: Flora Thiebaut Date: Thu, 12 Jun 2025 09:05:27 +0200 Subject: [PATCH 26/26] build: add action to build the container image (dev) --- .github/workflows/build.yaml | 71 ++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 .github/workflows/build.yaml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 00000000..3fe5df72 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,71 @@ +name: Build dev version + +on: + push: + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + CHART_NAME: ${{ github.repository }}/helm-chart + +defaults: + run: + shell: bash + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + build-image: + runs-on: ubuntu-24.04 + outputs: + image: ${{ steps.docker_image.outputs.image }} + image_repository: ${{ steps.docker_image.outputs.image_repository }} + image_tag: ${{ steps.docker_image.outputs.image_tag }} + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Docker image metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: type=sha + - name: Extract Docker image name + id: docker_image + env: + IMAGE_TAGS: ${{ steps.meta.outputs.tags }} + run: | + IMAGE=$(echo "$IMAGE_TAGS" | cut -d" " -f1) + IMAGE_REPOSITORY=$(echo "$IMAGE" | cut -d":" -f1) + IMAGE_TAG=$(echo "$IMAGE" | cut -d":" -f2) + echo "image=$IMAGE" >> "$GITHUB_OUTPUT" + echo "image_repository=$IMAGE_REPOSITORY" >> "$GITHUB_OUTPUT" + echo "image_tag=$IMAGE_TAG" >> "$GITHUB_OUTPUT" + - name: Set up Docker buildx + uses: docker/setup-buildx-action@v3 + - name: Set up Docker + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push Docker image + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=registry,ref=${{ steps.docker_image.outputs.image_repository }}:buildcache + cache-to: type=registry,ref=${{ steps.docker_image.outputs.image_repository }}:buildcache,mode=max + +# TODO: add job to build and push the helm chart if needed (manual trigger)