mirror of
https://github.com/kubernetes/kubernetes.git
synced 2026-06-11 01:41:54 -04:00
Merge pull request #137298 from dims/dsri/cri-streaming-option-a-hardcut
cri streaming option a hardcut - add new staging repositories `streaming` and `cri-streaming`
This commit is contained in:
commit
2bd6c7fe3c
159 changed files with 3496 additions and 600 deletions
4
go.mod
4
go.mod
|
|
@ -96,6 +96,7 @@ require (
|
|||
k8s.io/controller-manager v0.0.0
|
||||
k8s.io/cri-api v0.0.0
|
||||
k8s.io/cri-client v0.0.0
|
||||
k8s.io/cri-streaming v0.0.0
|
||||
k8s.io/csi-translation-lib v0.0.0
|
||||
k8s.io/dynamic-resource-allocation v0.0.0
|
||||
k8s.io/endpointslice v0.0.0
|
||||
|
|
@ -113,6 +114,7 @@ require (
|
|||
k8s.io/mount-utils v0.0.0
|
||||
k8s.io/pod-security-admission v0.0.0
|
||||
k8s.io/sample-apiserver v0.0.0
|
||||
k8s.io/streaming v0.0.0
|
||||
k8s.io/system-validators v1.12.1
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730
|
||||
|
|
@ -231,6 +233,7 @@ replace (
|
|||
k8s.io/controller-manager => ./staging/src/k8s.io/controller-manager
|
||||
k8s.io/cri-api => ./staging/src/k8s.io/cri-api
|
||||
k8s.io/cri-client => ./staging/src/k8s.io/cri-client
|
||||
k8s.io/cri-streaming => ./staging/src/k8s.io/cri-streaming
|
||||
k8s.io/csi-translation-lib => ./staging/src/k8s.io/csi-translation-lib
|
||||
k8s.io/dynamic-resource-allocation => ./staging/src/k8s.io/dynamic-resource-allocation
|
||||
k8s.io/endpointslice => ./staging/src/k8s.io/endpointslice
|
||||
|
|
@ -248,4 +251,5 @@ replace (
|
|||
k8s.io/sample-apiserver => ./staging/src/k8s.io/sample-apiserver
|
||||
k8s.io/sample-cli-plugin => ./staging/src/k8s.io/sample-cli-plugin
|
||||
k8s.io/sample-controller => ./staging/src/k8s.io/sample-controller
|
||||
k8s.io/streaming => ./staging/src/k8s.io/streaming
|
||||
)
|
||||
|
|
|
|||
2
go.work
2
go.work
|
|
@ -20,6 +20,7 @@ use (
|
|||
./staging/src/k8s.io/controller-manager
|
||||
./staging/src/k8s.io/cri-api
|
||||
./staging/src/k8s.io/cri-client
|
||||
./staging/src/k8s.io/cri-streaming
|
||||
./staging/src/k8s.io/csi-translation-lib
|
||||
./staging/src/k8s.io/dynamic-resource-allocation
|
||||
./staging/src/k8s.io/endpointslice
|
||||
|
|
@ -37,4 +38,5 @@ use (
|
|||
./staging/src/k8s.io/sample-apiserver
|
||||
./staging/src/k8s.io/sample-cli-plugin
|
||||
./staging/src/k8s.io/sample-controller
|
||||
./staging/src/k8s.io/streaming
|
||||
)
|
||||
|
|
|
|||
286
pkg/client/tests/cri_streaming_spdy_compat_test.go
Normal file
286
pkg/client/tests/cri_streaming_spdy_compat_test.go
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
/*
|
||||
Copyright 2025 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
restclient "k8s.io/client-go/rest"
|
||||
remoteclient "k8s.io/client-go/tools/remotecommand"
|
||||
clientspdy "k8s.io/client-go/transport/spdy"
|
||||
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||
streaming "k8s.io/cri-streaming/pkg/streaming"
|
||||
remotecommandserver "k8s.io/cri-streaming/pkg/streaming/remotecommand"
|
||||
streamingspdy "k8s.io/streaming/pkg/httpstream/spdy"
|
||||
)
|
||||
|
||||
const (
|
||||
execContainerID = "cri-streaming-exec-container"
|
||||
attachContainerID = "cri-streaming-attach-container"
|
||||
|
||||
execInput = "exec-stdin"
|
||||
execOutput = "exec-stdout"
|
||||
execErr = "exec-stderr"
|
||||
|
||||
attachInput = "attach-stdin"
|
||||
attachOutput = "attach-stdout"
|
||||
attachErr = "attach-stderr"
|
||||
)
|
||||
|
||||
type fakeStreamingRuntime struct{}
|
||||
|
||||
func (*fakeStreamingRuntime) Exec(ctx context.Context, containerID string, cmd []string, in io.Reader, out, errStream io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error {
|
||||
if containerID != execContainerID {
|
||||
return fmt.Errorf("unexpected exec container ID: %q", containerID)
|
||||
}
|
||||
|
||||
stdinData, err := io.ReadAll(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if string(stdinData) != execInput {
|
||||
return fmt.Errorf("unexpected exec stdin: %q", string(stdinData))
|
||||
}
|
||||
|
||||
if _, err := io.WriteString(out, execOutput); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.WriteString(errStream, execErr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*fakeStreamingRuntime) Attach(ctx context.Context, containerID string, in io.Reader, out, errStream io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error {
|
||||
if containerID != attachContainerID {
|
||||
return fmt.Errorf("unexpected attach container ID: %q", containerID)
|
||||
}
|
||||
|
||||
stdinData, err := io.ReadAll(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if string(stdinData) != attachInput {
|
||||
return fmt.Errorf("unexpected attach stdin: %q", string(stdinData))
|
||||
}
|
||||
|
||||
if _, err := io.WriteString(out, attachOutput); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.WriteString(errStream, attachErr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*fakeStreamingRuntime) PortForward(ctx context.Context, podSandboxID string, port int32, stream io.ReadWriteCloser) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func newCRIStreamingTestServer(t *testing.T) (streaming.Server, *httptest.Server) {
|
||||
t.Helper()
|
||||
|
||||
var server streaming.Server
|
||||
httpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
server.ServeHTTP(w, r)
|
||||
}))
|
||||
|
||||
baseURL, err := url.Parse(httpServer.URL)
|
||||
if err != nil {
|
||||
httpServer.Close()
|
||||
t.Fatalf("failed to parse test server URL: %v", err)
|
||||
}
|
||||
|
||||
config := streaming.DefaultConfig
|
||||
config.BaseURL = baseURL
|
||||
|
||||
server, err = streaming.NewServer(config, &fakeStreamingRuntime{})
|
||||
if err != nil {
|
||||
httpServer.Close()
|
||||
t.Fatalf("failed to create cri streaming server: %v", err)
|
||||
}
|
||||
|
||||
return server, httpServer
|
||||
}
|
||||
|
||||
// Verifies that client-go's SPDY executor can successfully stream against
|
||||
// the extracted cri-streaming server for both Exec and Attach endpoints.
|
||||
func TestCRIStreamingSPDYExecAttachCompatibility(t *testing.T) {
|
||||
server, httpServer := newCRIStreamingTestServer(t)
|
||||
defer httpServer.Close()
|
||||
|
||||
t.Run("exec", func(t *testing.T) {
|
||||
response, err := server.GetExec(&runtimeapi.ExecRequest{
|
||||
ContainerId: execContainerID,
|
||||
Cmd: []string{"echo", "exec"},
|
||||
Stdin: true,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get exec URL: %v", err)
|
||||
}
|
||||
|
||||
requestURL, err := url.Parse(response.Url)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse exec URL: %v", err)
|
||||
}
|
||||
|
||||
executor, err := remoteclient.NewSPDYExecutor(&restclient.Config{Host: requestURL.Host}, "POST", requestURL)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build exec executor: %v", err)
|
||||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
err = executor.StreamWithContext(ctx, remoteclient.StreamOptions{
|
||||
Stdin: strings.NewReader(execInput),
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected exec stream error: %v", err)
|
||||
}
|
||||
|
||||
if got := stdout.String(); got != execOutput {
|
||||
t.Fatalf("unexpected exec stdout: %q", got)
|
||||
}
|
||||
if got := stderr.String(); got != execErr {
|
||||
t.Fatalf("unexpected exec stderr: %q", got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("attach", func(t *testing.T) {
|
||||
response, err := server.GetAttach(&runtimeapi.AttachRequest{
|
||||
ContainerId: attachContainerID,
|
||||
Stdin: true,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get attach URL: %v", err)
|
||||
}
|
||||
|
||||
requestURL, err := url.Parse(response.Url)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse attach URL: %v", err)
|
||||
}
|
||||
|
||||
executor, err := remoteclient.NewSPDYExecutor(&restclient.Config{Host: requestURL.Host}, "POST", requestURL)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build attach executor: %v", err)
|
||||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
err = executor.StreamWithContext(ctx, remoteclient.StreamOptions{
|
||||
Stdin: strings.NewReader(attachInput),
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected attach stream error: %v", err)
|
||||
}
|
||||
|
||||
if got := stdout.String(); got != attachOutput {
|
||||
t.Fatalf("unexpected attach stdout: %q", got)
|
||||
}
|
||||
if got := stderr.String(); got != attachErr {
|
||||
t.Fatalf("unexpected attach stderr: %q", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Verifies client-go's compatibility upgrader adapter path by building a
|
||||
// streaming SPDY roundtripper from k8s.io/streaming and adapting it into a
|
||||
// client-go remotecommand executor.
|
||||
func TestCRIStreamingSPDYExecCompatibilityWithStreamingUpgraderAdapter(t *testing.T) {
|
||||
server, httpServer := newCRIStreamingTestServer(t)
|
||||
defer httpServer.Close()
|
||||
|
||||
response, err := server.GetExec(&runtimeapi.ExecRequest{
|
||||
ContainerId: execContainerID,
|
||||
Cmd: []string{"echo", "exec"},
|
||||
Stdin: true,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get exec URL: %v", err)
|
||||
}
|
||||
|
||||
requestURL, err := url.Parse(response.Url)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to parse exec URL: %v", err)
|
||||
}
|
||||
|
||||
roundTripper, err := streamingspdy.NewRoundTripperWithConfig(streamingspdy.RoundTripperConfig{
|
||||
PingPeriod: 5 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build streaming roundtripper: %v", err)
|
||||
}
|
||||
|
||||
executor, err := remoteclient.NewSPDYExecutorForTransports(
|
||||
roundTripper,
|
||||
clientspdy.NewUpgraderForStreaming(roundTripper),
|
||||
"POST",
|
||||
requestURL,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build adapter-based executor: %v", err)
|
||||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
err = executor.StreamWithContext(ctx, remoteclient.StreamOptions{
|
||||
Stdin: strings.NewReader(execInput),
|
||||
Stdout: &stdout,
|
||||
Stderr: &stderr,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected exec stream error: %v", err)
|
||||
}
|
||||
|
||||
if got := stdout.String(); got != execOutput {
|
||||
t.Fatalf("unexpected exec stdout: %q", got)
|
||||
}
|
||||
if got := stderr.String(); got != execErr {
|
||||
t.Fatalf("unexpected exec stderr: %q", got)
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ package tests
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
|
@ -31,11 +32,12 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"github.com/gorilla/websocket"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
. "k8s.io/client-go/tools/portforward"
|
||||
"k8s.io/client-go/transport/spdy"
|
||||
"k8s.io/kubelet/pkg/cri/streaming/portforward"
|
||||
"k8s.io/cri-streaming/pkg/streaming/portforward"
|
||||
"k8s.io/streaming/pkg/httpstream/wsstream"
|
||||
)
|
||||
|
||||
// fakePortForwarder simulates port forwarding for testing. It implements
|
||||
|
|
@ -52,7 +54,7 @@ type fakePortForwarder struct {
|
|||
|
||||
var _ portforward.PortForwarder = &fakePortForwarder{}
|
||||
|
||||
func (pf *fakePortForwarder) PortForward(_ context.Context, name string, uid types.UID, port int32, stream io.ReadWriteCloser) error {
|
||||
func (pf *fakePortForwarder) PortForward(_ context.Context, name string, uid string, port int32, stream io.ReadWriteCloser) error {
|
||||
defer stream.Close()
|
||||
|
||||
// read from the client
|
||||
|
|
@ -249,3 +251,108 @@ func TestForwardPortsReturnsErrorWhenAllBindsFailed(t *testing.T) {
|
|||
t.Fatal("expected non-nil error for pf2.ForwardPorts")
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardPortsWebSocketV4Framing(t *testing.T) {
|
||||
const testPort int32 = 5001
|
||||
const clientPayload = "abcd"
|
||||
const serverPayload = "1234"
|
||||
|
||||
done := make(chan struct{})
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
defer close(done)
|
||||
|
||||
opts, err := portforward.NewV4Options(req)
|
||||
if err != nil {
|
||||
t.Errorf("failed to build v4 options: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
pf := &fakePortForwarder{
|
||||
expected: map[int32]string{testPort: clientPayload},
|
||||
received: make(map[int32]string),
|
||||
send: map[int32]string{testPort: serverPayload},
|
||||
}
|
||||
portforward.ServePortForward(w, req, pf, "pod", "uid", opts, 0, 10*time.Second, portforward.SupportedProtocols)
|
||||
|
||||
got, ok := pf.received[testPort]
|
||||
if !ok {
|
||||
t.Errorf("server did not receive data for port %d", testPort)
|
||||
return
|
||||
}
|
||||
if got != clientPayload {
|
||||
t.Errorf("server expected %q, got %q for port %d", clientPayload, got, testPort)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
wsURL := strings.Replace(server.URL, "http://", "ws://", 1) + fmt.Sprintf("?port=%d", testPort)
|
||||
dialer := &websocket.Dialer{
|
||||
Subprotocols: []string{"v4." + wsstream.ChannelWebSocketProtocol},
|
||||
}
|
||||
conn, _, err := dialer.Dial(wsURL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("websocket dial failed: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
expectedPortBytes := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(expectedPortBytes, uint16(testPort))
|
||||
|
||||
seenDataPreamble := false
|
||||
seenErrorPreamble := false
|
||||
for !(seenDataPreamble && seenErrorPreamble) {
|
||||
_, frame, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
t.Fatalf("failed reading websocket preamble frame: %v", err)
|
||||
}
|
||||
if len(frame) == 0 {
|
||||
continue
|
||||
}
|
||||
channel := frame[0]
|
||||
payload := frame[1:]
|
||||
switch channel {
|
||||
case 0:
|
||||
if !bytes.Equal(expectedPortBytes, payload) {
|
||||
t.Fatalf("unexpected data-channel preamble payload: %q", payload)
|
||||
}
|
||||
seenDataPreamble = true
|
||||
case 1:
|
||||
if !bytes.Equal(expectedPortBytes, payload) {
|
||||
t.Fatalf("unexpected error-channel preamble payload: %q", payload)
|
||||
}
|
||||
seenErrorPreamble = true
|
||||
}
|
||||
}
|
||||
|
||||
frame := append([]byte{0}, []byte(clientPayload)...)
|
||||
if err := conn.WriteMessage(websocket.BinaryMessage, frame); err != nil {
|
||||
t.Fatalf("failed writing data frame: %v", err)
|
||||
}
|
||||
|
||||
gotServerPayload := false
|
||||
for !gotServerPayload {
|
||||
_, frame, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
t.Fatalf("failed reading websocket response frame: %v", err)
|
||||
}
|
||||
if len(frame) == 0 {
|
||||
continue
|
||||
}
|
||||
if frame[0] != 0 {
|
||||
continue
|
||||
}
|
||||
if bytes.Equal(frame[1:], []byte(serverPayload)) {
|
||||
gotServerPayload = true
|
||||
}
|
||||
}
|
||||
|
||||
if err := conn.Close(); err != nil {
|
||||
t.Fatalf("failed closing websocket connection: %v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("timeout waiting for websocket portforward handler to complete")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,15 +31,14 @@ import (
|
|||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
remoteclient "k8s.io/client-go/tools/remotecommand"
|
||||
"k8s.io/client-go/transport/spdy"
|
||||
"k8s.io/kubelet/pkg/cri/streaming/remotecommand"
|
||||
"k8s.io/cri-streaming/pkg/streaming/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
)
|
||||
|
||||
type fakeExecutor struct {
|
||||
|
|
@ -56,22 +55,22 @@ type fakeExecutor struct {
|
|||
exec bool
|
||||
}
|
||||
|
||||
func (ex *fakeExecutor) ExecInContainer(_ context.Context, name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remoteclient.TerminalSize, timeout time.Duration) error {
|
||||
func (ex *fakeExecutor) ExecInContainer(_ context.Context, name string, uid string, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
return ex.run(name, uid, container, cmd, in, out, err, tty)
|
||||
}
|
||||
|
||||
func (ex *fakeExecutor) AttachContainer(_ context.Context, name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remoteclient.TerminalSize) error {
|
||||
func (ex *fakeExecutor) AttachContainer(_ context.Context, name string, uid string, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
return ex.run(name, uid, container, nil, in, out, err, tty)
|
||||
}
|
||||
|
||||
func (ex *fakeExecutor) run(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error {
|
||||
func (ex *fakeExecutor) run(name string, uid string, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool) error {
|
||||
ex.command = cmd
|
||||
ex.tty = tty
|
||||
|
||||
if e, a := "pod", name; e != a {
|
||||
ex.t.Errorf("%s: pod: expected %q, got %q", ex.testName, e, a)
|
||||
}
|
||||
if e, a := "uid", uid; e != string(a) {
|
||||
if e, a := "uid", uid; e != a {
|
||||
ex.t.Errorf("%s: uid: expected %q, got %q", ex.testName, e, a)
|
||||
}
|
||||
if ex.exec {
|
||||
|
|
@ -339,7 +338,7 @@ func TestDial(t *testing.T) {
|
|||
Body: io.NopCloser(&bytes.Buffer{}),
|
||||
},
|
||||
}
|
||||
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: upgrader}, "POST", &url.URL{Host: "something.com", Scheme: "https"})
|
||||
dialer := spdy.NewDialerForStreaming(spdy.NewUpgraderForStreaming(upgrader), &http.Client{Transport: upgrader}, "POST", &url.URL{Host: "something.com", Scheme: "https"})
|
||||
conn, protocol, err := dialer.Dial("protocol1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@ import (
|
|||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
resourcehelper "k8s.io/component-helpers/resource"
|
||||
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||
"k8s.io/cri-streaming/pkg/streaming/portforward"
|
||||
remotecommandserver "k8s.io/cri-streaming/pkg/streaming/remotecommand"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubelet/pkg/cri/streaming/portforward"
|
||||
remotecommandserver "k8s.io/kubelet/pkg/cri/streaming/remotecommand"
|
||||
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
||||
"k8s.io/kubernetes/pkg/api/v1/resource"
|
||||
podshelper "k8s.io/kubernetes/pkg/apis/core/pods"
|
||||
|
|
|
|||
|
|
@ -49,8 +49,8 @@ import (
|
|||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
"k8s.io/component-base/metrics/testutil"
|
||||
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||
"k8s.io/kubelet/pkg/cri/streaming/portforward"
|
||||
"k8s.io/kubelet/pkg/cri/streaming/remotecommand"
|
||||
"k8s.io/cri-streaming/pkg/streaming/portforward"
|
||||
"k8s.io/cri-streaming/pkg/streaming/remotecommand"
|
||||
_ "k8s.io/kubernetes/pkg/apis/core/install"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
|
|
|
|||
|
|
@ -77,11 +77,11 @@ import (
|
|||
zpagesfeatures "k8s.io/component-base/zpages/features"
|
||||
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||
"k8s.io/cri-client/pkg/util"
|
||||
"k8s.io/cri-streaming/pkg/streaming"
|
||||
"k8s.io/cri-streaming/pkg/streaming/portforward"
|
||||
remotecommandserver "k8s.io/cri-streaming/pkg/streaming/remotecommand"
|
||||
podresourcesapi "k8s.io/kubelet/pkg/apis/podresources/v1"
|
||||
podresourcesapiv1alpha1 "k8s.io/kubelet/pkg/apis/podresources/v1alpha1"
|
||||
"k8s.io/kubelet/pkg/cri/streaming"
|
||||
"k8s.io/kubelet/pkg/cri/streaming/portforward"
|
||||
remotecommandserver "k8s.io/kubelet/pkg/cri/streaming/remotecommand"
|
||||
kubelettypes "k8s.io/kubelet/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
|
|
|
|||
|
|
@ -50,8 +50,6 @@ import (
|
|||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
|
|
@ -60,11 +58,13 @@ import (
|
|||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
remotecommand "k8s.io/client-go/tools/remotecommand"
|
||||
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||
statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/test/utils/ktesting"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
"k8s.io/streaming/pkg/httpstream/spdy"
|
||||
|
||||
// Do some initialization to decode the query parameters correctly.
|
||||
"k8s.io/apiserver/pkg/server/dynamiccertificates"
|
||||
|
|
@ -75,9 +75,9 @@ import (
|
|||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
"k8s.io/component-base/metrics/testutil"
|
||||
zpagesfeatures "k8s.io/component-base/zpages/features"
|
||||
"k8s.io/kubelet/pkg/cri/streaming"
|
||||
"k8s.io/kubelet/pkg/cri/streaming/portforward"
|
||||
remotecommandserver "k8s.io/kubelet/pkg/cri/streaming/remotecommand"
|
||||
"k8s.io/cri-streaming/pkg/streaming"
|
||||
"k8s.io/cri-streaming/pkg/streaming/portforward"
|
||||
remotecommandserver "k8s.io/cri-streaming/pkg/streaming/remotecommand"
|
||||
_ "k8s.io/kubernetes/pkg/apis/core/install"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config"
|
||||
|
|
@ -180,16 +180,16 @@ func (fk *fakeKubelet) SyncLoopHealthCheck(req *http.Request) error {
|
|||
}
|
||||
|
||||
type fakeRuntime struct {
|
||||
execFunc func(string, []string, io.Reader, io.WriteCloser, io.WriteCloser, bool, <-chan remotecommand.TerminalSize) error
|
||||
attachFunc func(string, io.Reader, io.WriteCloser, io.WriteCloser, bool, <-chan remotecommand.TerminalSize) error
|
||||
execFunc func(string, []string, io.Reader, io.WriteCloser, io.WriteCloser, bool, <-chan remotecommandserver.TerminalSize) error
|
||||
attachFunc func(string, io.Reader, io.WriteCloser, io.WriteCloser, bool, <-chan remotecommandserver.TerminalSize) error
|
||||
portForwardFunc func(string, int32, io.ReadWriteCloser) error
|
||||
}
|
||||
|
||||
func (f *fakeRuntime) Exec(_ context.Context, containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
func (f *fakeRuntime) Exec(_ context.Context, containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error {
|
||||
return f.execFunc(containerID, cmd, stdin, stdout, stderr, tty, resize)
|
||||
}
|
||||
|
||||
func (f *fakeRuntime) Attach(_ context.Context, containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
func (f *fakeRuntime) Attach(_ context.Context, containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error {
|
||||
return f.attachFunc(containerID, stdin, stdout, stderr, tty, resize)
|
||||
}
|
||||
|
||||
|
|
@ -1544,12 +1544,12 @@ func testExecAttach(t *testing.T, verb string) {
|
|||
return nil
|
||||
}
|
||||
|
||||
ss.fakeRuntime.execFunc = func(containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
ss.fakeRuntime.execFunc = func(containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error {
|
||||
assert.Equal(t, expectedCommand, strings.Join(cmd, " "), "cmd")
|
||||
return testStream(containerID, stdin, stdout, stderr, tty, done)
|
||||
}
|
||||
|
||||
ss.fakeRuntime.attachFunc = func(containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
ss.fakeRuntime.attachFunc = func(containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error {
|
||||
return testStream(containerID, stdin, stdout, stderr, tty, done)
|
||||
}
|
||||
|
||||
|
|
@ -1686,7 +1686,7 @@ func TestWebsocketExecAttach(t *testing.T) {
|
|||
attachInvoked = true
|
||||
}
|
||||
|
||||
ss.fakeRuntime.attachFunc = func(containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
ss.fakeRuntime.attachFunc = func(containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error {
|
||||
defer close(done)
|
||||
defer stdout.Close() //nolint:errcheck
|
||||
_, err := io.Copy(stdout, stdin)
|
||||
|
|
@ -2271,7 +2271,7 @@ func TestGetExecWebSocketHandlerSelection(t *testing.T) {
|
|||
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExtendWebSocketsToKubelet, tt.enableExtendWebSockets)
|
||||
|
||||
ss.fakeRuntime.execFunc = func(_ string, _ []string, _ io.Reader, stdout, _ io.WriteCloser, _ bool, _ <-chan remotecommand.TerminalSize) error {
|
||||
ss.fakeRuntime.execFunc = func(_ string, _ []string, _ io.Reader, stdout, _ io.WriteCloser, _ bool, _ <-chan remotecommandserver.TerminalSize) error {
|
||||
stdout.Close() //nolint:errcheck
|
||||
return nil
|
||||
}
|
||||
|
|
@ -2361,7 +2361,7 @@ func TestGetAttachWebSocketHandlerSelection(t *testing.T) {
|
|||
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExtendWebSocketsToKubelet, tt.enableExtendWebSockets)
|
||||
|
||||
ss.fakeRuntime.attachFunc = func(_ string, _ io.Reader, stdout, _ io.WriteCloser, _ bool, _ <-chan remotecommand.TerminalSize) error {
|
||||
ss.fakeRuntime.attachFunc = func(_ string, _ io.Reader, stdout, _ io.WriteCloser, _ bool, _ <-chan remotecommandserver.TerminalSize) error {
|
||||
stdout.Close() //nolint:errcheck
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import (
|
|||
"golang.org/x/net/websocket"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubelet/pkg/cri/streaming/portforward"
|
||||
"k8s.io/cri-streaming/pkg/streaming/portforward"
|
||||
"k8s.io/kubernetes/test/utils/ktesting"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import (
|
|||
"slices"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/wsstream"
|
||||
"k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/proxy"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
|
|
@ -38,6 +37,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/kubelet/client"
|
||||
"k8s.io/kubernetes/pkg/registry/core/pod"
|
||||
"k8s.io/streaming/pkg/httpstream/wsstream"
|
||||
)
|
||||
|
||||
// ProxyREST implements the proxy subresource for a Pod
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ Repositories currently staged here:
|
|||
- [`k8s.io/controller-manager`](https://github.com/kubernetes/controller-manager)
|
||||
- [`k8s.io/cri-api`](https://github.com/kubernetes/cri-api)
|
||||
- [`k8s.io/cri-client`](https://github.com/kubernetes/cri-client)
|
||||
- [`k8s.io/cri-streaming`](https://github.com/kubernetes/cri-streaming)
|
||||
- [`k8s.io/csi-translation-lib`](https://github.com/kubernetes/csi-translation-lib)
|
||||
- [`k8s.io/dynamic-resource-allocation`](https://github.com/kubernetes/dynamic-resource-allocation)
|
||||
- [`k8s.io/endpointslice`](https://github.com/kubernetes/endpointslice)
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
allowedImports:
|
||||
- k8s.io/apimachinery
|
||||
- k8s.io/kube-openapi
|
||||
- k8s.io/streaming
|
||||
- k8s.io/utils/clock
|
||||
- k8s.io/utils/dump
|
||||
- k8s.io/utils/net
|
||||
|
|
@ -80,6 +81,7 @@
|
|||
- k8s.io/client-go
|
||||
- k8s.io/klog
|
||||
- k8s.io/kube-openapi
|
||||
- k8s.io/streaming
|
||||
- k8s.io/utils
|
||||
|
||||
# prevent core machinery from taking explicit v1 references unless
|
||||
|
|
@ -116,6 +118,7 @@
|
|||
- k8s.io/client-go
|
||||
- k8s.io/component-base
|
||||
- k8s.io/kube-openapi
|
||||
- k8s.io/streaming
|
||||
- k8s.io/utils
|
||||
- k8s.io/klog
|
||||
- k8s.io/kms
|
||||
|
|
@ -139,6 +142,7 @@
|
|||
- k8s.io/component-base
|
||||
- k8s.io/kube-aggregator
|
||||
- k8s.io/kube-openapi
|
||||
- k8s.io/streaming
|
||||
- k8s.io/klog
|
||||
- k8s.io/utils
|
||||
|
||||
|
|
@ -153,6 +157,7 @@
|
|||
- k8s.io/kubectl
|
||||
- k8s.io/kube-openapi
|
||||
- k8s.io/metrics
|
||||
- k8s.io/streaming
|
||||
- k8s.io/utils
|
||||
- k8s.io/klog
|
||||
|
||||
|
|
@ -327,6 +332,20 @@
|
|||
- k8s.io/klog/v2
|
||||
- k8s.io/utils
|
||||
|
||||
- baseImportPath: "./staging/src/k8s.io/cri-streaming"
|
||||
allowedImports:
|
||||
- k8s.io/cri-api
|
||||
- k8s.io/cri-streaming
|
||||
- k8s.io/klog/v2
|
||||
- k8s.io/streaming
|
||||
- k8s.io/utils
|
||||
|
||||
- baseImportPath: "./staging/src/k8s.io/streaming"
|
||||
allowedImports:
|
||||
- k8s.io/klog/v2
|
||||
- k8s.io/streaming
|
||||
- k8s.io/utils
|
||||
|
||||
- baseImportPath: "./staging/src/k8s.io/externaljwt"
|
||||
allowedImports:
|
||||
- k8s.io/externaljwt
|
||||
|
|
|
|||
|
|
@ -1,7 +1,18 @@
|
|||
rules:
|
||||
- destination: streaming
|
||||
branches:
|
||||
- name: master
|
||||
source:
|
||||
branch: master
|
||||
dirs:
|
||||
- staging/src/k8s.io/streaming
|
||||
library: true
|
||||
- destination: apimachinery
|
||||
branches:
|
||||
- name: master
|
||||
dependencies:
|
||||
- repository: streaming
|
||||
branch: master
|
||||
source:
|
||||
branch: master
|
||||
dirs:
|
||||
|
|
@ -33,6 +44,8 @@ rules:
|
|||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
source:
|
||||
branch: master
|
||||
dirs:
|
||||
|
|
@ -76,6 +89,8 @@ rules:
|
|||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: api
|
||||
branch: master
|
||||
source:
|
||||
|
|
@ -149,6 +164,8 @@ rules:
|
|||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
source:
|
||||
branch: master
|
||||
dirs:
|
||||
|
|
@ -191,6 +208,8 @@ rules:
|
|||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: api
|
||||
branch: master
|
||||
- repository: client-go
|
||||
|
|
@ -254,6 +273,8 @@ rules:
|
|||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: api
|
||||
branch: master
|
||||
- repository: client-go
|
||||
|
|
@ -317,6 +338,8 @@ rules:
|
|||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
source:
|
||||
branch: master
|
||||
dirs:
|
||||
|
|
@ -360,6 +383,8 @@ rules:
|
|||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: api
|
||||
branch: master
|
||||
- repository: client-go
|
||||
|
|
@ -443,6 +468,8 @@ rules:
|
|||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: api
|
||||
branch: master
|
||||
- repository: client-go
|
||||
|
|
@ -545,6 +572,8 @@ rules:
|
|||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: api
|
||||
branch: master
|
||||
- repository: client-go
|
||||
|
|
@ -672,6 +701,8 @@ rules:
|
|||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: api
|
||||
branch: master
|
||||
- repository: client-go
|
||||
|
|
@ -769,6 +800,8 @@ rules:
|
|||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: api
|
||||
branch: master
|
||||
- repository: client-go
|
||||
|
|
@ -881,6 +914,8 @@ rules:
|
|||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: api
|
||||
branch: master
|
||||
- repository: client-go
|
||||
|
|
@ -956,6 +991,8 @@ rules:
|
|||
branch: master
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: client-go
|
||||
branch: master
|
||||
source:
|
||||
|
|
@ -1019,6 +1056,8 @@ rules:
|
|||
branch: master
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: cli-runtime
|
||||
branch: master
|
||||
- repository: client-go
|
||||
|
|
@ -1089,6 +1128,8 @@ rules:
|
|||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: component-base
|
||||
branch: master
|
||||
- repository: api
|
||||
|
|
@ -1192,6 +1233,8 @@ rules:
|
|||
branch: master
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: client-go
|
||||
branch: master
|
||||
- repository: component-base
|
||||
|
|
@ -1267,24 +1310,33 @@ rules:
|
|||
dirs:
|
||||
- staging/src/k8s.io/cri-client
|
||||
library: true
|
||||
- destination: cri-streaming
|
||||
branches:
|
||||
- name: master
|
||||
dependencies:
|
||||
- repository: cri-api
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
source:
|
||||
branch: master
|
||||
dirs:
|
||||
- staging/src/k8s.io/cri-streaming
|
||||
library: true
|
||||
- destination: kubelet
|
||||
branches:
|
||||
- name: master
|
||||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: apiserver
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: api
|
||||
branch: master
|
||||
- repository: client-go
|
||||
branch: master
|
||||
- repository: cri-api
|
||||
branch: master
|
||||
- repository: component-base
|
||||
branch: master
|
||||
- repository: kms
|
||||
branch: master
|
||||
source:
|
||||
branch: master
|
||||
dirs:
|
||||
|
|
@ -1378,6 +1430,8 @@ rules:
|
|||
branch: master
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: client-go
|
||||
branch: master
|
||||
- repository: component-base
|
||||
|
|
@ -1471,6 +1525,8 @@ rules:
|
|||
branch: master
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: apiserver
|
||||
branch: master
|
||||
- repository: client-go
|
||||
|
|
@ -1582,6 +1638,8 @@ rules:
|
|||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: apiserver
|
||||
branch: master
|
||||
- repository: component-base
|
||||
|
|
@ -1705,6 +1763,8 @@ rules:
|
|||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: api
|
||||
branch: master
|
||||
source:
|
||||
|
|
@ -1760,6 +1820,8 @@ rules:
|
|||
branch: master
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
source:
|
||||
branch: master
|
||||
dirs:
|
||||
|
|
@ -1841,6 +1903,8 @@ rules:
|
|||
branch: master
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: cli-runtime
|
||||
branch: master
|
||||
- repository: client-go
|
||||
|
|
@ -1954,6 +2018,8 @@ rules:
|
|||
branch: master
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: apiserver
|
||||
branch: master
|
||||
- repository: client-go
|
||||
|
|
@ -2045,14 +2111,14 @@ rules:
|
|||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: apiserver
|
||||
branch: master
|
||||
- repository: api
|
||||
branch: master
|
||||
- repository: client-go
|
||||
branch: master
|
||||
- repository: cri-api
|
||||
branch: master
|
||||
- repository: component-base
|
||||
branch: master
|
||||
- repository: component-helpers
|
||||
|
|
@ -2167,6 +2233,8 @@ rules:
|
|||
dependencies:
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: component-base
|
||||
branch: master
|
||||
- repository: api
|
||||
|
|
@ -2272,6 +2340,8 @@ rules:
|
|||
branch: master
|
||||
- repository: apimachinery
|
||||
branch: master
|
||||
- repository: streaming
|
||||
branch: master
|
||||
- repository: client-go
|
||||
branch: master
|
||||
- repository: component-base
|
||||
|
|
|
|||
|
|
@ -35,4 +35,7 @@ require (
|
|||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
)
|
||||
|
||||
replace k8s.io/apimachinery => ../apimachinery
|
||||
replace (
|
||||
k8s.io/apimachinery => ../apimachinery
|
||||
k8s.io/streaming => ../streaming
|
||||
)
|
||||
|
|
|
|||
1
staging/src/k8s.io/api/go.sum
generated
1
staging/src/k8s.io/api/go.sum
generated
|
|
@ -1,5 +1,4 @@
|
|||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
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=
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ require (
|
|||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect
|
||||
k8s.io/kms v0.0.0 // indirect
|
||||
k8s.io/streaming v0.0.0 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect
|
||||
)
|
||||
|
||||
|
|
@ -133,4 +134,5 @@ replace (
|
|||
k8s.io/code-generator => ../code-generator
|
||||
k8s.io/component-base => ../component-base
|
||||
k8s.io/kms => ../kms
|
||||
k8s.io/streaming => ../streaming
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,12 +7,10 @@ go 1.26.0
|
|||
godebug default=go1.26
|
||||
|
||||
require (
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||
github.com/fxamacker/cbor/v2 v2.9.0
|
||||
github.com/google/gnostic-models v0.7.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/moby/spdystream v0.5.0
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
|
||||
github.com/spf13/pflag v1.0.9
|
||||
|
|
@ -23,6 +21,7 @@ require (
|
|||
gopkg.in/inf.v0 v0.9.1
|
||||
k8s.io/klog/v2 v2.140.0
|
||||
k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf
|
||||
k8s.io/streaming v0.0.0
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730
|
||||
sigs.k8s.io/randfill v1.0.0
|
||||
|
|
@ -39,6 +38,7 @@ require (
|
|||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/moby/spdystream v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.28.1 // indirect
|
||||
|
|
@ -51,3 +51,5 @@ require (
|
|||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace k8s.io/streaming => ../streaming
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package httpstream adds multiplexed streaming support to HTTP requests and
|
||||
// responses via connection upgrades.
|
||||
// Package httpstream contains compatibility wrappers for streaming transport APIs.
|
||||
//
|
||||
// Deprecated: use k8s.io/streaming/pkg/httpstream directly.
|
||||
package httpstream
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package spdy contains compatibility wrappers for the SPDY transport stack.
|
||||
//
|
||||
// Deprecated: use k8s.io/streaming/pkg/httpstream/spdy directly.
|
||||
package spdy
|
||||
236
staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/spdy.go
Normal file
236
staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/spdy.go
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package spdy
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
apihttpstream "k8s.io/apimachinery/pkg/util/httpstream"
|
||||
streamhttp "k8s.io/streaming/pkg/httpstream"
|
||||
streamspdy "k8s.io/streaming/pkg/httpstream/spdy"
|
||||
)
|
||||
|
||||
const HeaderSpdy31 = streamspdy.HeaderSpdy31
|
||||
|
||||
// SpdyRoundTripper is a compatibility wrapper around the streaming module's
|
||||
// SPDY round tripper.
|
||||
type SpdyRoundTripper struct {
|
||||
delegate *streamspdy.SpdyRoundTripper
|
||||
}
|
||||
|
||||
func NewRoundTripper(tlsConfig *tls.Config) (*SpdyRoundTripper, error) {
|
||||
delegate, err := streamspdy.NewRoundTripper(tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SpdyRoundTripper{delegate: delegate}, nil
|
||||
}
|
||||
|
||||
func NewRoundTripperWithProxy(tlsConfig *tls.Config, proxier func(*http.Request) (*url.URL, error)) (*SpdyRoundTripper, error) {
|
||||
delegate, err := streamspdy.NewRoundTripperWithProxy(tlsConfig, proxier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SpdyRoundTripper{delegate: delegate}, nil
|
||||
}
|
||||
|
||||
// RoundTripperConfig is a set of options for an SpdyRoundTripper.
|
||||
type RoundTripperConfig struct {
|
||||
// TLS configuration used by the round tripper if UpgradeTransport not present.
|
||||
TLS *tls.Config
|
||||
// Proxier is a proxy function invoked on each request. Optional.
|
||||
Proxier func(*http.Request) (*url.URL, error)
|
||||
// PingPeriod is a period for sending SPDY Pings on the connection.
|
||||
// Optional.
|
||||
PingPeriod time.Duration
|
||||
// UpgradeTransport is a subtitute transport used for dialing. If set,
|
||||
// this field will be used instead of "TLS" and "Proxier" for connection creation.
|
||||
// Optional.
|
||||
UpgradeTransport http.RoundTripper
|
||||
}
|
||||
|
||||
func NewRoundTripperWithConfig(cfg RoundTripperConfig) (*SpdyRoundTripper, error) {
|
||||
delegate, err := streamspdy.NewRoundTripperWithConfig(streamspdy.RoundTripperConfig{
|
||||
TLS: cfg.TLS,
|
||||
Proxier: cfg.Proxier,
|
||||
PingPeriod: cfg.PingPeriod,
|
||||
UpgradeTransport: cfg.UpgradeTransport,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SpdyRoundTripper{delegate: delegate}, nil
|
||||
}
|
||||
|
||||
// TLSClientConfig implements pkg/util/net.TLSClientConfigHolder for proper TLS checking during
|
||||
// proxying with a spdy roundtripper.
|
||||
func (s *SpdyRoundTripper) TLSClientConfig() *tls.Config {
|
||||
return s.delegate.TLSClientConfig()
|
||||
}
|
||||
|
||||
// Dial opens a network connection for an upgrade request.
|
||||
func (s *SpdyRoundTripper) Dial(req *http.Request) (net.Conn, error) {
|
||||
return s.delegate.Dial(req)
|
||||
}
|
||||
|
||||
// RoundTrip executes a request and upgrades the connection.
|
||||
func (s *SpdyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return s.delegate.RoundTrip(req)
|
||||
}
|
||||
|
||||
// NewConnection validates a server upgrade response and prepares the transport.
|
||||
func (s *SpdyRoundTripper) NewConnection(resp *http.Response) (apihttpstream.Connection, error) {
|
||||
conn, err := s.delegate.NewConnection(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wrapConnection(conn), nil
|
||||
}
|
||||
|
||||
type responseUpgraderAdapter struct {
|
||||
delegate streamhttp.ResponseUpgrader
|
||||
}
|
||||
|
||||
func (r *responseUpgraderAdapter) UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler apihttpstream.NewStreamHandler) apihttpstream.Connection {
|
||||
conn := r.delegate.UpgradeResponse(w, req, wrapNewStreamHandler(newStreamHandler))
|
||||
return wrapConnection(conn)
|
||||
}
|
||||
|
||||
func NewResponseUpgrader() apihttpstream.ResponseUpgrader {
|
||||
return &responseUpgraderAdapter{delegate: streamspdy.NewResponseUpgrader()}
|
||||
}
|
||||
|
||||
func NewResponseUpgraderWithPings(pingPeriod time.Duration) apihttpstream.ResponseUpgrader {
|
||||
return &responseUpgraderAdapter{delegate: streamspdy.NewResponseUpgraderWithPings(pingPeriod)}
|
||||
}
|
||||
|
||||
func NewClientConnection(conn net.Conn) (apihttpstream.Connection, error) {
|
||||
c, err := streamspdy.NewClientConnection(conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wrapConnection(c), nil
|
||||
}
|
||||
|
||||
func NewClientConnectionWithPings(conn net.Conn, pingPeriod time.Duration) (apihttpstream.Connection, error) {
|
||||
c, err := streamspdy.NewClientConnectionWithPings(conn, pingPeriod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wrapConnection(c), nil
|
||||
}
|
||||
|
||||
func NewServerConnection(conn net.Conn, newStreamHandler apihttpstream.NewStreamHandler) (apihttpstream.Connection, error) {
|
||||
c, err := streamspdy.NewServerConnection(conn, wrapNewStreamHandler(newStreamHandler))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wrapConnection(c), nil
|
||||
}
|
||||
|
||||
func NewServerConnectionWithPings(conn net.Conn, newStreamHandler apihttpstream.NewStreamHandler, pingPeriod time.Duration) (apihttpstream.Connection, error) {
|
||||
c, err := streamspdy.NewServerConnectionWithPings(conn, wrapNewStreamHandler(newStreamHandler), pingPeriod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wrapConnection(c), nil
|
||||
}
|
||||
|
||||
type streamAdapter struct {
|
||||
delegate streamhttp.Stream
|
||||
}
|
||||
|
||||
func (s *streamAdapter) Read(p []byte) (int, error) {
|
||||
return s.delegate.Read(p)
|
||||
}
|
||||
|
||||
func (s *streamAdapter) Write(p []byte) (int, error) {
|
||||
return s.delegate.Write(p)
|
||||
}
|
||||
|
||||
func (s *streamAdapter) Close() error {
|
||||
return s.delegate.Close()
|
||||
}
|
||||
|
||||
func (s *streamAdapter) Reset() error {
|
||||
return s.delegate.Reset()
|
||||
}
|
||||
|
||||
func (s *streamAdapter) Headers() http.Header {
|
||||
return s.delegate.Headers()
|
||||
}
|
||||
|
||||
func (s *streamAdapter) Identifier() uint32 {
|
||||
return s.delegate.Identifier()
|
||||
}
|
||||
|
||||
type connectionAdapter struct {
|
||||
delegate streamhttp.Connection
|
||||
}
|
||||
|
||||
func (c *connectionAdapter) CreateStream(headers http.Header) (apihttpstream.Stream, error) {
|
||||
stream, err := c.delegate.CreateStream(headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &streamAdapter{delegate: stream}, nil
|
||||
}
|
||||
|
||||
func (c *connectionAdapter) Close() error {
|
||||
return c.delegate.Close()
|
||||
}
|
||||
|
||||
func (c *connectionAdapter) CloseChan() <-chan bool {
|
||||
return c.delegate.CloseChan()
|
||||
}
|
||||
|
||||
func (c *connectionAdapter) SetIdleTimeout(timeout time.Duration) {
|
||||
c.delegate.SetIdleTimeout(timeout)
|
||||
}
|
||||
|
||||
func (c *connectionAdapter) RemoveStreams(streams ...apihttpstream.Stream) {
|
||||
streamingStreams := make([]streamhttp.Stream, 0, len(streams))
|
||||
for _, stream := range streams {
|
||||
if stream == nil {
|
||||
continue
|
||||
}
|
||||
if s, ok := stream.(streamhttp.Stream); ok {
|
||||
streamingStreams = append(streamingStreams, s)
|
||||
}
|
||||
}
|
||||
c.delegate.RemoveStreams(streamingStreams...)
|
||||
}
|
||||
|
||||
func wrapConnection(conn streamhttp.Connection) apihttpstream.Connection {
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
return &connectionAdapter{delegate: conn}
|
||||
}
|
||||
|
||||
func wrapNewStreamHandler(newStreamHandler apihttpstream.NewStreamHandler) streamhttp.NewStreamHandler {
|
||||
if newStreamHandler == nil {
|
||||
return nil
|
||||
}
|
||||
return func(stream streamhttp.Stream, replySent <-chan struct{}) error {
|
||||
return newStreamHandler(&streamAdapter{delegate: stream}, replySent)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,56 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package wsstream contains utilities for streaming content over WebSockets.
|
||||
// The Conn type allows callers to multiplex multiple read/write channels over
|
||||
// a single websocket.
|
||||
// Package wsstream contains compatibility wrappers for websocket streaming.
|
||||
//
|
||||
// "channel.k8s.io"
|
||||
//
|
||||
// The Websocket RemoteCommand subprotocol "channel.k8s.io" prepends each binary message with a
|
||||
// byte indicating the channel number (zero indexed) the message was sent on. Messages in both
|
||||
// directions should prefix their messages with this channel byte. Used for remote execution,
|
||||
// the channel numbers are by convention defined to match the POSIX file-descriptors assigned
|
||||
// to STDIN, STDOUT, and STDERR (0, 1, and 2). No other conversion is performed on the raw
|
||||
// subprotocol - writes are sent as they are received by the server.
|
||||
//
|
||||
// Example client session:
|
||||
//
|
||||
// CONNECT http://server.com with subprotocol "channel.k8s.io"
|
||||
// WRITE []byte{0, 102, 111, 111, 10} # send "foo\n" on channel 0 (STDIN)
|
||||
// READ []byte{1, 10} # receive "\n" on channel 1 (STDOUT)
|
||||
// CLOSE
|
||||
//
|
||||
// "v2.channel.k8s.io"
|
||||
//
|
||||
// The second Websocket subprotocol version "v2.channel.k8s.io" is the same as version 1,
|
||||
// but it is the first "versioned" subprotocol.
|
||||
//
|
||||
// "v3.channel.k8s.io"
|
||||
//
|
||||
// The third version of the Websocket RemoteCommand subprotocol adds another channel
|
||||
// for terminal resizing events. This channel is prepended with the byte '3', and it
|
||||
// transmits two window sizes (encoding TerminalSize struct) with integers in the range
|
||||
// (0,65536].
|
||||
//
|
||||
// "v4.channel.k8s.io"
|
||||
//
|
||||
// The fourth version of the Websocket RemoteCommand subprotocol adds a channel for
|
||||
// errors. This channel returns structured errors containing process exit codes. The
|
||||
// error is "apierrors.StatusError{}".
|
||||
//
|
||||
// "v5.channel.k8s.io"
|
||||
//
|
||||
// The fifth version of the Websocket RemoteCommand subprotocol adds a CLOSE signal,
|
||||
// which is sent as the first byte of the message. The second byte is the channel
|
||||
// id. This CLOSE signal is handled by the websocket server by closing the stream,
|
||||
// allowing the other streams to complete transmission if necessary, and gracefully
|
||||
// shutdown the connection.
|
||||
//
|
||||
// Example client session:
|
||||
//
|
||||
// CONNECT http://server.com with subprotocol "v5.channel.k8s.io"
|
||||
// WRITE []byte{0, 102, 111, 111, 10} # send "foo\n" on channel 0 (STDIN)
|
||||
// WRITE []byte{255, 0} # send CLOSE signal (STDIN)
|
||||
// CLOSE
|
||||
// Deprecated: use k8s.io/streaming/pkg/httpstream/wsstream directly.
|
||||
package wsstream
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package wsstream
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
streamws "k8s.io/streaming/pkg/httpstream/wsstream"
|
||||
)
|
||||
|
||||
const (
|
||||
WebSocketProtocolHeader = streamws.WebSocketProtocolHeader
|
||||
ChannelWebSocketProtocol = streamws.ChannelWebSocketProtocol
|
||||
Base64ChannelWebSocketProtocol = streamws.Base64ChannelWebSocketProtocol
|
||||
)
|
||||
|
||||
type ChannelType = streamws.ChannelType
|
||||
|
||||
const (
|
||||
IgnoreChannel = streamws.IgnoreChannel
|
||||
ReadChannel = streamws.ReadChannel
|
||||
WriteChannel = streamws.WriteChannel
|
||||
ReadWriteChannel = streamws.ReadWriteChannel
|
||||
)
|
||||
|
||||
func IsWebSocketRequest(req *http.Request) bool {
|
||||
return streamws.IsWebSocketRequest(req)
|
||||
}
|
||||
|
||||
func IsWebSocketRequestWithStreamCloseProtocol(req *http.Request) bool {
|
||||
return streamws.IsWebSocketRequestWithStreamCloseProtocol(req)
|
||||
}
|
||||
|
||||
func IsWebSocketRequestWithTunnelingProtocol(req *http.Request) bool {
|
||||
return streamws.IsWebSocketRequestWithTunnelingProtocol(req)
|
||||
}
|
||||
|
||||
func IgnoreReceives(ws *websocket.Conn, timeout time.Duration) {
|
||||
streamws.IgnoreReceives(ws, timeout)
|
||||
}
|
||||
|
||||
func IgnoreReceivesWithLogger(logger klog.Logger, ws *websocket.Conn, timeout time.Duration) {
|
||||
streamws.IgnoreReceivesWithLogger(logger, ws, timeout)
|
||||
}
|
||||
|
||||
type ChannelProtocolConfig = streamws.ChannelProtocolConfig
|
||||
|
||||
func NewDefaultChannelProtocols(channels []ChannelType) map[string]ChannelProtocolConfig {
|
||||
return streamws.NewDefaultChannelProtocols(channels)
|
||||
}
|
||||
|
||||
type Conn = streamws.Conn
|
||||
|
||||
func NewConn(protocols map[string]ChannelProtocolConfig) *Conn {
|
||||
return streamws.NewConn(protocols)
|
||||
}
|
||||
|
||||
type ReaderProtocolConfig = streamws.ReaderProtocolConfig
|
||||
|
||||
func NewDefaultReaderProtocols() map[string]ReaderProtocolConfig {
|
||||
return streamws.NewDefaultReaderProtocols()
|
||||
}
|
||||
|
||||
type Reader = streamws.Reader
|
||||
|
||||
func NewReader(r io.Reader, ping bool, protocols map[string]ReaderProtocolConfig) *Reader {
|
||||
return streamws.NewReader(r, ping, protocols)
|
||||
}
|
||||
|
||||
func NewReaderWithLogger(logger klog.Logger, r io.Reader, ping bool, protocols map[string]ReaderProtocolConfig) *Reader {
|
||||
return streamws.NewReaderWithLogger(logger, r, ping, protocols)
|
||||
}
|
||||
|
|
@ -31,9 +31,9 @@ import (
|
|||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
|
||||
"github.com/mxk/go-flowrate/flowrate"
|
||||
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ import (
|
|||
|
||||
"golang.org/x/net/websocket"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
)
|
||||
|
||||
const fakeStatusCode = 567
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ require (
|
|||
k8s.io/klog/v2 v2.140.0
|
||||
k8s.io/kms v0.0.0
|
||||
k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf
|
||||
k8s.io/streaming v0.0.0
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730
|
||||
|
|
@ -129,4 +130,5 @@ replace (
|
|||
k8s.io/client-go => ../client-go
|
||||
k8s.io/component-base => ../component-base
|
||||
k8s.io/kms => ../kms
|
||||
k8s.io/streaming => ../streaming
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ import (
|
|||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/wsstream"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/streaming/pkg/httpstream/wsstream"
|
||||
)
|
||||
|
||||
const bearerProtocolPrefix = "base64url.bearer.authorization.k8s.io."
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ import (
|
|||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/wsstream"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
|
|
@ -44,6 +43,7 @@ import (
|
|||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/apiserver/pkg/util/flushwriter"
|
||||
"k8s.io/component-base/tracing"
|
||||
"k8s.io/streaming/pkg/httpstream/wsstream"
|
||||
)
|
||||
|
||||
// StreamObject performs input stream negotiation from a ResourceStreamer and writes that to the response.
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import (
|
|||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/wsstream"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
|
|
@ -44,6 +43,7 @@ import (
|
|||
compbasemetrics "k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/tracing"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/streaming/pkg/httpstream/wsstream"
|
||||
)
|
||||
|
||||
// timeoutFactory abstracts watch timeout logic for testing
|
||||
|
|
|
|||
|
|
@ -26,11 +26,12 @@ import (
|
|||
"github.com/mxk/go-flowrate/flowrate"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
constants "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/apiserver/pkg/util/proxy/metrics"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
clientspdy "k8s.io/client-go/transport/spdy"
|
||||
"k8s.io/client-go/util/exec"
|
||||
"k8s.io/streaming/pkg/httpstream/spdy"
|
||||
)
|
||||
|
||||
// StreamTranslatorHandler is a handler which translates WebSocket stream data
|
||||
|
|
@ -77,7 +78,12 @@ func (h *StreamTranslatorHandler) ServeHTTP(w http.ResponseWriter, req *http.Req
|
|||
websocketStreams.writeStatus(apierrors.NewInternalError(err)) //nolint:errcheck
|
||||
return
|
||||
}
|
||||
spdyExecutor, err := remotecommand.NewSPDYExecutorRejectRedirects(spdyRoundTripper, spdyRoundTripper, "POST", h.Location)
|
||||
spdyExecutor, err := remotecommand.NewSPDYExecutorRejectRedirects(
|
||||
spdyRoundTripper,
|
||||
clientspdy.NewUpgraderForStreaming(spdyRoundTripper),
|
||||
"POST",
|
||||
h.Location,
|
||||
)
|
||||
if err != nil {
|
||||
metrics.IncStreamTranslatorRequest(req.Context(), strconv.Itoa(http.StatusInternalServerError))
|
||||
websocketStreams.writeStatus(apierrors.NewInternalError(err)) //nolint:errcheck
|
||||
|
|
|
|||
|
|
@ -38,8 +38,6 @@ import (
|
|||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
rcconstants "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/util/proxy/metrics"
|
||||
|
|
@ -48,6 +46,8 @@ import (
|
|||
"k8s.io/client-go/transport"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
"k8s.io/component-base/metrics/testutil"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
"k8s.io/streaming/pkg/httpstream/spdy"
|
||||
)
|
||||
|
||||
// TestStreamTranslator_LoopbackStdinToStdout returns random data sent on the client's
|
||||
|
|
|
|||
|
|
@ -31,14 +31,14 @@ import (
|
|||
|
||||
gwebsocket "github.com/gorilla/websocket"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/wsstream"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
constants "k8s.io/apimachinery/pkg/util/portforward"
|
||||
"k8s.io/apiserver/pkg/util/proxy/metrics"
|
||||
"k8s.io/client-go/tools/portforward"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
"k8s.io/streaming/pkg/httpstream/spdy"
|
||||
"k8s.io/streaming/pkg/httpstream/wsstream"
|
||||
)
|
||||
|
||||
// TunnelingHandler is a handler which tunnels SPDY through WebSockets.
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
constants "k8s.io/apimachinery/pkg/util/portforward"
|
||||
"k8s.io/apimachinery/pkg/util/proxy"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
|
|
@ -44,6 +42,8 @@ import (
|
|||
"k8s.io/client-go/tools/portforward"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
"k8s.io/component-base/metrics/testutil"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
"k8s.io/streaming/pkg/httpstream/spdy"
|
||||
)
|
||||
|
||||
func TestTunnelingHandler_UpgradeStreamingAndTunneling(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/wsstream"
|
||||
"k8s.io/streaming/pkg/httpstream/wsstream"
|
||||
)
|
||||
|
||||
// fakeHandler implements http.Handler interface
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ import (
|
|||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/wsstream"
|
||||
constants "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
"k8s.io/streaming/pkg/httpstream/wsstream"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Deprecated: This WebSockets package under apiserver is no longer in use.
|
||||
// Please use the apimachinery version of the package at:
|
||||
//
|
||||
// k8s.io/apimachinery/pkg/util/httpstream/wsstream
|
||||
package wsstream
|
||||
|
||||
import apimachinerywsstream "k8s.io/apimachinery/pkg/util/httpstream/wsstream"
|
||||
|
||||
// Aliases for all exported symbols previously in "conn.go"
|
||||
const (
|
||||
ChannelWebSocketProtocol = apimachinerywsstream.ChannelWebSocketProtocol
|
||||
Base64ChannelWebSocketProtocol = apimachinerywsstream.Base64ChannelWebSocketProtocol
|
||||
)
|
||||
|
||||
type ChannelType = apimachinerywsstream.ChannelType
|
||||
|
||||
const (
|
||||
IgnoreChannel = apimachinerywsstream.IgnoreChannel
|
||||
ReadChannel = apimachinerywsstream.ReadChannel
|
||||
WriteChannel = apimachinerywsstream.WriteChannel
|
||||
ReadWriteChannel = apimachinerywsstream.ReadWriteChannel
|
||||
)
|
||||
|
||||
type ChannelProtocolConfig = apimachinerywsstream.ChannelProtocolConfig
|
||||
|
||||
var (
|
||||
IsWebSocketRequest = apimachinerywsstream.IsWebSocketRequest
|
||||
IgnoreReceives = apimachinerywsstream.IgnoreReceives
|
||||
NewDefaultChannelProtocols = apimachinerywsstream.NewDefaultChannelProtocols
|
||||
)
|
||||
|
||||
type Conn = apimachinerywsstream.Conn
|
||||
|
||||
var NewConn = apimachinerywsstream.NewConn
|
||||
|
||||
// Aliases for all exported symbols previously in "stream.go"
|
||||
type ReaderProtocolConfig = apimachinerywsstream.ReaderProtocolConfig
|
||||
|
||||
var NewDefaultReaderProtocols = apimachinerywsstream.NewDefaultReaderProtocols
|
||||
|
||||
type Reader = apimachinerywsstream.Reader
|
||||
|
||||
var NewReader = apimachinerywsstream.NewReader
|
||||
|
|
@ -73,4 +73,5 @@ replace (
|
|||
k8s.io/api => ../api
|
||||
k8s.io/apimachinery => ../apimachinery
|
||||
k8s.io/client-go => ../client-go
|
||||
k8s.io/streaming => ../streaming
|
||||
)
|
||||
|
|
|
|||
1
staging/src/k8s.io/cli-runtime/go.sum
generated
1
staging/src/k8s.io/cli-runtime/go.sum
generated
|
|
@ -4,7 +4,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg6
|
|||
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/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ require (
|
|||
k8s.io/apimachinery v0.0.0
|
||||
k8s.io/klog/v2 v2.140.0
|
||||
k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf
|
||||
k8s.io/streaming v0.0.0
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730
|
||||
sigs.k8s.io/randfill v1.0.0
|
||||
|
|
@ -50,7 +51,6 @@ require (
|
|||
github.com/moby/spdystream v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
|
|
@ -66,4 +66,5 @@ require (
|
|||
replace (
|
||||
k8s.io/api => ../api
|
||||
k8s.io/apimachinery => ../apimachinery
|
||||
k8s.io/streaming => ../streaming
|
||||
)
|
||||
|
|
|
|||
1
staging/src/k8s.io/client-go/go.sum
generated
1
staging/src/k8s.io/client-go/go.sum
generated
|
|
@ -64,7 +64,6 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd
|
|||
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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
|
||||
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
|
||||
|
|
|
|||
|
|
@ -19,9 +19,11 @@ package portforward
|
|||
import (
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/klog/v2"
|
||||
streamhttp "k8s.io/streaming/pkg/httpstream"
|
||||
)
|
||||
|
||||
var _ httpstream.Dialer = &FallbackDialer{}
|
||||
var _ streamhttp.Dialer = &StreamingFallbackDialer{}
|
||||
|
||||
// FallbackDialer encapsulates a primary and secondary dialer, including
|
||||
// the boolean function to determine if the primary dialer failed. Implements
|
||||
|
|
@ -42,6 +44,24 @@ func NewFallbackDialer(primary, secondary httpstream.Dialer, shouldFallback func
|
|||
}
|
||||
}
|
||||
|
||||
// StreamingFallbackDialer encapsulates a primary and secondary streaming dialer
|
||||
// with fallback behavior.
|
||||
type StreamingFallbackDialer struct {
|
||||
primary streamhttp.Dialer
|
||||
secondary streamhttp.Dialer
|
||||
shouldFallback func(error) bool
|
||||
}
|
||||
|
||||
// NewFallbackDialerForStreaming creates a fallback dialer for in-tree callers
|
||||
// that use k8s.io/streaming/pkg/httpstream types.
|
||||
func NewFallbackDialerForStreaming(primary, secondary streamhttp.Dialer, shouldFallback func(error) bool) streamhttp.Dialer {
|
||||
return &StreamingFallbackDialer{
|
||||
primary: primary,
|
||||
secondary: secondary,
|
||||
shouldFallback: shouldFallback,
|
||||
}
|
||||
}
|
||||
|
||||
// Dial is the single function necessary to implement the "httpstream.Dialer" interface.
|
||||
// It takes the protocol version strings to request, returning an the upgraded
|
||||
// httstream.Connection and the negotiated protocol version accepted. If the initial
|
||||
|
|
@ -55,3 +75,14 @@ func (f *FallbackDialer) Dial(protocols ...string) (httpstream.Connection, strin
|
|||
}
|
||||
return conn, version, err
|
||||
}
|
||||
|
||||
// Dial is the single function necessary to implement the
|
||||
// "k8s.io/streaming/pkg/httpstream.Dialer" interface.
|
||||
func (f *StreamingFallbackDialer) Dial(protocols ...string) (streamhttp.Connection, string, error) {
|
||||
conn, version, err := f.primary.Dial(protocols...)
|
||||
if err != nil && f.shouldFallback(err) {
|
||||
klog.V(4).Infof("fallback to secondary dialer from primary dialer err: %v", err)
|
||||
return f.secondary.Dial(protocols...)
|
||||
}
|
||||
return conn, version, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,10 +26,13 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/klog/v2"
|
||||
streamhttp "k8s.io/streaming/pkg/httpstream"
|
||||
netutils "k8s.io/utils/net"
|
||||
)
|
||||
|
||||
|
|
@ -164,6 +167,12 @@ func New(dialer httpstream.Dialer, ports []string, stopChan <-chan struct{}, rea
|
|||
return NewOnAddresses(dialer, []string{"localhost"}, ports, stopChan, readyChan, out, errOut)
|
||||
}
|
||||
|
||||
// NewForStreaming creates a new PortForwarder with localhost listen addresses
|
||||
// for in-tree callers that use k8s.io/streaming/pkg/httpstream types.
|
||||
func NewForStreaming(dialer streamhttp.Dialer, ports []string, stopChan <-chan struct{}, readyChan chan struct{}, out, errOut io.Writer) (*PortForwarder, error) {
|
||||
return NewOnAddressesForStreaming(dialer, []string{"localhost"}, ports, stopChan, readyChan, out, errOut)
|
||||
}
|
||||
|
||||
// NewOnAddresses creates a new PortForwarder with custom listen addresses.
|
||||
func NewOnAddresses(dialer httpstream.Dialer, addresses []string, ports []string, stopChan <-chan struct{}, readyChan chan struct{}, out, errOut io.Writer) (*PortForwarder, error) {
|
||||
if len(addresses) == 0 {
|
||||
|
|
@ -191,6 +200,95 @@ func NewOnAddresses(dialer httpstream.Dialer, addresses []string, ports []string
|
|||
}, nil
|
||||
}
|
||||
|
||||
// NewOnAddressesForStreaming creates a new PortForwarder with custom listen
|
||||
// addresses for in-tree callers that use k8s.io/streaming/pkg/httpstream types.
|
||||
func NewOnAddressesForStreaming(dialer streamhttp.Dialer, addresses []string, ports []string, stopChan <-chan struct{}, readyChan chan struct{}, out, errOut io.Writer) (*PortForwarder, error) {
|
||||
return NewOnAddresses(&compatDialerAdapter{delegate: dialer}, addresses, ports, stopChan, readyChan, out, errOut)
|
||||
}
|
||||
|
||||
type compatDialerAdapter struct {
|
||||
delegate streamhttp.Dialer
|
||||
}
|
||||
|
||||
func (d *compatDialerAdapter) Dial(protocols ...string) (httpstream.Connection, string, error) {
|
||||
conn, protocol, err := d.delegate.Dial(protocols...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return &compatConnectionAdapter{delegate: conn}, protocol, nil
|
||||
}
|
||||
|
||||
type compatConnectionAdapter struct {
|
||||
delegate streamhttp.Connection
|
||||
}
|
||||
|
||||
func (c *compatConnectionAdapter) CreateStream(headers http.Header) (httpstream.Stream, error) {
|
||||
stream, err := c.delegate.CreateStream(headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &compatStreamAdapter{delegate: stream}, nil
|
||||
}
|
||||
|
||||
func (c *compatConnectionAdapter) Close() error {
|
||||
return c.delegate.Close()
|
||||
}
|
||||
|
||||
func (c *compatConnectionAdapter) CloseChan() <-chan bool {
|
||||
return c.delegate.CloseChan()
|
||||
}
|
||||
|
||||
func (c *compatConnectionAdapter) SetIdleTimeout(timeout time.Duration) {
|
||||
c.delegate.SetIdleTimeout(timeout)
|
||||
}
|
||||
|
||||
func (c *compatConnectionAdapter) RemoveStreams(streams ...httpstream.Stream) {
|
||||
streamingStreams := make([]streamhttp.Stream, 0, len(streams))
|
||||
for _, stream := range streams {
|
||||
if stream == nil {
|
||||
continue
|
||||
}
|
||||
if s, ok := stream.(*compatStreamAdapter); ok {
|
||||
streamingStreams = append(streamingStreams, s.delegate)
|
||||
continue
|
||||
}
|
||||
if s, ok := stream.(streamhttp.Stream); ok {
|
||||
streamingStreams = append(streamingStreams, s)
|
||||
continue
|
||||
}
|
||||
klog.V(5).Infof("dropping unadaptable stream %T in portforward RemoveStreams", stream)
|
||||
}
|
||||
c.delegate.RemoveStreams(streamingStreams...)
|
||||
}
|
||||
|
||||
type compatStreamAdapter struct {
|
||||
delegate streamhttp.Stream
|
||||
}
|
||||
|
||||
func (s *compatStreamAdapter) Read(p []byte) (int, error) {
|
||||
return s.delegate.Read(p)
|
||||
}
|
||||
|
||||
func (s *compatStreamAdapter) Write(p []byte) (int, error) {
|
||||
return s.delegate.Write(p)
|
||||
}
|
||||
|
||||
func (s *compatStreamAdapter) Close() error {
|
||||
return s.delegate.Close()
|
||||
}
|
||||
|
||||
func (s *compatStreamAdapter) Reset() error {
|
||||
return s.delegate.Reset()
|
||||
}
|
||||
|
||||
func (s *compatStreamAdapter) Headers() http.Header {
|
||||
return s.delegate.Headers()
|
||||
}
|
||||
|
||||
func (s *compatStreamAdapter) Identifier() uint32 {
|
||||
return s.delegate.Identifier()
|
||||
}
|
||||
|
||||
// ForwardPorts formats and executes a port forwarding request. The connection will remain
|
||||
// open until stopChan is closed.
|
||||
func (pf *PortForwarder) ForwardPorts() error {
|
||||
|
|
|
|||
|
|
@ -24,11 +24,13 @@ import (
|
|||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
httpstreamspdy "k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
constants "k8s.io/apimachinery/pkg/util/portforward"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/transport/websocket"
|
||||
"k8s.io/klog/v2"
|
||||
streamhttp "k8s.io/streaming/pkg/httpstream"
|
||||
streamspdy "k8s.io/streaming/pkg/httpstream/spdy"
|
||||
)
|
||||
|
||||
const PingPeriod = 10 * time.Second
|
||||
|
|
@ -40,6 +42,14 @@ type tunnelingDialer struct {
|
|||
holder websocket.ConnectionHolder
|
||||
}
|
||||
|
||||
// streamingTunnelingDialer implements "k8s.io/streaming/pkg/httpstream.Dialer"
|
||||
// for in-tree callers.
|
||||
type streamingTunnelingDialer struct {
|
||||
url *url.URL
|
||||
transport http.RoundTripper
|
||||
holder websocket.ConnectionHolder
|
||||
}
|
||||
|
||||
// NewTunnelingDialer creates and returns the tunnelingDialer structure which implemements the "httpstream.Dialer"
|
||||
// interface. The dialer can upgrade a websocket request, creating a websocket connection. This function
|
||||
// returns an error if one occurs.
|
||||
|
|
@ -55,25 +65,36 @@ func NewSPDYOverWebsocketDialer(url *url.URL, config *restclient.Config) (httpst
|
|||
}, nil
|
||||
}
|
||||
|
||||
// Dial upgrades to a tunneling streaming connection, returning a SPDY connection
|
||||
// containing a WebSockets connection (which implements "net.Conn"). Also
|
||||
// returns the protocol negotiated, or an error.
|
||||
func (d *tunnelingDialer) Dial(protocols ...string) (httpstream.Connection, string, error) {
|
||||
// NewSPDYOverWebsocketDialerForStreaming creates a SPDY-over-websocket dialer
|
||||
// for in-tree callers that use k8s.io/streaming/pkg/httpstream types.
|
||||
func NewSPDYOverWebsocketDialerForStreaming(url *url.URL, config *restclient.Config) (streamhttp.Dialer, error) {
|
||||
transport, holder, err := websocket.RoundTripperFor(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &streamingTunnelingDialer{
|
||||
url: url,
|
||||
transport: transport,
|
||||
holder: holder,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func negotiateSPDYOverWebsocket(url *url.URL, transport http.RoundTripper, holder websocket.ConnectionHolder, protocols ...string) (*TunnelingConnection, string, error) {
|
||||
// There is no passed context, so skip the context when creating request for now.
|
||||
// Websockets requires "GET" method: RFC 6455 Sec. 4.1 (page 17).
|
||||
req, err := http.NewRequest("GET", d.url.String(), nil)
|
||||
req, err := http.NewRequest("GET", url.String(), nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
// Add the spdy tunneling prefix to the requested protocols. The tunneling
|
||||
// handler will know how to negotiate these protocols.
|
||||
tunnelingProtocols := []string{}
|
||||
tunnelingProtocols := make([]string, 0, len(protocols))
|
||||
for _, protocol := range protocols {
|
||||
tunnelingProtocol := constants.WebsocketsSPDYTunnelingPrefix + protocol
|
||||
tunnelingProtocols = append(tunnelingProtocols, tunnelingProtocol)
|
||||
}
|
||||
klog.V(4).Infoln("Before WebSocket Upgrade Connection...")
|
||||
conn, err := websocket.Negotiate(d.transport, d.holder, req, tunnelingProtocols...)
|
||||
conn, err := websocket.Negotiate(transport, holder, req, tunnelingProtocols...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
|
@ -84,10 +105,32 @@ func (d *tunnelingDialer) Dial(protocols ...string) (httpstream.Connection, stri
|
|||
protocol = strings.TrimPrefix(protocol, constants.WebsocketsSPDYTunnelingPrefix)
|
||||
klog.V(4).Infof("negotiated protocol: %s", protocol)
|
||||
|
||||
// Wrap the websocket connection which implements "net.Conn".
|
||||
tConn := NewTunnelingConnection("client", conn)
|
||||
return NewTunnelingConnection("client", conn), protocol, nil
|
||||
}
|
||||
|
||||
// Dial upgrades to a tunneling streaming connection, returning a SPDY connection
|
||||
// containing a WebSockets connection (which implements "net.Conn"). Also
|
||||
// returns the protocol negotiated, or an error.
|
||||
func (d *tunnelingDialer) Dial(protocols ...string) (httpstream.Connection, string, error) {
|
||||
tConn, protocol, err := negotiateSPDYOverWebsocket(d.url, d.transport, d.holder, protocols...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
// Create SPDY connection injecting the previously created tunneling connection.
|
||||
spdyConn, err := spdy.NewClientConnectionWithPings(tConn, PingPeriod)
|
||||
spdyConn, err := httpstreamspdy.NewClientConnectionWithPings(tConn, PingPeriod)
|
||||
|
||||
return spdyConn, protocol, err
|
||||
}
|
||||
|
||||
// Dial upgrades to a tunneling streaming connection for callers using
|
||||
// k8s.io/streaming/pkg/httpstream types.
|
||||
func (d *streamingTunnelingDialer) Dial(protocols ...string) (streamhttp.Connection, string, error) {
|
||||
tConn, protocol, err := negotiateSPDYOverWebsocket(d.url, d.transport, d.holder, protocols...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
// Create SPDY connection injecting the previously created tunneling connection.
|
||||
spdyConn, err := streamspdy.NewClientConnectionWithPings(tConn, PingPeriod)
|
||||
|
||||
return spdyConn, protocol, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,17 +25,21 @@ import (
|
|||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilnettesting "k8s.io/apimachinery/pkg/util/net/testing"
|
||||
"k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/rest"
|
||||
utilexec "k8s.io/client-go/util/exec"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
)
|
||||
|
||||
func TestFallbackClient_WebSocketPrimarySucceeds(t *testing.T) {
|
||||
|
|
@ -234,6 +238,74 @@ func TestFallbackClient_PrimaryAndSecondaryFail(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestFallbackClient_SPDYSecondaryNonZeroExitCode(t *testing.T) {
|
||||
const expectedExitCode = 23
|
||||
const expectedStdout = "stdout-before-exit"
|
||||
|
||||
// Create fake SPDY server that writes stdout followed by a v4 status error.
|
||||
spdyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, err := createHTTPStreams(w, req, &StreamOptions{
|
||||
Stdin: strings.NewReader("input"),
|
||||
Stdout: &bytes.Buffer{},
|
||||
})
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
defer ctx.conn.Close()
|
||||
|
||||
if _, err := io.WriteString(ctx.stdoutStream, expectedStdout); err != nil {
|
||||
t.Fatalf("error writing stdout stream: %v", err)
|
||||
}
|
||||
|
||||
statusErr := &apierrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Reason: remotecommand.NonZeroExitCodeReason,
|
||||
Details: &metav1.StatusDetails{
|
||||
Causes: []metav1.StatusCause{
|
||||
{
|
||||
Type: remotecommand.ExitCodeCauseType,
|
||||
Message: "23",
|
||||
},
|
||||
},
|
||||
},
|
||||
Message: "command terminated with non-zero exit code: 23",
|
||||
}}
|
||||
if err := ctx.writeStatus(statusErr); err != nil {
|
||||
t.Fatalf("error writing status stream: %v", err)
|
||||
}
|
||||
}))
|
||||
defer spdyServer.Close()
|
||||
|
||||
spdyLocation, err := url.Parse(spdyServer.URL)
|
||||
require.NoError(t, err)
|
||||
// Primary websocket executor points at a SPDY-only endpoint and should fail.
|
||||
websocketExecutor, err := NewWebSocketExecutor(&rest.Config{Host: spdyLocation.Host}, "GET", spdyServer.URL+"?stdin=true&stdout=true")
|
||||
require.NoError(t, err)
|
||||
spdyExecutor, err := NewSPDYExecutor(&rest.Config{Host: spdyLocation.Host}, "POST", spdyLocation)
|
||||
require.NoError(t, err)
|
||||
|
||||
var sawPrimaryError atomic.Bool
|
||||
exec, err := NewFallbackExecutor(websocketExecutor, spdyExecutor, func(err error) bool {
|
||||
sawPrimaryError.Store(true)
|
||||
return true
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var stdout bytes.Buffer
|
||||
err = exec.StreamWithContext(context.Background(), StreamOptions{
|
||||
Stdin: strings.NewReader("input"),
|
||||
Stdout: &stdout,
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.True(t, sawPrimaryError.Load(), "expected primary websocket path to fail and trigger fallback")
|
||||
|
||||
var exitErr utilexec.ExitError
|
||||
require.ErrorAs(t, err, &exitErr, "expected ExitError from secondary SPDY path, got: %T: %v", err, err)
|
||||
require.Equal(t, expectedExitCode, exitErr.ExitStatus())
|
||||
require.Equal(t, expectedStdout, stdout.String())
|
||||
}
|
||||
|
||||
// localhostCert was generated from crypto/tls/generate_cert.go with the following command:
|
||||
//
|
||||
// go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
)
|
||||
|
||||
// StreamOptions holds information pertaining to the current streaming session:
|
||||
|
|
|
|||
|
|
@ -22,11 +22,11 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/transport/spdy"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
)
|
||||
|
||||
// spdyStreamExecutor handles transporting standard shell streams over an httpstream connection.
|
||||
|
|
@ -109,7 +109,7 @@ func (e *spdyStreamExecutor) newConnectionAndStream(ctx context.Context, options
|
|||
return fmt.Errorf("redirect not allowed")
|
||||
}
|
||||
}
|
||||
conn, protocol, err := spdy.Negotiate(
|
||||
conn, protocol, err := spdy.NegotiateStreaming(
|
||||
e.upgrader,
|
||||
&client,
|
||||
req,
|
||||
|
|
|
|||
|
|
@ -33,12 +33,13 @@ import (
|
|||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/rest"
|
||||
utilexec "k8s.io/client-go/util/exec"
|
||||
"k8s.io/klog/v2/ktesting"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
"k8s.io/streaming/pkg/httpstream/spdy"
|
||||
)
|
||||
|
||||
type AttachFunc func(in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan TerminalSize) error
|
||||
|
|
@ -183,6 +184,71 @@ func TestSPDYExecutorStream(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestSPDYExecutorNonZeroExitCode verifies SPDY v4 status-stream non-zero exit
|
||||
// code handling remains compatible with CRI streaming servers.
|
||||
func TestSPDYExecutorNonZeroExitCode(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
ctx, err := createHTTPStreams(writer, request, &StreamOptions{
|
||||
Stdin: strings.NewReader("input"),
|
||||
Stdout: &bytes.Buffer{},
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected stream setup error: %v", err)
|
||||
return
|
||||
}
|
||||
defer ctx.conn.Close()
|
||||
|
||||
if _, err := io.WriteString(ctx.stdoutStream, "stdout-before-exit"); err != nil {
|
||||
t.Errorf("unexpected stdout write error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = ctx.writeStatus(&apierrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Reason: remotecommandconsts.NonZeroExitCodeReason,
|
||||
Details: &metav1.StatusDetails{
|
||||
Causes: []metav1.StatusCause{
|
||||
{
|
||||
Type: remotecommandconsts.ExitCodeCauseType,
|
||||
Message: "17",
|
||||
},
|
||||
},
|
||||
},
|
||||
Message: "command terminated with non-zero exit code: 17",
|
||||
}})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected status write error: %v", err)
|
||||
}
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
uri, _ := url.Parse(server.URL)
|
||||
exec, err := NewSPDYExecutor(&rest.Config{Host: uri.Host}, "POST", uri)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected executor error: %v", err)
|
||||
}
|
||||
|
||||
var stdout bytes.Buffer
|
||||
err = exec.StreamWithContext(context.Background(), StreamOptions{
|
||||
Stdin: strings.NewReader("input"),
|
||||
Stdout: &stdout,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected non-zero exit error, got nil")
|
||||
}
|
||||
|
||||
var exitErr utilexec.ExitError
|
||||
if !errors.As(err, &exitErr) {
|
||||
t.Fatalf("expected ExitError, got: %T: %v", err, err)
|
||||
}
|
||||
if exitErr.ExitStatus() != 17 {
|
||||
t.Fatalf("expected exit code 17, got %d", exitErr.ExitStatus())
|
||||
}
|
||||
if got := stdout.String(); got != "stdout-before-exit" {
|
||||
t.Fatalf("unexpected stdout: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func newTestHTTPServer(f AttachFunc, options *StreamOptions) *httptest.Server {
|
||||
//nolint:errcheck
|
||||
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ import (
|
|||
"net/http"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
)
|
||||
|
||||
// streamProtocolV1 implements the first version of the streaming exec & attach
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ import (
|
|||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/klog/v2/ktesting"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
)
|
||||
|
||||
type fakeReader struct {
|
||||
|
|
|
|||
|
|
@ -29,11 +29,11 @@ import (
|
|||
gwebsocket "github.com/gorilla/websocket"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/transport/websocket"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
)
|
||||
|
||||
// writeDeadline defines the time that a client-side write to the websocket
|
||||
|
|
|
|||
|
|
@ -42,13 +42,13 @@ import (
|
|||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/wsstream"
|
||||
utilnettesting "k8s.io/apimachinery/pkg/util/net/testing"
|
||||
"k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/rest"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/klog/v2/ktesting"
|
||||
"k8s.io/streaming/pkg/httpstream/wsstream"
|
||||
)
|
||||
|
||||
// TestWebSocketClient_LoopbackStdinToStdout returns random data sent on the STDIN channel
|
||||
|
|
|
|||
|
|
@ -23,8 +23,10 @@ import (
|
|||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
httpstreamspdy "k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/klog/v2"
|
||||
streamhttp "k8s.io/streaming/pkg/httpstream"
|
||||
)
|
||||
|
||||
// Upgrader validates a response from the server after a SPDY upgrade.
|
||||
|
|
@ -43,7 +45,7 @@ func RoundTripperFor(config *restclient.Config) (http.RoundTripper, Upgrader, er
|
|||
if config.Proxy != nil {
|
||||
proxy = config.Proxy
|
||||
}
|
||||
upgradeRoundTripper, err := spdy.NewRoundTripperWithConfig(spdy.RoundTripperConfig{
|
||||
upgradeRoundTripper, err := httpstreamspdy.NewRoundTripperWithConfig(httpstreamspdy.RoundTripperConfig{
|
||||
TLS: tlsConfig,
|
||||
Proxier: proxy,
|
||||
PingPeriod: time.Second * 5,
|
||||
|
|
@ -79,6 +81,18 @@ func NewDialer(upgrader Upgrader, client *http.Client, method string, url *url.U
|
|||
}
|
||||
}
|
||||
|
||||
// NewDialerForStreaming creates a SPDY dialer for in-tree callers that use
|
||||
// k8s.io/streaming/pkg/httpstream types.
|
||||
func NewDialerForStreaming(upgrader Upgrader, client *http.Client, method string, url *url.URL) streamhttp.Dialer {
|
||||
return &streamingDialerAdapter{delegate: NewDialer(upgrader, client, method, url)}
|
||||
}
|
||||
|
||||
// NewUpgraderForStreaming adapts a streaming upgrader for callers that need
|
||||
// the compatibility Upgrader interface.
|
||||
func NewUpgraderForStreaming(upgrader streamhttp.UpgradeRoundTripper) Upgrader {
|
||||
return &compatUpgraderAdapter{delegate: upgrader}
|
||||
}
|
||||
|
||||
func (d *dialer) Dial(protocols ...string) (httpstream.Connection, string, error) {
|
||||
req, err := http.NewRequest(d.method, d.url.String(), nil)
|
||||
if err != nil {
|
||||
|
|
@ -105,3 +119,199 @@ func Negotiate(upgrader Upgrader, client *http.Client, req *http.Request, protoc
|
|||
}
|
||||
return conn, resp.Header.Get(httpstream.HeaderProtocolVersion), nil
|
||||
}
|
||||
|
||||
// NegotiateStreaming is for in-tree callers that still operate on
|
||||
// k8s.io/streaming/pkg/httpstream types.
|
||||
func NegotiateStreaming(upgrader Upgrader, client *http.Client, req *http.Request, protocols ...string) (streamhttp.Connection, string, error) {
|
||||
conn, protocol, err := Negotiate(upgrader, client, req, protocols...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return wrapStreamingConnection(conn), protocol, nil
|
||||
}
|
||||
|
||||
type streamingDialerAdapter struct {
|
||||
delegate httpstream.Dialer
|
||||
}
|
||||
|
||||
func (d *streamingDialerAdapter) Dial(protocols ...string) (streamhttp.Connection, string, error) {
|
||||
conn, protocol, err := d.delegate.Dial(protocols...)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return wrapStreamingConnection(conn), protocol, nil
|
||||
}
|
||||
|
||||
type compatUpgraderAdapter struct {
|
||||
delegate streamhttp.UpgradeRoundTripper
|
||||
}
|
||||
|
||||
func (u *compatUpgraderAdapter) NewConnection(resp *http.Response) (httpstream.Connection, error) {
|
||||
conn, err := u.delegate.NewConnection(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wrapCompatConnection(conn), nil
|
||||
}
|
||||
|
||||
type streamingStreamAdapter struct {
|
||||
delegate httpstream.Stream
|
||||
}
|
||||
|
||||
func (s *streamingStreamAdapter) Read(p []byte) (int, error) {
|
||||
return s.delegate.Read(p)
|
||||
}
|
||||
|
||||
func (s *streamingStreamAdapter) Write(p []byte) (int, error) {
|
||||
return s.delegate.Write(p)
|
||||
}
|
||||
|
||||
func (s *streamingStreamAdapter) Close() error {
|
||||
return s.delegate.Close()
|
||||
}
|
||||
|
||||
func (s *streamingStreamAdapter) Reset() error {
|
||||
return s.delegate.Reset()
|
||||
}
|
||||
|
||||
func (s *streamingStreamAdapter) Headers() http.Header {
|
||||
return s.delegate.Headers()
|
||||
}
|
||||
|
||||
func (s *streamingStreamAdapter) Identifier() uint32 {
|
||||
return s.delegate.Identifier()
|
||||
}
|
||||
|
||||
type streamingConnectionAdapter struct {
|
||||
delegate httpstream.Connection
|
||||
}
|
||||
|
||||
func (c *streamingConnectionAdapter) CreateStream(headers http.Header) (streamhttp.Stream, error) {
|
||||
stream, err := c.delegate.CreateStream(headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &streamingStreamAdapter{delegate: stream}, nil
|
||||
}
|
||||
|
||||
func (c *streamingConnectionAdapter) Close() error {
|
||||
return c.delegate.Close()
|
||||
}
|
||||
|
||||
func (c *streamingConnectionAdapter) CloseChan() <-chan bool {
|
||||
return c.delegate.CloseChan()
|
||||
}
|
||||
|
||||
func (c *streamingConnectionAdapter) SetIdleTimeout(timeout time.Duration) {
|
||||
c.delegate.SetIdleTimeout(timeout)
|
||||
}
|
||||
|
||||
func (c *streamingConnectionAdapter) RemoveStreams(streams ...streamhttp.Stream) {
|
||||
compatStreams := make([]httpstream.Stream, 0, len(streams))
|
||||
for _, stream := range streams {
|
||||
if stream == nil {
|
||||
continue
|
||||
}
|
||||
if s, ok := stream.(*streamingStreamAdapter); ok {
|
||||
compatStreams = append(compatStreams, s.delegate)
|
||||
continue
|
||||
}
|
||||
if s, ok := stream.(httpstream.Stream); ok {
|
||||
compatStreams = append(compatStreams, s)
|
||||
continue
|
||||
}
|
||||
klog.V(5).Infof("dropping unadaptable streaming stream %T in RemoveStreams", stream)
|
||||
}
|
||||
c.delegate.RemoveStreams(compatStreams...)
|
||||
}
|
||||
|
||||
func wrapStreamingConnection(conn httpstream.Connection) streamhttp.Connection {
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
if wrapped, ok := conn.(*compatConnectionAdapter); ok {
|
||||
return wrapped.delegate
|
||||
}
|
||||
return &streamingConnectionAdapter{delegate: conn}
|
||||
}
|
||||
|
||||
type compatStreamAdapter struct {
|
||||
delegate streamhttp.Stream
|
||||
}
|
||||
|
||||
func (s *compatStreamAdapter) Read(p []byte) (int, error) {
|
||||
return s.delegate.Read(p)
|
||||
}
|
||||
|
||||
func (s *compatStreamAdapter) Write(p []byte) (int, error) {
|
||||
return s.delegate.Write(p)
|
||||
}
|
||||
|
||||
func (s *compatStreamAdapter) Close() error {
|
||||
return s.delegate.Close()
|
||||
}
|
||||
|
||||
func (s *compatStreamAdapter) Reset() error {
|
||||
return s.delegate.Reset()
|
||||
}
|
||||
|
||||
func (s *compatStreamAdapter) Headers() http.Header {
|
||||
return s.delegate.Headers()
|
||||
}
|
||||
|
||||
func (s *compatStreamAdapter) Identifier() uint32 {
|
||||
return s.delegate.Identifier()
|
||||
}
|
||||
|
||||
type compatConnectionAdapter struct {
|
||||
delegate streamhttp.Connection
|
||||
}
|
||||
|
||||
func (c *compatConnectionAdapter) CreateStream(headers http.Header) (httpstream.Stream, error) {
|
||||
stream, err := c.delegate.CreateStream(headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &compatStreamAdapter{delegate: stream}, nil
|
||||
}
|
||||
|
||||
func (c *compatConnectionAdapter) Close() error {
|
||||
return c.delegate.Close()
|
||||
}
|
||||
|
||||
func (c *compatConnectionAdapter) CloseChan() <-chan bool {
|
||||
return c.delegate.CloseChan()
|
||||
}
|
||||
|
||||
func (c *compatConnectionAdapter) SetIdleTimeout(timeout time.Duration) {
|
||||
c.delegate.SetIdleTimeout(timeout)
|
||||
}
|
||||
|
||||
func (c *compatConnectionAdapter) RemoveStreams(streams ...httpstream.Stream) {
|
||||
streamingStreams := make([]streamhttp.Stream, 0, len(streams))
|
||||
for _, stream := range streams {
|
||||
if stream == nil {
|
||||
continue
|
||||
}
|
||||
if s, ok := stream.(*compatStreamAdapter); ok {
|
||||
streamingStreams = append(streamingStreams, s.delegate)
|
||||
continue
|
||||
}
|
||||
if s, ok := stream.(streamhttp.Stream); ok {
|
||||
streamingStreams = append(streamingStreams, s)
|
||||
continue
|
||||
}
|
||||
klog.V(5).Infof("dropping unadaptable compat stream %T in RemoveStreams", stream)
|
||||
}
|
||||
c.delegate.RemoveStreams(streamingStreams...)
|
||||
}
|
||||
|
||||
func wrapCompatConnection(conn streamhttp.Connection) httpstream.Connection {
|
||||
if conn == nil {
|
||||
return nil
|
||||
}
|
||||
if wrapped, ok := conn.(*streamingConnectionAdapter); ok {
|
||||
return wrapped.delegate
|
||||
}
|
||||
return &compatConnectionAdapter{delegate: conn}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,11 +31,11 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/wsstream"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/transport"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
"k8s.io/streaming/pkg/httpstream/wsstream"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -32,10 +32,10 @@ import (
|
|||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/wsstream"
|
||||
"k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
"k8s.io/streaming/pkg/httpstream/wsstream"
|
||||
)
|
||||
|
||||
func TestWebSocketRoundTripper_RoundTripperSucceeds(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -104,6 +104,7 @@ require (
|
|||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/kms v0.0.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf // indirect
|
||||
k8s.io/streaming v0.0.0 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
|
|
@ -120,4 +121,5 @@ replace (
|
|||
k8s.io/component-helpers => ../component-helpers
|
||||
k8s.io/controller-manager => ../controller-manager
|
||||
k8s.io/kms => ../kms
|
||||
k8s.io/streaming => ../streaming
|
||||
)
|
||||
|
|
|
|||
|
|
@ -40,4 +40,5 @@ require (
|
|||
replace (
|
||||
k8s.io/api => ../api
|
||||
k8s.io/apimachinery => ../apimachinery
|
||||
k8s.io/streaming => ../streaming
|
||||
)
|
||||
|
|
|
|||
1
staging/src/k8s.io/cluster-bootstrap/go.sum
generated
1
staging/src/k8s.io/cluster-bootstrap/go.sum
generated
|
|
@ -1,5 +1,4 @@
|
|||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
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=
|
||||
|
|
|
|||
|
|
@ -50,4 +50,7 @@ require (
|
|||
sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
|
||||
)
|
||||
|
||||
replace k8s.io/apimachinery => ../apimachinery
|
||||
replace (
|
||||
k8s.io/apimachinery => ../apimachinery
|
||||
k8s.io/streaming => ../streaming
|
||||
)
|
||||
|
|
|
|||
1
staging/src/k8s.io/code-generator/go.sum
generated
1
staging/src/k8s.io/code-generator/go.sum
generated
|
|
@ -1,7 +1,6 @@
|
|||
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/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||
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=
|
||||
|
|
|
|||
|
|
@ -90,4 +90,5 @@ replace (
|
|||
k8s.io/api => ../api
|
||||
k8s.io/apimachinery => ../apimachinery
|
||||
k8s.io/client-go => ../client-go
|
||||
k8s.io/streaming => ../streaming
|
||||
)
|
||||
|
|
|
|||
1
staging/src/k8s.io/component-base/go.sum
generated
1
staging/src/k8s.io/component-base/go.sum
generated
|
|
@ -9,7 +9,6 @@ github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMo
|
|||
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
|
|
|
|||
|
|
@ -56,4 +56,5 @@ replace (
|
|||
k8s.io/api => ../api
|
||||
k8s.io/apimachinery => ../apimachinery
|
||||
k8s.io/client-go => ../client-go
|
||||
k8s.io/streaming => ../streaming
|
||||
)
|
||||
|
|
|
|||
1
staging/src/k8s.io/component-helpers/go.sum
generated
1
staging/src/k8s.io/component-helpers/go.sum
generated
|
|
@ -2,7 +2,6 @@ cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1h
|
|||
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/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
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=
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ require (
|
|||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf // indirect
|
||||
k8s.io/streaming v0.0.0 // indirect
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
|
|
@ -110,4 +111,5 @@ replace (
|
|||
k8s.io/client-go => ../client-go
|
||||
k8s.io/component-base => ../component-base
|
||||
k8s.io/kms => ../kms
|
||||
k8s.io/streaming => ../streaming
|
||||
)
|
||||
|
|
|
|||
|
|
@ -84,4 +84,5 @@ replace (
|
|||
k8s.io/client-go => ../client-go
|
||||
k8s.io/component-base => ../component-base
|
||||
k8s.io/cri-api => ../cri-api
|
||||
k8s.io/streaming => ../streaming
|
||||
)
|
||||
|
|
|
|||
1
staging/src/k8s.io/cri-client/go.sum
generated
1
staging/src/k8s.io/cri-client/go.sum
generated
|
|
@ -9,7 +9,6 @@ github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMo
|
|||
github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE=
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
|
||||
|
|
|
|||
2
staging/src/k8s.io/cri-streaming/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
2
staging/src/k8s.io/cri-streaming/.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Sorry, we do not accept changes directly against this repository. Please see
|
||||
CONTRIBUTING.md for information on where and how to contribute instead.
|
||||
7
staging/src/k8s.io/cri-streaming/CONTRIBUTING.md
Normal file
7
staging/src/k8s.io/cri-streaming/CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Contributing guidelines
|
||||
|
||||
Do not open pull requests directly against this repository, they will be ignored. Instead, please open pull requests against [kubernetes/kubernetes](https://git.k8s.io/kubernetes/). Please follow the same [contributing guide](https://git.k8s.io/kubernetes/CONTRIBUTING.md) you would follow for any other pull request made to kubernetes/kubernetes.
|
||||
|
||||
This repository is published from [kubernetes/kubernetes/staging/src/k8s.io/cri-streaming](https://git.k8s.io/kubernetes/staging/src/k8s.io/cri-streaming) by the [kubernetes publishing-bot](https://git.k8s.io/publishing-bot).
|
||||
|
||||
Please see [Staging Directory and Publishing](https://git.k8s.io/community/contributors/devel/sig-architecture/staging.md) for more information
|
||||
202
staging/src/k8s.io/cri-streaming/LICENSE
Normal file
202
staging/src/k8s.io/cri-streaming/LICENSE
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
18
staging/src/k8s.io/cri-streaming/OWNERS
Normal file
18
staging/src/k8s.io/cri-streaming/OWNERS
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- dims
|
||||
- mikebrow
|
||||
- saschagrunert
|
||||
- aojea
|
||||
- seans3
|
||||
- liggitt
|
||||
reviewers:
|
||||
- dims
|
||||
- mikebrow
|
||||
- saschagrunert
|
||||
- aojea
|
||||
- seans3
|
||||
- liggitt
|
||||
labels:
|
||||
- sig/node
|
||||
35
staging/src/k8s.io/cri-streaming/README.md
Normal file
35
staging/src/k8s.io/cri-streaming/README.md
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
> ⚠️ **This is an automatically published [staged repository](https://git.k8s.io/kubernetes/staging#external-repository-staging-area) for Kubernetes**.
|
||||
> Contributions, including issues and pull requests, should be made to the main Kubernetes repository: [https://github.com/kubernetes/kubernetes](https://github.com/kubernetes/kubernetes).
|
||||
> This repository is read-only for importing, and not used for direct contributions.
|
||||
> See [CONTRIBUTING.md](./CONTRIBUTING.md) for more details.
|
||||
|
||||
# cri-streaming
|
||||
|
||||
This repository contains the Kubernetes CRI streaming server implementation used for:
|
||||
|
||||
- Exec
|
||||
- Attach
|
||||
- PortForward
|
||||
|
||||
The goal of this module is to provide a dedicated, runtime-focused import target for CRI streaming functionality without requiring consumers to depend on the full `k8s.io/kubelet` module surface.
|
||||
|
||||
## Migration notes
|
||||
|
||||
- The legacy package path `k8s.io/kubelet/pkg/cri/streaming` has moved to `k8s.io/cri-streaming/pkg/streaming`.
|
||||
- Shared transport dependencies now come from `k8s.io/streaming/pkg/httpstream` and subpackages.
|
||||
- This extraction does not provide compatibility shims at the old kubelet/apimachinery paths.
|
||||
|
||||
## Community, discussion, contribution, and support
|
||||
|
||||
cri-streaming is planned as a sub-project of [SIG Node](https://github.com/kubernetes/community/tree/master/sig-node).
|
||||
|
||||
You can reach maintainers of this project at:
|
||||
|
||||
- Slack: [#sig-node](https://kubernetes.slack.com/messages/sig-node)
|
||||
- Mailing List: [kubernetes-sig-node](https://groups.google.com/forum/#!forum/kubernetes-sig-node)
|
||||
|
||||
Learn how to engage with the Kubernetes community on the [community page](http://kubernetes.io/community/).
|
||||
|
||||
### Code of conduct
|
||||
|
||||
Participation in the Kubernetes community is governed by the [Kubernetes Code of Conduct](code-of-conduct.md).
|
||||
17
staging/src/k8s.io/cri-streaming/SECURITY_CONTACTS
Normal file
17
staging/src/k8s.io/cri-streaming/SECURITY_CONTACTS
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Defined below are the security contacts for this repo.
|
||||
#
|
||||
# They are the contact point for the Product Security Committee to reach out
|
||||
# to for triaging and handling of incoming issues.
|
||||
#
|
||||
# The below names agree to abide by the
|
||||
# [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy)
|
||||
# and will be removed and replaced if they violate that agreement.
|
||||
#
|
||||
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
|
||||
# INSTRUCTIONS AT https://kubernetes.io/security/
|
||||
|
||||
cjcullen
|
||||
joelsmith
|
||||
liggitt
|
||||
philips
|
||||
tallclair
|
||||
3
staging/src/k8s.io/cri-streaming/code-of-conduct.md
Normal file
3
staging/src/k8s.io/cri-streaming/code-of-conduct.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Kubernetes Community Code of Conduct
|
||||
|
||||
Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)
|
||||
18
staging/src/k8s.io/cri-streaming/doc.go
Normal file
18
staging/src/k8s.io/cri-streaming/doc.go
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package cristreaming contains the staged module root for Kubernetes CRI streaming.
|
||||
package cristreaming
|
||||
41
staging/src/k8s.io/cri-streaming/go.mod
Normal file
41
staging/src/k8s.io/cri-streaming/go.mod
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// This is a generated file. Do not edit directly.
|
||||
|
||||
module k8s.io/cri-streaming
|
||||
|
||||
go 1.26.0
|
||||
|
||||
godebug default=go1.26
|
||||
|
||||
require (
|
||||
github.com/emicklei/go-restful/v3 v3.13.0
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
|
||||
github.com/stretchr/testify v1.11.1
|
||||
go.uber.org/goleak v1.3.0
|
||||
google.golang.org/grpc v1.78.0
|
||||
k8s.io/cri-api v0.0.0
|
||||
k8s.io/klog/v2 v2.140.0
|
||||
k8s.io/streaming v0.0.0
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/moby/spdystream v0.5.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
k8s.io/cri-api => ../cri-api
|
||||
k8s.io/streaming => ../streaming
|
||||
)
|
||||
91
staging/src/k8s.io/cri-streaming/go.sum
generated
Normal file
91
staging/src/k8s.io/cri-streaming/go.sum
generated
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
|
||||
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
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/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
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/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
|
||||
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329/go.mod h1:Alz8LEClvR7xKsrq3qzoc4N0guvVNSS8KmSChGYr9hs=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs=
|
||||
github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
|
||||
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
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/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/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
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/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
|
||||
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||
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/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
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/contrib/detectors/gcp v1.38.0/go.mod h1:SU+iU7nu5ud4oCb3LQOhIZ3nRLj6FNVrKgtflbaf2ts=
|
||||
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
|
||||
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
|
||||
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
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/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
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/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/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/api v0.0.0-20251029180050-ab9386a59fda/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
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.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
|
||||
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
||||
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
rules:
|
||||
# prevent import of k8s.io/kubernetes
|
||||
- selectorRegexp: k8s[.]io/kubernetes
|
||||
forbiddenPrefixes:
|
||||
- ''
|
||||
18
staging/src/k8s.io/cri-streaming/pkg/streaming/doc.go
Normal file
18
staging/src/k8s.io/cri-streaming/pkg/streaming/doc.go
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package streaming contains CRI server-side streaming interfaces and implementations.
|
||||
package streaming
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package httpresponse
|
||||
|
||||
import "net/http"
|
||||
|
||||
type wrapper interface {
|
||||
Unwrap() http.ResponseWriter
|
||||
}
|
||||
|
||||
// GetOriginal walks any response writer wrapper chain and returns the first writer.
|
||||
func GetOriginal(w http.ResponseWriter) http.ResponseWriter {
|
||||
for {
|
||||
decorator, ok := w.(wrapper)
|
||||
if !ok {
|
||||
return w
|
||||
}
|
||||
inner := decorator.Unwrap()
|
||||
if inner == w {
|
||||
panic("http.ResponseWriter decorator chain has a cycle")
|
||||
}
|
||||
w = inner
|
||||
}
|
||||
}
|
||||
|
|
@ -22,3 +22,17 @@ const ProtocolV1Name = "portforward.k8s.io"
|
|||
|
||||
// SupportedProtocols are the supported port forwarding protocols.
|
||||
var SupportedProtocols = []string{ProtocolV1Name}
|
||||
|
||||
const (
|
||||
PortForwardV1Name = ProtocolV1Name
|
||||
WebsocketsSPDYTunnelingPrefix = "SPDY/3.1+"
|
||||
KubernetesSuffix = ".k8s.io"
|
||||
WebsocketsSPDYTunnelingPortForwardV1 = WebsocketsSPDYTunnelingPrefix + PortForwardV1Name
|
||||
|
||||
PortHeader = "port"
|
||||
PortForwardRequestIDHeader = "requestID"
|
||||
|
||||
StreamType = "streamType"
|
||||
StreamTypeData = "data"
|
||||
StreamTypeError = "error"
|
||||
)
|
||||
|
|
@ -25,16 +25,14 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
api "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
"k8s.io/streaming/pkg/httpstream/spdy"
|
||||
utilruntime "k8s.io/streaming/pkg/runtime"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
func handleHTTPStreams(req *http.Request, w http.ResponseWriter, portForwarder PortForwarder, podName string, uid types.UID, supportedPortForwardProtocols []string, idleTimeout, streamCreationTimeout time.Duration) error {
|
||||
func handleHTTPStreams(req *http.Request, w http.ResponseWriter, portForwarder PortForwarder, podName string, uid string, supportedPortForwardProtocols []string, idleTimeout, streamCreationTimeout time.Duration) error {
|
||||
_, err := httpstream.Handshake(req, w, supportedPortForwardProtocols)
|
||||
// negotiated protocol isn't currently used server side, but could be in the future
|
||||
if err != nil {
|
||||
|
|
@ -75,9 +73,9 @@ func handleHTTPStreams(req *http.Request, w http.ResponseWriter, portForwarder P
|
|||
func httpStreamReceived(streams chan httpstream.Stream) func(httpstream.Stream, <-chan struct{}) error {
|
||||
return func(stream httpstream.Stream, replySent <-chan struct{}) error {
|
||||
// make sure it has a valid port header
|
||||
portString := stream.Headers().Get(api.PortHeader)
|
||||
portString := stream.Headers().Get(PortHeader)
|
||||
if len(portString) == 0 {
|
||||
return fmt.Errorf("%q header is required", api.PortHeader)
|
||||
return fmt.Errorf("%q header is required", PortHeader)
|
||||
}
|
||||
port, err := strconv.ParseUint(portString, 10, 16)
|
||||
if err != nil {
|
||||
|
|
@ -88,11 +86,11 @@ func httpStreamReceived(streams chan httpstream.Stream) func(httpstream.Stream,
|
|||
}
|
||||
|
||||
// make sure it has a valid stream type header
|
||||
streamType := stream.Headers().Get(api.StreamType)
|
||||
streamType := stream.Headers().Get(StreamType)
|
||||
if len(streamType) == 0 {
|
||||
return fmt.Errorf("%q header is required", api.StreamType)
|
||||
return fmt.Errorf("%q header is required", StreamType)
|
||||
}
|
||||
if streamType != api.StreamTypeError && streamType != api.StreamTypeData {
|
||||
if streamType != StreamTypeError && streamType != StreamTypeData {
|
||||
return fmt.Errorf("invalid stream type %q", streamType)
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +108,7 @@ type httpStreamHandler struct {
|
|||
streamPairs map[string]*httpStreamPair
|
||||
streamCreationTimeout time.Duration
|
||||
pod string
|
||||
uid types.UID
|
||||
uid string
|
||||
forwarder PortForwarder
|
||||
}
|
||||
|
||||
|
|
@ -173,7 +171,7 @@ func (h *httpStreamHandler) removeStreamPair(requestID string) {
|
|||
|
||||
// requestID returns the request id for stream.
|
||||
func (h *httpStreamHandler) requestID(stream httpstream.Stream) string {
|
||||
requestID := stream.Headers().Get(api.PortForwardRequestIDHeader)
|
||||
requestID := stream.Headers().Get(PortForwardRequestIDHeader)
|
||||
if len(requestID) == 0 {
|
||||
klog.V(5).InfoS("Connection stream received without requestID header", "connection", h.conn)
|
||||
// If we get here, it's because the connection came from an older client
|
||||
|
|
@ -194,11 +192,11 @@ func (h *httpStreamHandler) requestID(stream httpstream.Stream) string {
|
|||
// old clients that don't generate request ids. If there are concurrent
|
||||
// new connections, it's possible that 1 connection gets streams whose IDs
|
||||
// are not consecutive (e.g. 5 and 9 instead of 5 and 7).
|
||||
streamType := stream.Headers().Get(api.StreamType)
|
||||
streamType := stream.Headers().Get(StreamType)
|
||||
switch streamType {
|
||||
case api.StreamTypeError:
|
||||
case StreamTypeError:
|
||||
requestID = strconv.Itoa(int(stream.Identifier()))
|
||||
case api.StreamTypeData:
|
||||
case StreamTypeData:
|
||||
requestID = strconv.Itoa(int(stream.Identifier()) - 2)
|
||||
}
|
||||
|
||||
|
|
@ -220,7 +218,7 @@ Loop:
|
|||
break Loop
|
||||
case stream := <-h.streamChan:
|
||||
requestID := h.requestID(stream)
|
||||
streamType := stream.Headers().Get(api.StreamType)
|
||||
streamType := stream.Headers().Get(StreamType)
|
||||
klog.V(5).InfoS("Connection request received new type of stream", "connection", h.conn, "request", requestID, "streamType", streamType)
|
||||
|
||||
p, created := h.getStreamPair(requestID)
|
||||
|
|
@ -245,7 +243,7 @@ func (h *httpStreamHandler) portForward(p *httpStreamPair) {
|
|||
defer p.dataStream.Close()
|
||||
defer p.errorStream.Close()
|
||||
|
||||
portString := p.dataStream.Headers().Get(api.PortHeader)
|
||||
portString := p.dataStream.Headers().Get(PortHeader)
|
||||
port, _ := strconv.ParseInt(portString, 10, 32)
|
||||
|
||||
klog.V(5).InfoS("Connection request invoking forwarder.PortForward for port", "connection", h.conn, "request", p.requestID, "port", portString)
|
||||
|
|
@ -291,13 +289,13 @@ func (p *httpStreamPair) add(stream httpstream.Stream) (bool, error) {
|
|||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
switch stream.Headers().Get(api.StreamType) {
|
||||
case api.StreamTypeError:
|
||||
switch stream.Headers().Get(StreamType) {
|
||||
case StreamTypeError:
|
||||
if p.errorStream != nil {
|
||||
return false, errors.New("error stream already assigned")
|
||||
}
|
||||
p.errorStream = stream
|
||||
case api.StreamTypeData:
|
||||
case StreamTypeData:
|
||||
if p.dataStream != nil {
|
||||
return false, errors.New("data stream already assigned")
|
||||
}
|
||||
|
|
@ -21,8 +21,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
api "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
)
|
||||
|
||||
func TestHTTPStreamReceived(t *testing.T) {
|
||||
|
|
@ -148,7 +147,7 @@ func TestGetStreamPair(t *testing.T) {
|
|||
|
||||
// removed via complete
|
||||
dataStream := newFakeHTTPStream()
|
||||
dataStream.headers.Set(api.StreamType, api.StreamTypeData)
|
||||
dataStream.headers.Set(StreamType, StreamTypeData)
|
||||
complete, err := p.add(dataStream)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error adding data stream to pair: %v", err)
|
||||
|
|
@ -158,7 +157,7 @@ func TestGetStreamPair(t *testing.T) {
|
|||
}
|
||||
|
||||
errorStream := newFakeHTTPStream()
|
||||
errorStream.headers.Set(api.StreamType, api.StreamTypeError)
|
||||
errorStream.headers.Set(StreamType, StreamTypeError)
|
||||
complete, err = p.add(errorStream)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error adding error stream to pair: %v", err)
|
||||
|
|
@ -210,20 +209,20 @@ func TestRequestID(t *testing.T) {
|
|||
h := &httpStreamHandler{}
|
||||
|
||||
s := newFakeHTTPStream()
|
||||
s.headers.Set(api.StreamType, api.StreamTypeError)
|
||||
s.headers.Set(StreamType, StreamTypeError)
|
||||
s.id = 1
|
||||
if e, a := "1", h.requestID(s); e != a {
|
||||
t.Errorf("expected %q, got %q", e, a)
|
||||
}
|
||||
|
||||
s.headers.Set(api.StreamType, api.StreamTypeData)
|
||||
s.headers.Set(StreamType, StreamTypeData)
|
||||
s.id = 3
|
||||
if e, a := "1", h.requestID(s); e != a {
|
||||
t.Errorf("expected %q, got %q", e, a)
|
||||
}
|
||||
|
||||
s.id = 7
|
||||
s.headers.Set(api.PortForwardRequestIDHeader, "2")
|
||||
s.headers.Set(PortForwardRequestIDHeader, "2")
|
||||
if e, a := "2", h.requestID(s); e != a {
|
||||
t.Errorf("expected %q, got %q", e, a)
|
||||
}
|
||||
|
|
@ -22,16 +22,15 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/wsstream"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/streaming/pkg/httpstream/wsstream"
|
||||
"k8s.io/streaming/pkg/runtime"
|
||||
)
|
||||
|
||||
// PortForwarder knows how to forward content from a data stream to/from a port
|
||||
// in a pod.
|
||||
type PortForwarder interface {
|
||||
// PortForwarder copies data between a data stream and a port in a pod.
|
||||
PortForward(ctx context.Context, name string, uid types.UID, port int32, stream io.ReadWriteCloser) error
|
||||
PortForward(ctx context.Context, name string, uid string, port int32, stream io.ReadWriteCloser) error
|
||||
}
|
||||
|
||||
// ServePortForward handles a port forwarding request. A single request is
|
||||
|
|
@ -39,7 +38,7 @@ type PortForwarder interface {
|
|||
// been timed out due to idleness. This function handles multiple forwarded
|
||||
// connections; i.e., multiple `curl http://localhost:8888/` requests will be
|
||||
// handled by a single invocation of ServePortForward.
|
||||
func ServePortForward(w http.ResponseWriter, req *http.Request, portForwarder PortForwarder, podName string, uid types.UID, portForwardOptions *V4Options, idleTimeout time.Duration, streamCreationTimeout time.Duration, supportedProtocols []string) {
|
||||
func ServePortForward(w http.ResponseWriter, req *http.Request, portForwarder PortForwarder, podName string, uid string, portForwardOptions *V4Options, idleTimeout time.Duration, streamCreationTimeout time.Duration, supportedProtocols []string) {
|
||||
var err error
|
||||
if wsstream.IsWebSocketRequest(req) {
|
||||
err = handleWebSocketStreams(req, w, portForwarder, podName, uid, portForwardOptions, supportedProtocols, idleTimeout, streamCreationTimeout)
|
||||
|
|
@ -29,11 +29,9 @@ import (
|
|||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
api "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/wsstream"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/endpoints/responsewriter"
|
||||
"k8s.io/cri-streaming/pkg/streaming/internal/httpresponse"
|
||||
"k8s.io/streaming/pkg/httpstream/wsstream"
|
||||
"k8s.io/streaming/pkg/runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -58,15 +56,15 @@ func NewV4Options(req *http.Request) (*V4Options, error) {
|
|||
return &V4Options{}, nil
|
||||
}
|
||||
|
||||
portStrings := req.URL.Query()[api.PortHeader]
|
||||
portStrings := req.URL.Query()[PortHeader]
|
||||
if len(portStrings) == 0 {
|
||||
return nil, fmt.Errorf("query parameter %q is required", api.PortHeader)
|
||||
return nil, fmt.Errorf("query parameter %q is required", PortHeader)
|
||||
}
|
||||
|
||||
ports := make([]int32, 0, len(portStrings))
|
||||
for _, portString := range portStrings {
|
||||
if len(portString) == 0 {
|
||||
return nil, fmt.Errorf("query parameter %q cannot be empty", api.PortHeader)
|
||||
return nil, fmt.Errorf("query parameter %q cannot be empty", PortHeader)
|
||||
}
|
||||
for _, p := range strings.Split(portString, ",") {
|
||||
port, err := strconv.ParseUint(p, 10, 16)
|
||||
|
|
@ -94,7 +92,7 @@ func BuildV4Options(ports []int32) (*V4Options, error) {
|
|||
// a PortForwarder. A pair of streams are created per port (DATA n,
|
||||
// ERROR n+1). The associated port is written to each stream as a unsigned 16
|
||||
// bit integer in little endian format.
|
||||
func handleWebSocketStreams(req *http.Request, w http.ResponseWriter, portForwarder PortForwarder, podName string, uid types.UID, opts *V4Options, supportedPortForwardProtocols []string, idleTimeout, streamCreationTimeout time.Duration) error {
|
||||
func handleWebSocketStreams(req *http.Request, w http.ResponseWriter, portForwarder PortForwarder, podName string, uid string, opts *V4Options, supportedPortForwardProtocols []string, idleTimeout, streamCreationTimeout time.Duration) error {
|
||||
channels := make([]wsstream.ChannelType, 0, len(opts.Ports)*2)
|
||||
for i := 0; i < len(opts.Ports); i++ {
|
||||
channels = append(channels, wsstream.ReadWriteChannel, wsstream.WriteChannel)
|
||||
|
|
@ -114,7 +112,7 @@ func handleWebSocketStreams(req *http.Request, w http.ResponseWriter, portForwar
|
|||
},
|
||||
})
|
||||
conn.SetIdleTimeout(idleTimeout)
|
||||
_, streams, err := conn.Open(responsewriter.GetOriginal(w), req)
|
||||
_, streams, err := conn.Open(httpresponse.GetOriginal(w), req)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("unable to upgrade websocket connection: %v", err)
|
||||
return err
|
||||
|
|
@ -161,7 +159,7 @@ type websocketStreamHandler struct {
|
|||
conn *wsstream.Conn
|
||||
streamPairs []*websocketStreamPair
|
||||
pod string
|
||||
uid types.UID
|
||||
uid string
|
||||
forwarder PortForwarder
|
||||
}
|
||||
|
||||
|
|
@ -23,23 +23,19 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
"k8s.io/streaming/pkg/runtime"
|
||||
)
|
||||
|
||||
// Attacher knows how to attach to a running container in a pod.
|
||||
type Attacher interface {
|
||||
// AttachContainer attaches to the running container in the pod, copying data between in/out/err
|
||||
// and the container's stdin/stdout/stderr.
|
||||
AttachContainer(ctx context.Context, name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error
|
||||
AttachContainer(ctx context.Context, name string, uid string, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan TerminalSize) error
|
||||
}
|
||||
|
||||
// ServeAttach handles requests to attach to a container. After creating/receiving the required
|
||||
// streams, it delegates the actual attaching to attacher.
|
||||
func ServeAttach(w http.ResponseWriter, req *http.Request, attacher Attacher, podName string, uid types.UID, container string, streamOpts *Options, idleTimeout, streamCreationTimeout time.Duration, supportedProtocols []string) {
|
||||
func ServeAttach(w http.ResponseWriter, req *http.Request, attacher Attacher, podName string, uid string, container string, streamOpts *Options, idleTimeout, streamCreationTimeout time.Duration, supportedProtocols []string) {
|
||||
ctx, ok := createStreams(req, w, streamOpts, supportedProtocols, idleTimeout, streamCreationTimeout)
|
||||
if !ok {
|
||||
// error is handled by createStreams
|
||||
|
|
@ -51,10 +47,10 @@ func ServeAttach(w http.ResponseWriter, req *http.Request, attacher Attacher, po
|
|||
if err != nil {
|
||||
err = fmt.Errorf("error attaching to container: %v", err)
|
||||
runtime.HandleError(err)
|
||||
ctx.writeStatus(apierrors.NewInternalError(err))
|
||||
ctx.writeStatus(newInternalError(err))
|
||||
} else {
|
||||
ctx.writeStatus(&apierrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusSuccess,
|
||||
ctx.writeStatus(&streamStatusError{ErrStatus: streamStatus{
|
||||
Status: statusSuccess,
|
||||
}})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import "time"
|
||||
|
||||
const (
|
||||
DefaultStreamCreationTimeout = 30 * time.Second
|
||||
|
||||
ExecStdinParam = "input"
|
||||
ExecStdoutParam = "output"
|
||||
ExecStderrParam = "error"
|
||||
ExecTTYParam = "tty"
|
||||
|
||||
StreamType = "streamType"
|
||||
StreamTypeStdin = "stdin"
|
||||
StreamTypeStdout = "stdout"
|
||||
StreamTypeStderr = "stderr"
|
||||
StreamTypeError = "error"
|
||||
StreamTypeResize = "resize"
|
||||
|
||||
// The SPDY subprotocol "channel.k8s.io" is used for remote command
|
||||
// attachment/execution. This represents the initial unversioned subprotocol,
|
||||
// which has the known bugs https://issues.k8s.io/13394 and
|
||||
// https://issues.k8s.io/13395.
|
||||
StreamProtocolV1Name = "channel.k8s.io"
|
||||
|
||||
// The SPDY subprotocol "v2.channel.k8s.io" is used for remote command
|
||||
// attachment/execution. It is the second version of the subprotocol and
|
||||
// resolves the issues present in the first version.
|
||||
StreamProtocolV2Name = "v2.channel.k8s.io"
|
||||
|
||||
// The SPDY subprotocol "v3.channel.k8s.io" is used for remote command
|
||||
// attachment/execution. It is the third version of the subprotocol and
|
||||
// adds support for resizing container terminals.
|
||||
StreamProtocolV3Name = "v3.channel.k8s.io"
|
||||
|
||||
// The SPDY subprotocol "v4.channel.k8s.io" is used for remote command
|
||||
// attachment/execution. It is the 4th version of the subprotocol and
|
||||
// adds support for exit codes.
|
||||
StreamProtocolV4Name = "v4.channel.k8s.io"
|
||||
|
||||
// The subprotocol "v5.channel.k8s.io" is used for remote command
|
||||
// attachment/execution. It is the 5th version of the subprotocol and
|
||||
// adds support for a CLOSE signal.
|
||||
StreamProtocolV5Name = "v5.channel.k8s.io"
|
||||
|
||||
NonZeroExitCodeReason = "NonZeroExitCode"
|
||||
ExitCodeCauseType = "ExitCode"
|
||||
|
||||
// RemoteCommand stream identifiers. The first three identifiers (for STDIN,
|
||||
// STDOUT, STDERR) are the same as their file descriptors.
|
||||
StreamStdIn = 0
|
||||
StreamStdOut = 1
|
||||
StreamStdErr = 2
|
||||
StreamErr = 3
|
||||
StreamResize = 4
|
||||
StreamClose = 255
|
||||
)
|
||||
|
||||
var SupportedStreamingProtocols = []string{StreamProtocolV4Name, StreamProtocolV3Name, StreamProtocolV2Name, StreamProtocolV1Name}
|
||||
|
|
@ -23,12 +23,7 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
"k8s.io/streaming/pkg/runtime"
|
||||
utilexec "k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
|
|
@ -36,13 +31,13 @@ import (
|
|||
type Executor interface {
|
||||
// ExecInContainer executes a command in a container in the pod, copying data
|
||||
// between in/out/err and the container's stdin/stdout/stderr.
|
||||
ExecInContainer(ctx context.Context, name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error
|
||||
ExecInContainer(ctx context.Context, name string, uid string, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan TerminalSize, timeout time.Duration) error
|
||||
}
|
||||
|
||||
// ServeExec handles requests to execute a command in a container. After
|
||||
// creating/receiving the required streams, it delegates the actual execution
|
||||
// to the executor.
|
||||
func ServeExec(w http.ResponseWriter, req *http.Request, executor Executor, podName string, uid types.UID, container string, cmd []string, streamOpts *Options, idleTimeout, streamCreationTimeout time.Duration, supportedProtocols []string) {
|
||||
func ServeExec(w http.ResponseWriter, req *http.Request, executor Executor, podName string, uid string, container string, cmd []string, streamOpts *Options, idleTimeout, streamCreationTimeout time.Duration, supportedProtocols []string) {
|
||||
ctx, ok := createStreams(req, w, streamOpts, supportedProtocols, idleTimeout, streamCreationTimeout)
|
||||
if !ok {
|
||||
// error is handled by createStreams
|
||||
|
|
@ -54,13 +49,13 @@ func ServeExec(w http.ResponseWriter, req *http.Request, executor Executor, podN
|
|||
if err != nil {
|
||||
if exitErr, ok := err.(utilexec.ExitError); ok && exitErr.Exited() {
|
||||
rc := exitErr.ExitStatus()
|
||||
ctx.writeStatus(&apierrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Reason: remotecommandconsts.NonZeroExitCodeReason,
|
||||
Details: &metav1.StatusDetails{
|
||||
Causes: []metav1.StatusCause{
|
||||
ctx.writeStatus(&streamStatusError{ErrStatus: streamStatus{
|
||||
Status: statusFailure,
|
||||
Reason: NonZeroExitCodeReason,
|
||||
Details: &streamStatusDetails{
|
||||
Causes: []streamStatusCause{
|
||||
{
|
||||
Type: remotecommandconsts.ExitCodeCauseType,
|
||||
Type: ExitCodeCauseType,
|
||||
Message: fmt.Sprintf("%d", rc),
|
||||
},
|
||||
},
|
||||
|
|
@ -70,11 +65,11 @@ func ServeExec(w http.ResponseWriter, req *http.Request, executor Executor, podN
|
|||
} else {
|
||||
err = fmt.Errorf("error executing command in container: %v", err)
|
||||
runtime.HandleError(err)
|
||||
ctx.writeStatus(apierrors.NewInternalError(err))
|
||||
ctx.writeStatus(newInternalError(err))
|
||||
}
|
||||
} else {
|
||||
ctx.writeStatus(&apierrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusSuccess,
|
||||
ctx.writeStatus(&streamStatusError{ErrStatus: streamStatus{
|
||||
Status: statusSuccess,
|
||||
}})
|
||||
}
|
||||
}
|
||||
|
|
@ -25,15 +25,10 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
api "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/wsstream"
|
||||
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
"k8s.io/streaming/pkg/httpstream"
|
||||
"k8s.io/streaming/pkg/httpstream/spdy"
|
||||
"k8s.io/streaming/pkg/httpstream/wsstream"
|
||||
"k8s.io/streaming/pkg/runtime"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
|
@ -49,10 +44,10 @@ type Options struct {
|
|||
|
||||
// NewOptions creates a new Options from the Request.
|
||||
func NewOptions(req *http.Request) (*Options, error) {
|
||||
tty := req.FormValue(api.ExecTTYParam) == "1"
|
||||
stdin := req.FormValue(api.ExecStdinParam) == "1"
|
||||
stdout := req.FormValue(api.ExecStdoutParam) == "1"
|
||||
stderr := req.FormValue(api.ExecStderrParam) == "1"
|
||||
tty := req.FormValue(ExecTTYParam) == "1"
|
||||
stdin := req.FormValue(ExecStdinParam) == "1"
|
||||
stdout := req.FormValue(ExecStdoutParam) == "1"
|
||||
stderr := req.FormValue(ExecStderrParam) == "1"
|
||||
if tty && stderr {
|
||||
// TODO: make this an error before we reach this method
|
||||
klog.V(4).InfoS("Access to exec with tty and stderr is not supported, bypassing stderr")
|
||||
|
|
@ -78,9 +73,9 @@ type connectionContext struct {
|
|||
stdinStream io.ReadCloser
|
||||
stdoutStream io.WriteCloser
|
||||
stderrStream io.WriteCloser
|
||||
writeStatus func(status *apierrors.StatusError) error
|
||||
writeStatus func(status *streamStatusError) error
|
||||
resizeStream io.ReadCloser
|
||||
resizeChan chan remotecommand.TerminalSize
|
||||
resizeChan chan TerminalSize
|
||||
tty bool
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +111,7 @@ func createStreams(req *http.Request, w http.ResponseWriter, opts *Options, supp
|
|||
}
|
||||
|
||||
if ctx.resizeStream != nil {
|
||||
ctx.resizeChan = make(chan remotecommand.TerminalSize)
|
||||
ctx.resizeChan = make(chan TerminalSize)
|
||||
go handleResizeEvents(req.Context(), ctx.resizeStream, ctx.resizeChan)
|
||||
}
|
||||
|
||||
|
|
@ -149,16 +144,16 @@ func createHTTPStreamStreams(req *http.Request, w http.ResponseWriter, opts *Opt
|
|||
|
||||
var handler protocolHandler
|
||||
switch protocol {
|
||||
case remotecommandconsts.StreamProtocolV4Name:
|
||||
case StreamProtocolV4Name:
|
||||
handler = &v4ProtocolHandler{}
|
||||
case remotecommandconsts.StreamProtocolV3Name:
|
||||
case StreamProtocolV3Name:
|
||||
handler = &v3ProtocolHandler{}
|
||||
case remotecommandconsts.StreamProtocolV2Name:
|
||||
case StreamProtocolV2Name:
|
||||
handler = &v2ProtocolHandler{}
|
||||
case "":
|
||||
klog.V(4).InfoS("Client did not request protocol negotiation. Falling back", "protocol", remotecommandconsts.StreamProtocolV1Name)
|
||||
klog.V(4).InfoS("Client did not request protocol negotiation. Falling back", "protocol", StreamProtocolV1Name)
|
||||
fallthrough
|
||||
case remotecommandconsts.StreamProtocolV1Name:
|
||||
case StreamProtocolV1Name:
|
||||
handler = &v1ProtocolHandler{}
|
||||
}
|
||||
|
||||
|
|
@ -201,7 +196,7 @@ type protocolHandler interface {
|
|||
}
|
||||
|
||||
// v4ProtocolHandler implements the V4 protocol version for streaming command execution. It only differs
|
||||
// in from v3 in the error stream format using an json-marshaled metav1.Status which carries
|
||||
// in from v3 in the error stream format using a json-marshaled status object which carries
|
||||
// the process' exit code.
|
||||
type v4ProtocolHandler struct{}
|
||||
|
||||
|
|
@ -215,21 +210,21 @@ WaitForStreams:
|
|||
for {
|
||||
select {
|
||||
case stream := <-streams:
|
||||
streamType := stream.Headers().Get(api.StreamType)
|
||||
streamType := stream.Headers().Get(StreamType)
|
||||
switch streamType {
|
||||
case api.StreamTypeError:
|
||||
case StreamTypeError:
|
||||
ctx.writeStatus = v4WriteStatusFunc(stream) // write json errors
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdin:
|
||||
case StreamTypeStdin:
|
||||
ctx.stdinStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdout:
|
||||
case StreamTypeStdout:
|
||||
ctx.stdoutStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStderr:
|
||||
case StreamTypeStderr:
|
||||
ctx.stderrStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeResize:
|
||||
case StreamTypeResize:
|
||||
ctx.resizeStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
default:
|
||||
|
|
@ -266,21 +261,21 @@ WaitForStreams:
|
|||
for {
|
||||
select {
|
||||
case stream := <-streams:
|
||||
streamType := stream.Headers().Get(api.StreamType)
|
||||
streamType := stream.Headers().Get(StreamType)
|
||||
switch streamType {
|
||||
case api.StreamTypeError:
|
||||
case StreamTypeError:
|
||||
ctx.writeStatus = v1WriteStatusFunc(stream)
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdin:
|
||||
case StreamTypeStdin:
|
||||
ctx.stdinStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdout:
|
||||
case StreamTypeStdout:
|
||||
ctx.stdoutStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStderr:
|
||||
case StreamTypeStderr:
|
||||
ctx.stderrStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeResize:
|
||||
case StreamTypeResize:
|
||||
ctx.resizeStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
default:
|
||||
|
|
@ -317,18 +312,18 @@ WaitForStreams:
|
|||
for {
|
||||
select {
|
||||
case stream := <-streams:
|
||||
streamType := stream.Headers().Get(api.StreamType)
|
||||
streamType := stream.Headers().Get(StreamType)
|
||||
switch streamType {
|
||||
case api.StreamTypeError:
|
||||
case StreamTypeError:
|
||||
ctx.writeStatus = v1WriteStatusFunc(stream)
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdin:
|
||||
case StreamTypeStdin:
|
||||
ctx.stdinStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdout:
|
||||
case StreamTypeStdout:
|
||||
ctx.stdoutStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStderr:
|
||||
case StreamTypeStderr:
|
||||
ctx.stderrStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
default:
|
||||
|
|
@ -365,9 +360,9 @@ WaitForStreams:
|
|||
for {
|
||||
select {
|
||||
case stream := <-streams:
|
||||
streamType := stream.Headers().Get(api.StreamType)
|
||||
streamType := stream.Headers().Get(StreamType)
|
||||
switch streamType {
|
||||
case api.StreamTypeError:
|
||||
case StreamTypeError:
|
||||
ctx.writeStatus = v1WriteStatusFunc(stream)
|
||||
|
||||
// This defer statement shouldn't be here, but due to previous refactoring, it ended up in
|
||||
|
|
@ -376,13 +371,13 @@ WaitForStreams:
|
|||
defer stream.Reset()
|
||||
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdin:
|
||||
case StreamTypeStdin:
|
||||
ctx.stdinStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStdout:
|
||||
case StreamTypeStdout:
|
||||
ctx.stdoutStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
case api.StreamTypeStderr:
|
||||
case StreamTypeStderr:
|
||||
ctx.stderrStream = stream
|
||||
go waitStreamReply(stream.replySent, replyChan, stop)
|
||||
default:
|
||||
|
|
@ -410,13 +405,13 @@ WaitForStreams:
|
|||
// supportsTerminalResizing returns false because v1ProtocolHandler doesn't support it.
|
||||
func (*v1ProtocolHandler) supportsTerminalResizing() bool { return false }
|
||||
|
||||
func handleResizeEvents(reqctx context.Context, stream io.Reader, channel chan<- remotecommand.TerminalSize) {
|
||||
func handleResizeEvents(reqctx context.Context, stream io.Reader, channel chan<- TerminalSize) {
|
||||
defer runtime.HandleCrash()
|
||||
defer close(channel)
|
||||
|
||||
decoder := json.NewDecoder(stream)
|
||||
for {
|
||||
size := remotecommand.TerminalSize{}
|
||||
size := TerminalSize{}
|
||||
if err := decoder.Decode(&size); err != nil {
|
||||
break
|
||||
}
|
||||
|
|
@ -429,9 +424,9 @@ func handleResizeEvents(reqctx context.Context, stream io.Reader, channel chan<-
|
|||
}
|
||||
}
|
||||
|
||||
func v1WriteStatusFunc(stream io.Writer) func(status *apierrors.StatusError) error {
|
||||
return func(status *apierrors.StatusError) error {
|
||||
if status.Status().Status == metav1.StatusSuccess {
|
||||
func v1WriteStatusFunc(stream io.Writer) func(status *streamStatusError) error {
|
||||
return func(status *streamStatusError) error {
|
||||
if status.status().Status == statusSuccess {
|
||||
return nil // send error messages
|
||||
}
|
||||
_, err := stream.Write([]byte(status.Error()))
|
||||
|
|
@ -439,11 +434,11 @@ func v1WriteStatusFunc(stream io.Writer) func(status *apierrors.StatusError) err
|
|||
}
|
||||
}
|
||||
|
||||
// v4WriteStatusFunc returns a WriteStatusFunc that marshals a given api Status
|
||||
// v4WriteStatusFunc returns a WriteStatusFunc that marshals a status object
|
||||
// as json in the error channel.
|
||||
func v4WriteStatusFunc(stream io.Writer) func(status *apierrors.StatusError) error {
|
||||
return func(status *apierrors.StatusError) error {
|
||||
bs, err := json.Marshal(status.Status())
|
||||
func v4WriteStatusFunc(stream io.Writer) func(status *streamStatusError) error {
|
||||
return func(status *streamStatusError) error {
|
||||
bs, err := json.Marshal(status.status())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -25,12 +25,10 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/goleak"
|
||||
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
func TestHandleResizeEvents(t *testing.T) {
|
||||
var testTerminalSize remotecommand.TerminalSize
|
||||
var testTerminalSize TerminalSize
|
||||
rawTerminalSize, err := json.Marshal(&testTerminalSize)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
|
@ -63,7 +61,7 @@ func TestHandleResizeEvents(t *testing.T) {
|
|||
ctx, cancel := context.WithCancel(context.Background())
|
||||
connCtx := connectionContext{
|
||||
resizeStream: io.NopCloser(bytes.NewReader(testCase.resizeStreamData)),
|
||||
resizeChan: make(chan remotecommand.TerminalSize),
|
||||
resizeChan: make(chan TerminalSize),
|
||||
}
|
||||
|
||||
go handleResizeEvents(ctx, connCtx.resizeStream, connCtx.resizeChan)
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
// TerminalSize represents the width and height of a terminal.
|
||||
type TerminalSize struct {
|
||||
Width uint16
|
||||
Height uint16
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const (
|
||||
statusSuccess = "Success"
|
||||
statusFailure = "Failure"
|
||||
|
||||
statusReasonInternalError = "InternalError"
|
||||
)
|
||||
|
||||
// streamStatusError carries status details written to the error stream.
|
||||
type streamStatusError struct {
|
||||
ErrStatus streamStatus
|
||||
}
|
||||
|
||||
func (e *streamStatusError) Error() string {
|
||||
return e.ErrStatus.Message
|
||||
}
|
||||
|
||||
func (e *streamStatusError) status() streamStatus {
|
||||
return e.ErrStatus
|
||||
}
|
||||
|
||||
func newInternalError(err error) *streamStatusError {
|
||||
return &streamStatusError{
|
||||
ErrStatus: streamStatus{
|
||||
Status: statusFailure,
|
||||
Reason: statusReasonInternalError,
|
||||
Message: fmt.Sprintf("Internal error occurred: %v", err),
|
||||
Code: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type streamStatus struct {
|
||||
Status string `json:"status,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Details *streamStatusDetails `json:"details,omitempty"`
|
||||
Code int32 `json:"code,omitempty"`
|
||||
}
|
||||
|
||||
type streamStatusDetails struct {
|
||||
Causes []streamStatusCause `json:"causes,omitempty"`
|
||||
}
|
||||
|
||||
// streamStatusCause uses "reason" on the wire to match metav1.StatusCause.
|
||||
type streamStatusCause struct {
|
||||
Type string `json:"reason,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
|
@ -21,9 +21,9 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/httpstream/wsstream"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/endpoints/responsewriter"
|
||||
"k8s.io/cri-streaming/pkg/streaming/internal/httpresponse"
|
||||
"k8s.io/streaming/pkg/httpstream/wsstream"
|
||||
"k8s.io/streaming/pkg/runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -95,7 +95,7 @@ func createWebSocketStreams(req *http.Request, w http.ResponseWriter, opts *Opti
|
|||
},
|
||||
})
|
||||
conn.SetIdleTimeout(idleTimeout)
|
||||
negotiatedProtocol, streams, err := conn.Open(responsewriter.GetOriginal(w), req)
|
||||
negotiatedProtocol, streams, err := conn.Open(httpresponse.GetOriginal(w), req)
|
||||
if err != nil {
|
||||
runtime.HandleError(fmt.Errorf("unable to upgrade websocket connection: %v", err))
|
||||
return nil, false
|
||||
|
|
@ -32,12 +32,9 @@ import (
|
|||
|
||||
restful "github.com/emicklei/go-restful/v3"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||
"k8s.io/kubelet/pkg/cri/streaming/portforward"
|
||||
remotecommandserver "k8s.io/kubelet/pkg/cri/streaming/remotecommand"
|
||||
"k8s.io/cri-streaming/pkg/streaming/portforward"
|
||||
remotecommandserver "k8s.io/cri-streaming/pkg/streaming/remotecommand"
|
||||
)
|
||||
|
||||
// Server is the library interface to serve the stream requests.
|
||||
|
|
@ -62,8 +59,8 @@ type Server interface {
|
|||
|
||||
// Runtime is the interface to execute the commands and provide the streams.
|
||||
type Runtime interface {
|
||||
Exec(ctx context.Context, containerID string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error
|
||||
Attach(ctx context.Context, containerID string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error
|
||||
Exec(ctx context.Context, containerID string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error
|
||||
Attach(ctx context.Context, containerID string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error
|
||||
PortForward(ctx context.Context, podSandboxID string, port int32, stream io.ReadWriteCloser) error
|
||||
}
|
||||
|
||||
|
|
@ -99,8 +96,8 @@ type Config struct {
|
|||
// some fields like Addr must still be provided.
|
||||
var DefaultConfig = Config{
|
||||
StreamIdleTimeout: 4 * time.Hour,
|
||||
StreamCreationTimeout: remotecommandconsts.DefaultStreamCreationTimeout,
|
||||
SupportedRemoteCommandProtocols: remotecommandconsts.SupportedStreamingProtocols,
|
||||
StreamCreationTimeout: remotecommandserver.DefaultStreamCreationTimeout,
|
||||
SupportedRemoteCommandProtocols: remotecommandserver.SupportedStreamingProtocols,
|
||||
SupportedPortForwardProtocols: portforward.SupportedProtocols,
|
||||
}
|
||||
|
||||
|
|
@ -370,14 +367,14 @@ var _ remotecommandserver.Executor = &criAdapter{}
|
|||
var _ remotecommandserver.Attacher = &criAdapter{}
|
||||
var _ portforward.PortForwarder = &criAdapter{}
|
||||
|
||||
func (a *criAdapter) ExecInContainer(ctx context.Context, podName string, podUID types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
func (a *criAdapter) ExecInContainer(ctx context.Context, podName string, podUID string, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize, timeout time.Duration) error {
|
||||
return a.Runtime.Exec(ctx, container, cmd, in, out, err, tty, resize)
|
||||
}
|
||||
|
||||
func (a *criAdapter) AttachContainer(ctx context.Context, podName string, podUID types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
func (a *criAdapter) AttachContainer(ctx context.Context, podName string, podUID string, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error {
|
||||
return a.Runtime.Attach(ctx, container, in, out, err, tty, resize)
|
||||
}
|
||||
|
||||
func (a *criAdapter) PortForward(ctx context.Context, podName string, podUID types.UID, port int32, stream io.ReadWriteCloser) error {
|
||||
func (a *criAdapter) PortForward(ctx context.Context, podName string, podUID string, port int32, stream io.ReadWriteCloser) error {
|
||||
return a.Runtime.PortForward(ctx, podName, port, stream)
|
||||
}
|
||||
|
|
@ -19,24 +19,20 @@ package streaming
|
|||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
api "k8s.io/api/core/v1"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
"k8s.io/client-go/transport/spdy"
|
||||
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||
kubeletportforward "k8s.io/kubelet/pkg/cri/streaming/portforward"
|
||||
remotecommandserver "k8s.io/cri-streaming/pkg/streaming/remotecommand"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -277,32 +273,22 @@ func TestServePortForward(t *testing.T) {
|
|||
|
||||
resp, err := s.GetPortForward(&runtimeapi.PortForwardRequest{
|
||||
PodSandboxId: testPodSandboxID,
|
||||
Port: []int32{testPort},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
reqURL, err := url.Parse(resp.Url)
|
||||
require.NoError(t, err)
|
||||
|
||||
transport, upgrader, err := spdy.RoundTripperFor(&restclient.Config{})
|
||||
require.NoError(t, err)
|
||||
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", reqURL)
|
||||
streamConn, _, err := dialer.Dial(kubeletportforward.ProtocolV1Name)
|
||||
require.NoError(t, err)
|
||||
defer streamConn.Close()
|
||||
wsConn := dialWebSocket(t, reqURL)
|
||||
defer wsConn.Close()
|
||||
|
||||
// Create the streams.
|
||||
headers := http.Header{}
|
||||
// Error stream is required, but unused in this test.
|
||||
headers.Set(api.StreamType, api.StreamTypeError)
|
||||
headers.Set(api.PortHeader, strconv.Itoa(testPort))
|
||||
_, err = streamConn.CreateStream(headers)
|
||||
require.NoError(t, err)
|
||||
// Setup the data stream.
|
||||
headers.Set(api.StreamType, api.StreamTypeData)
|
||||
headers.Set(api.PortHeader, strconv.Itoa(testPort))
|
||||
stream, err := streamConn.CreateStream(headers)
|
||||
require.NoError(t, err)
|
||||
expectedPortBytes := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(expectedPortBytes, uint16(testPort))
|
||||
requireWebSocketPayload(t, wsConn, 0, expectedPortBytes, false)
|
||||
requireWebSocketPayload(t, wsConn, 1, expectedPortBytes, false)
|
||||
|
||||
doClientStreams(t, "portforward", stream, stream, nil)
|
||||
writeWebSocketPayload(t, wsConn, 0, []byte("portforward"+testInput))
|
||||
requireWebSocketPayload(t, wsConn, 0, []byte("portforward"+testOutput), false)
|
||||
}
|
||||
|
||||
// Run the remote command test.
|
||||
|
|
@ -338,38 +324,12 @@ func runRemoteCommandTest(t *testing.T, commandType string) {
|
|||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
wsConn := dialWebSocket(t, reqURL)
|
||||
defer wsConn.Close()
|
||||
|
||||
stdinR, stdinW := io.Pipe()
|
||||
stdoutR, stdoutW := io.Pipe()
|
||||
stderrR, stderrW := io.Pipe()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
exec, err := remotecommand.NewSPDYExecutor(&restclient.Config{}, "POST", reqURL)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
opts := remotecommand.StreamOptions{
|
||||
Stdin: stdinR,
|
||||
Stdout: stdoutW,
|
||||
Stderr: stderrW,
|
||||
Tty: false,
|
||||
}
|
||||
if err = exec.StreamWithContext(context.Background(), opts); err != nil {
|
||||
t.Errorf("unexpected error %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
doClientStreams(t, commandType, stdinW, stdoutR, stderrR)
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
requireWebSocketPayload(t, wsConn, 2, []byte(commandType+testErr), false)
|
||||
writeWebSocketPayload(t, wsConn, 0, []byte(commandType+testInput))
|
||||
requireWebSocketPayload(t, wsConn, 1, []byte(commandType+testOutput), true)
|
||||
|
||||
// Repeat request with the same URL should be a 404.
|
||||
resp, err := http.Get(reqURL.String())
|
||||
|
|
@ -419,13 +379,13 @@ type fakeRuntime struct {
|
|||
t *testing.T
|
||||
}
|
||||
|
||||
func (f *fakeRuntime) Exec(_ context.Context, containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
func (f *fakeRuntime) Exec(_ context.Context, containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error {
|
||||
assert.Equal(f.t, testContainerID, containerID)
|
||||
doServerStreams(f.t, "exec", stdin, stdout, stderr)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeRuntime) Attach(_ context.Context, containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
func (f *fakeRuntime) Attach(_ context.Context, containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommandserver.TerminalSize) error {
|
||||
assert.Equal(f.t, testContainerID, containerID)
|
||||
doServerStreams(f.t, "attach", stdin, stdout, stderr)
|
||||
return nil
|
||||
|
|
@ -448,16 +408,6 @@ func doServerStreams(t *testing.T, prefix string, stdin io.Reader, stdout, stder
|
|||
writeExpected(t, "server stdout", stdout, prefix+testOutput)
|
||||
}
|
||||
|
||||
// Send & receive expected input/output. Must be the inverse of doServerStreams.
|
||||
// Function will block until the expected i/o is finished.
|
||||
func doClientStreams(t *testing.T, prefix string, stdin io.Writer, stdout, stderr io.Reader) {
|
||||
if stderr != nil {
|
||||
readExpected(t, "client stderr", stderr, prefix+testErr)
|
||||
}
|
||||
writeExpected(t, "client stdin", stdin, prefix+testInput)
|
||||
readExpected(t, "client stdout", stdout, prefix+testOutput)
|
||||
}
|
||||
|
||||
// Read and verify the expected string from the stream.
|
||||
func readExpected(t *testing.T, streamName string, r io.Reader, expected string) {
|
||||
result := make([]byte, len(expected))
|
||||
|
|
@ -472,3 +422,52 @@ func writeExpected(t *testing.T, streamName string, w io.Writer, data string) {
|
|||
assert.NoError(t, err, "stream %s", streamName)
|
||||
assert.Equal(t, len(data), n, "stream %s", streamName)
|
||||
}
|
||||
|
||||
func dialWebSocket(t *testing.T, reqURL *url.URL) *websocket.Conn {
|
||||
t.Helper()
|
||||
|
||||
wsURL := *reqURL
|
||||
switch wsURL.Scheme {
|
||||
case "http":
|
||||
wsURL.Scheme = "ws"
|
||||
case "https":
|
||||
wsURL.Scheme = "wss"
|
||||
default:
|
||||
t.Fatalf("unsupported URL scheme %q", wsURL.Scheme)
|
||||
}
|
||||
|
||||
dialer := websocket.Dialer{
|
||||
Subprotocols: []string{"v4.channel.k8s.io"},
|
||||
}
|
||||
conn, _, err := dialer.Dial(wsURL.String(), nil)
|
||||
require.NoError(t, err)
|
||||
return conn
|
||||
}
|
||||
|
||||
func writeWebSocketPayload(t *testing.T, conn *websocket.Conn, channel byte, payload []byte) {
|
||||
t.Helper()
|
||||
frame := make([]byte, len(payload)+1)
|
||||
frame[0] = channel
|
||||
copy(frame[1:], payload)
|
||||
require.NoError(t, conn.WriteMessage(websocket.BinaryMessage, frame))
|
||||
}
|
||||
|
||||
func requireWebSocketPayload(t *testing.T, conn *websocket.Conn, channel byte, expected []byte, skipEmpty bool) {
|
||||
t.Helper()
|
||||
for {
|
||||
_, frame, err := conn.ReadMessage()
|
||||
require.NoError(t, err)
|
||||
if len(frame) == 0 {
|
||||
continue
|
||||
}
|
||||
if frame[0] != channel {
|
||||
continue
|
||||
}
|
||||
payload := frame[1:]
|
||||
if skipEmpty && len(payload) == 0 {
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, expected, payload)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -39,4 +39,5 @@ require (
|
|||
replace (
|
||||
k8s.io/api => ../api
|
||||
k8s.io/apimachinery => ../apimachinery
|
||||
k8s.io/streaming => ../streaming
|
||||
)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue