From 8a023645f9fe07148a43bdecae8357423b4e8f10 Mon Sep 17 00:00:00 2001 From: stefan Date: Thu, 30 Apr 2026 11:27:56 +0200 Subject: [PATCH 1/2] Refactor ScaleExtender to use unified dao.Scaler Introduces a unified `dao.Scaler` interface and implementation to handle resource scaling logic, replacing direct type assertions. This simplifies the code and makes it more robust when dealing with different resource types. --- internal/view/scale_extender.go | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/internal/view/scale_extender.go b/internal/view/scale_extender.go index f9e4d00b06..35ecc375b6 100644 --- a/internal/view/scale_extender.go +++ b/internal/view/scale_extender.go @@ -109,20 +109,13 @@ func (s *ScaleExtender) replicasFromReady(_ string) (string, error) { } func (s *ScaleExtender) replicasFromScaleSubresource(sel string) (string, error) { - res, err := dao.AccessorFor(s.App().factory, s.GVR()) - if err != nil { - return "", err - } - - replicasGetter, ok := res.(dao.ReplicasGetter) - if !ok { - return "", fmt.Errorf("expecting a replicasGetter resource for %q", s.GVR()) - } - + var scaler dao.Scaler + scaler.Init(s.App().factory, s.GVR()) + ctx, cancel := context.WithTimeout(context.Background(), s.App().Conn().Config().CallTimeout()) defer cancel() - replicas, err := replicasGetter.Replicas(ctx, sel) + replicas, err := scaler.Replicas(ctx, sel) if err != nil { return "", err } @@ -145,12 +138,11 @@ func (s *ScaleExtender) makeScaleForm(fqns []string) (*tview.Form, error) { // For built-in resources or cases where we can't get the replicas from the CRD, we can // only try to get the number of copies from the READY field. if factor == "0" { - replicas, err := s.replicasFromReady(fqns[0]) - if err != nil { - return nil, err + if replicas, err := s.replicasFromReady(fqns[0]); err == nil { + factor = replicas + } else { + slog.Warn("Unable to read replicas from ready column", slogs.Error, err) } - - factor = replicas } } @@ -222,7 +214,9 @@ func (s *ScaleExtender) scale(ctx context.Context, path string, replicas int32) } scaler, ok := res.(dao.Scalable) if !ok { - return fmt.Errorf("expecting a scalable resource for %q", s.GVR()) + var genericScaler dao.Scaler + genericScaler.Init(s.App().factory, s.GVR()) + return genericScaler.Scale(ctx, path, replicas) } return scaler.Scale(ctx, path, replicas) From 708856c87ce05ccf1eb24e337dae9723ed7824e4 Mon Sep 17 00:00:00 2001 From: stefan Date: Mon, 25 May 2026 00:15:39 +0200 Subject: [PATCH 2/2] scale: refuse to open scale dialog when current count is unknown When scaling a single resource, the dialog used to pre-fill the replicas field with "0" whenever both the CRD scale subresource and the READY column failed to yield a count. A user pressing OK without editing would silently scale the workload to zero. makeScaleForm now uses the empty string as an "unknown" sentinel (so a legitimately scaled-to-zero workload still pre-fills 0) and returns an error when neither read path succeeds, which the caller surfaces via the flash bar. Bulk-scale keeps the documented "0 default, must type a target" behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/view/scale_extender.go | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/internal/view/scale_extender.go b/internal/view/scale_extender.go index 35ecc375b6..45058d8bee 100644 --- a/internal/view/scale_extender.go +++ b/internal/view/scale_extender.go @@ -124,7 +124,9 @@ func (s *ScaleExtender) replicasFromScaleSubresource(sel string) (string, error) } func (s *ScaleExtender) makeScaleForm(fqns []string) (*tview.Form, error) { - factor := "0" + // Use empty string as the "unknown" sentinel so a legitimate current + // replica count of 0 (e.g., paused workload) is not treated as failure. + factor := "" if len(fqns) == 1 { // If the CRD resource supports scaling, then first try to // read the replicas directly from the CRD. @@ -137,13 +139,30 @@ func (s *ScaleExtender) makeScaleForm(fqns []string) (*tview.Form, error) { // For built-in resources or cases where we can't get the replicas from the CRD, we can // only try to get the number of copies from the READY field. - if factor == "0" { - if replicas, err := s.replicasFromReady(fqns[0]); err == nil { + var readyErr error + if factor == "" { + replicas, err := s.replicasFromReady(fqns[0]) + if err == nil { factor = replicas } else { + readyErr = err slog.Warn("Unable to read replicas from ready column", slogs.Error, err) } } + + // Refuse to open the dialog when we cannot determine the current + // replica count — otherwise a user hitting OK without editing would + // silently scale the workload to zero. + if factor == "" { + if readyErr != nil { + return nil, fmt.Errorf("unable to determine current replica count: %w", readyErr) + } + return nil, fmt.Errorf("unable to determine current replica count for %s", fqns[0]) + } + } else { + // Bulk-scale: there is no single "current" value to pre-fill, so the + // user must explicitly enter a target. Keep the documented default. + factor = "0" } styles := s.App().Styles.Dialog()