Decouple term and remotecommand packages

This allows consumers of term to not pull in dependencies on
github.com/gorilla/websocket and github.com/moby/spdystream.

Kubernetes-commit: 640dabd58b04b72f646ed85947cb8b407b36dc08
This commit is contained in:
Mikhail Mazurskiy 2025-08-03 20:35:40 +10:00 committed by Kubernetes Publisher
parent 22d999d2fc
commit ce4d90902a
6 changed files with 68 additions and 21 deletions

View File

@ -42,6 +42,7 @@ import (
"k8s.io/kubectl/pkg/util/completion" "k8s.io/kubectl/pkg/util/completion"
"k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates" "k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubectl/pkg/util/term"
) )
var ( var (
@ -300,7 +301,9 @@ func (o *AttachOptions) Run() error {
sizePlusOne.Height++ sizePlusOne.Height++
// this call spawns a goroutine to monitor/update the terminal size // this call spawns a goroutine to monitor/update the terminal size
sizeQueue = t.MonitorSize(&sizePlusOne, size) sizeQueue = &terminalSizeQueueAdapter{
delegate: t.MonitorSize(&sizePlusOne, size),
}
} }
o.DisableStderr = true o.DisableStderr = true
@ -357,3 +360,18 @@ func (o *AttachOptions) reattachMessage(containerName string, rawTTY bool) strin
} }
return fmt.Sprintf("Session ended, resume using '%s %s -c %s -i -t' command when the pod is running", o.CommandName, o.Pod.Name, containerName) return fmt.Sprintf("Session ended, resume using '%s %s -c %s -i -t' command when the pod is running", o.CommandName, o.Pod.Name, containerName)
} }
type terminalSizeQueueAdapter struct {
delegate term.TerminalSizeQueue
}
func (a *terminalSizeQueueAdapter) Next() *remotecommand.TerminalSize {
next := a.delegate.Next()
if next == nil {
return nil
}
return &remotecommand.TerminalSize{
Width: next.Width,
Height: next.Height,
}
}

View File

@ -366,7 +366,9 @@ func (p *ExecOptions) Run() error {
var sizeQueue remotecommand.TerminalSizeQueue var sizeQueue remotecommand.TerminalSizeQueue
if t.Raw { if t.Raw {
// this call spawns a goroutine to monitor/update the terminal size // this call spawns a goroutine to monitor/update the terminal size
sizeQueue = t.MonitorSize(t.GetSize()) sizeQueue = &terminalSizeQueueAdapter{
delegate: t.MonitorSize(t.GetSize()),
}
// unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is // unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is
// true // true
@ -403,3 +405,18 @@ func (p *ExecOptions) Run() error {
return nil return nil
} }
type terminalSizeQueueAdapter struct {
delegate term.TerminalSizeQueue
}
func (a *terminalSizeQueueAdapter) Next() *remotecommand.TerminalSize {
next := a.delegate.Next()
if next == nil {
return nil
}
return &remotecommand.TerminalSize{
Width: next.Width,
Height: next.Height,
}
}

View File

@ -21,12 +21,28 @@ import (
"github.com/moby/term" "github.com/moby/term"
"k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/remotecommand"
) )
// TerminalSize represents the width and height of a terminal.
// It is the same as staging/src/k8s.io/client-go/tools/remotecommand.TerminalSize.
// Copied to decouple the packages. Terminal-related package should not depend on API client and vice versa.
type TerminalSize struct {
Width uint16
Height uint16
}
// TerminalSizeQueue is capable of returning terminal resize events as they occur.
// It is the same as staging/src/k8s.io/client-go/tools/remotecommand.TerminalSizeQueue.
// Copied to decouple the packages. Terminal-related package should not depend on API client and vice versa.
type TerminalSizeQueue interface {
// Next returns the new terminal size after the terminal has been resized. It returns nil when
// monitoring has been stopped.
Next() *TerminalSize
}
// GetSize returns the current size of the user's terminal. If it isn't a terminal, // GetSize returns the current size of the user's terminal. If it isn't a terminal,
// nil is returned. // nil is returned.
func (t TTY) GetSize() *remotecommand.TerminalSize { func (t TTY) GetSize() *TerminalSize {
outFd, isTerminal := term.GetFdInfo(t.Out) outFd, isTerminal := term.GetFdInfo(t.Out)
if !isTerminal { if !isTerminal {
return nil return nil
@ -35,19 +51,19 @@ func (t TTY) GetSize() *remotecommand.TerminalSize {
} }
// GetSize returns the current size of the terminal associated with fd. // GetSize returns the current size of the terminal associated with fd.
func GetSize(fd uintptr) *remotecommand.TerminalSize { func GetSize(fd uintptr) *TerminalSize {
winsize, err := term.GetWinsize(fd) winsize, err := term.GetWinsize(fd)
if err != nil { if err != nil {
runtime.HandleError(fmt.Errorf("unable to get terminal size: %v", err)) runtime.HandleError(fmt.Errorf("unable to get terminal size: %v", err))
return nil return nil
} }
return &remotecommand.TerminalSize{Width: winsize.Width, Height: winsize.Height} return &TerminalSize{Width: winsize.Width, Height: winsize.Height}
} }
// MonitorSize monitors the terminal's size. It returns a TerminalSizeQueue primed with // MonitorSize monitors the terminal's size. It returns a TerminalSizeQueue primed with
// initialSizes, or nil if there's no TTY present. // initialSizes, or nil if there's no TTY present.
func (t *TTY) MonitorSize(initialSizes ...*remotecommand.TerminalSize) remotecommand.TerminalSizeQueue { func (t *TTY) MonitorSize(initialSizes ...*TerminalSize) TerminalSizeQueue {
outFd, isTerminal := term.GetFdInfo(t.Out) outFd, isTerminal := term.GetFdInfo(t.Out)
if !isTerminal { if !isTerminal {
return nil return nil
@ -57,7 +73,7 @@ func (t *TTY) MonitorSize(initialSizes ...*remotecommand.TerminalSize) remotecom
t: *t, t: *t,
// make it buffered so we can send the initial terminal sizes without blocking, prior to starting // make it buffered so we can send the initial terminal sizes without blocking, prior to starting
// the streaming below // the streaming below
resizeChan: make(chan remotecommand.TerminalSize, len(initialSizes)), resizeChan: make(chan TerminalSize, len(initialSizes)),
stopResizing: make(chan struct{}), stopResizing: make(chan struct{}),
} }
@ -70,16 +86,16 @@ func (t *TTY) MonitorSize(initialSizes ...*remotecommand.TerminalSize) remotecom
type sizeQueue struct { type sizeQueue struct {
t TTY t TTY
// resizeChan receives a Size each time the user's terminal is resized. // resizeChan receives a Size each time the user's terminal is resized.
resizeChan chan remotecommand.TerminalSize resizeChan chan TerminalSize
stopResizing chan struct{} stopResizing chan struct{}
} }
// make sure sizeQueue implements the resize.TerminalSizeQueue interface // make sure sizeQueue implements the TerminalSizeQueue interface
var _ remotecommand.TerminalSizeQueue = &sizeQueue{} var _ TerminalSizeQueue = &sizeQueue{}
// monitorSize primes resizeChan with initialSizes and then monitors for resize events. With each // monitorSize primes resizeChan with initialSizes and then monitors for resize events. With each
// new event, it sends the current terminal size to resizeChan. // new event, it sends the current terminal size to resizeChan.
func (s *sizeQueue) monitorSize(outFd uintptr, initialSizes ...*remotecommand.TerminalSize) { func (s *sizeQueue) monitorSize(outFd uintptr, initialSizes ...*TerminalSize) {
// send the initial sizes // send the initial sizes
for i := range initialSizes { for i := range initialSizes {
if initialSizes[i] != nil { if initialSizes[i] != nil {
@ -87,7 +103,7 @@ func (s *sizeQueue) monitorSize(outFd uintptr, initialSizes ...*remotecommand.Te
} }
} }
resizeEvents := make(chan remotecommand.TerminalSize, 1) resizeEvents := make(chan TerminalSize, 1)
monitorResizeEvents(outFd, resizeEvents, s.stopResizing) monitorResizeEvents(outFd, resizeEvents, s.stopResizing)
@ -118,7 +134,7 @@ func (s *sizeQueue) monitorSize(outFd uintptr, initialSizes ...*remotecommand.Te
// Next returns the new terminal size after the terminal has been resized. It returns nil when // Next returns the new terminal size after the terminal has been resized. It returns nil when
// monitoring has been stopped. // monitoring has been stopped.
func (s *sizeQueue) Next() *remotecommand.TerminalSize { func (s *sizeQueue) Next() *TerminalSize {
size, ok := <-s.resizeChan size, ok := <-s.resizeChan
if !ok { if !ok {
return nil return nil

View File

@ -25,13 +25,12 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/remotecommand"
) )
// monitorResizeEvents spawns a goroutine that waits for SIGWINCH signals (these indicate the // monitorResizeEvents spawns a goroutine that waits for SIGWINCH signals (these indicate the
// terminal has resized). After receiving a SIGWINCH, this gets the terminal size and tries to send // terminal has resized). After receiving a SIGWINCH, this gets the terminal size and tries to send
// it to the resizeEvents channel. The goroutine stops when the stop channel is closed. // it to the resizeEvents channel. The goroutine stops when the stop channel is closed.
func monitorResizeEvents(fd uintptr, resizeEvents chan<- remotecommand.TerminalSize, stop chan struct{}) { func monitorResizeEvents(fd uintptr, resizeEvents chan<- TerminalSize, stop chan struct{}) {
go func() { go func() {
defer runtime.HandleCrash() defer runtime.HandleCrash()

View File

@ -20,13 +20,12 @@ import (
"time" "time"
"k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/remotecommand"
) )
// monitorResizeEvents spawns a goroutine that periodically gets the terminal size and tries to send // monitorResizeEvents spawns a goroutine that periodically gets the terminal size and tries to send
// it to the resizeEvents channel if the size has changed. The goroutine stops when the stop channel // it to the resizeEvents channel if the size has changed. The goroutine stops when the stop channel
// is closed. // is closed.
func monitorResizeEvents(fd uintptr, resizeEvents chan<- remotecommand.TerminalSize, stop chan struct{}) { func monitorResizeEvents(fd uintptr, resizeEvents chan<- TerminalSize, stop chan struct{}) {
go func() { go func() {
defer runtime.HandleCrash() defer runtime.HandleCrash()

View File

@ -23,8 +23,6 @@ import (
wordwrap "github.com/mitchellh/go-wordwrap" wordwrap "github.com/mitchellh/go-wordwrap"
"github.com/moby/term" "github.com/moby/term"
"k8s.io/client-go/tools/remotecommand"
) )
type wordWrapWriter struct { type wordWrapWriter struct {
@ -70,7 +68,7 @@ func NewWordWrapWriter(w io.Writer, limit uint) io.Writer {
} }
} }
func getTerminalLimitWidth(terminalSize *remotecommand.TerminalSize) uint { func getTerminalLimitWidth(terminalSize *TerminalSize) uint {
var limit uint var limit uint
switch { switch {
case terminalSize.Width >= 120: case terminalSize.Width >= 120: