From 1ee1ff97fb7f9755a44d29bee0c80d2ccbed68dc Mon Sep 17 00:00:00 2001 From: Davanum Srinivas Date: Sat, 28 Feb 2026 19:40:07 -0500 Subject: [PATCH 1/5] staging: extract CRI streaming modules with client-go compatibility Extract streaming code into dedicated staging modules while keeping stable compatibility APIs for external client-go consumers. This commit: - adds `k8s.io/cri-streaming` for CRI exec/attach/portforward server code - adds `k8s.io/streaming` as the canonical home for shared transport primitives (`httpstream`, `spdy`, `wsstream`, runtime helpers) - switches in-tree transport consumers to `k8s.io/streaming` - removes in-tree kubelet CRI streaming package - preserves NO_PROXY/no_proxy CIDR handling in extracted SPDY proxier logic - adds deprecated `k8s.io/apimachinery/pkg/util/httpstream` compatibility wrappers (`httpstream`, `spdy`, `wsstream`) backed by `k8s.io/streaming` - restores exported client-go SPDY/portforward API signatures to apimachinery `httpstream` types for downstream compatibility - adds streaming-native client-go adapters/constructors so in-tree callers can use `k8s.io/streaming` without changing external compatibility APIs - deduplicates SPDY-over-websocket dial negotiation shared by compat and streaming tunneling dialers - logs dropped unknown stream types in `RemoveStreams` adapter fallbacks to improve compatibility-path debuggability - adds integration coverage for the streaming-upgrader-to-client-go-compat adapter path against a real cri-streaming exec endpoint - clarifies kubectl streaming import aliasing to avoid `httpstream` package ambiguity - updates tests, import restrictions, publishing metadata, and vendor/module metadata for the new staging modules Signed-off-by: Davanum Srinivas --- go.mod | 4 + go.work | 2 + .../tests/cri_streaming_spdy_compat_test.go | 286 ++++++++++++++++++ pkg/client/tests/portfoward_test.go | 113 ++++++- pkg/client/tests/remotecommand_test.go | 15 +- pkg/kubelet/kubelet_pods.go | 4 +- pkg/kubelet/kubelet_pods_test.go | 5 +- pkg/kubelet/server/server.go | 6 +- pkg/kubelet/server/server_test.go | 30 +- pkg/kubelet/server/server_websocket_test.go | 2 +- pkg/registry/core/pod/rest/subresources.go | 2 +- staging/README.md | 1 + staging/publishing/import-restrictions.yaml | 19 ++ staging/publishing/rules.yaml | 84 ++++- staging/src/k8s.io/api/go.mod | 6 +- staging/src/k8s.io/api/go.sum | 1 - .../src/k8s.io/apiextensions-apiserver/go.mod | 2 + staging/src/k8s.io/apimachinery/go.mod | 7 +- .../apimachinery/pkg/util/httpstream/doc.go | 5 +- .../pkg/util/httpstream/spdy/doc.go | 20 ++ .../pkg/util/httpstream/spdy/spdy.go | 236 +++++++++++++++ .../pkg/util/httpstream/wsstream/doc.go | 53 +--- .../pkg/util/httpstream/wsstream/wsstream.go | 91 ++++++ .../pkg/util/proxy/upgradeaware.go | 2 +- .../pkg/util/proxy/upgradeaware_test.go | 2 +- staging/src/k8s.io/apiserver/go.mod | 2 + .../request/websocket/protocol.go | 2 +- .../handlers/responsewriters/writers.go | 2 +- .../apiserver/pkg/endpoints/handlers/watch.go | 2 +- .../pkg/util/proxy/streamtranslator.go | 10 +- .../pkg/util/proxy/streamtranslator_test.go | 4 +- .../apiserver/pkg/util/proxy/streamtunnel.go | 6 +- .../pkg/util/proxy/streamtunnel_test.go | 4 +- .../pkg/util/proxy/translatinghandler_test.go | 2 +- .../apiserver/pkg/util/proxy/websocket.go | 2 +- .../apiserver/pkg/util/wsstream/legacy.go | 59 ---- staging/src/k8s.io/cli-runtime/go.mod | 1 + staging/src/k8s.io/cli-runtime/go.sum | 1 - staging/src/k8s.io/client-go/go.mod | 3 +- staging/src/k8s.io/client-go/go.sum | 1 - .../tools/portforward/fallback_dialer.go | 31 ++ .../tools/portforward/portforward.go | 98 ++++++ .../tools/portforward/tunneling_dialer.go | 65 +++- .../tools/remotecommand/fallback_test.go | 74 ++++- .../tools/remotecommand/remotecommand.go | 2 +- .../client-go/tools/remotecommand/spdy.go | 4 +- .../tools/remotecommand/spdy_test.go | 70 ++++- .../client-go/tools/remotecommand/v1.go | 2 +- .../client-go/tools/remotecommand/v2_test.go | 2 +- .../tools/remotecommand/websocket.go | 2 +- .../tools/remotecommand/websocket_test.go | 2 +- .../k8s.io/client-go/transport/spdy/spdy.go | 214 ++++++++++++- .../transport/websocket/roundtripper.go | 4 +- .../transport/websocket/roundtripper_test.go | 4 +- staging/src/k8s.io/cloud-provider/go.mod | 2 + staging/src/k8s.io/cluster-bootstrap/go.mod | 1 + staging/src/k8s.io/cluster-bootstrap/go.sum | 1 - staging/src/k8s.io/code-generator/go.mod | 6 +- staging/src/k8s.io/code-generator/go.sum | 1 - staging/src/k8s.io/component-base/go.mod | 1 + staging/src/k8s.io/component-base/go.sum | 1 - staging/src/k8s.io/component-helpers/go.mod | 1 + staging/src/k8s.io/component-helpers/go.sum | 1 - staging/src/k8s.io/controller-manager/go.mod | 2 + staging/src/k8s.io/cri-client/go.mod | 1 + staging/src/k8s.io/cri-client/go.sum | 1 - .../.github/PULL_REQUEST_TEMPLATE.md | 2 + .../src/k8s.io/cri-streaming/CONTRIBUTING.md | 7 + staging/src/k8s.io/cri-streaming/LICENSE | 202 +++++++++++++ staging/src/k8s.io/cri-streaming/OWNERS | 13 + staging/src/k8s.io/cri-streaming/README.md | 35 +++ .../k8s.io/cri-streaming/SECURITY_CONTACTS | 17 ++ .../k8s.io/cri-streaming/code-of-conduct.md | 3 + staging/src/k8s.io/cri-streaming/doc.go | 18 ++ staging/src/k8s.io/cri-streaming/go.mod | 41 +++ staging/src/k8s.io/cri-streaming/go.sum | 91 ++++++ .../pkg/streaming/.import-restrictions | 5 + .../k8s.io/cri-streaming/pkg/streaming/doc.go | 18 ++ .../pkg}/streaming/errors.go | 0 .../streaming/internal/httpresponse/writer.go | 38 +++ .../pkg}/streaming/portforward/constants.go | 14 + .../pkg}/streaming/portforward/httpstream.go | 40 ++- .../streaming/portforward/httpstream_test.go | 13 +- .../pkg}/streaming/portforward/portforward.go | 9 +- .../pkg}/streaming/portforward/websocket.go | 20 +- .../streaming/portforward/websocket_test.go | 0 .../pkg}/streaming/remotecommand/attach.go | 16 +- .../pkg/streaming/remotecommand/constants.go | 75 +++++ .../pkg}/streaming/remotecommand/doc.go | 0 .../pkg}/streaming/remotecommand/exec.go | 29 +- .../streaming/remotecommand/httpstream.go | 101 +++---- .../remotecommand/httpstream_test.go | 6 +- .../pkg/streaming/remotecommand/resize.go | 23 ++ .../pkg/streaming/remotecommand/status.go | 71 +++++ .../pkg}/streaming/remotecommand/websocket.go | 8 +- .../pkg}/streaming/request_cache.go | 0 .../pkg}/streaming/request_cache_test.go | 0 .../pkg}/streaming/server.go | 21 +- .../pkg}/streaming/server_test.go | 137 +++++---- staging/src/k8s.io/csi-translation-lib/go.mod | 1 + staging/src/k8s.io/csi-translation-lib/go.sum | 1 - .../k8s.io/dynamic-resource-allocation/go.mod | 3 +- .../k8s.io/dynamic-resource-allocation/go.sum | 1 - staging/src/k8s.io/endpointslice/go.mod | 1 + staging/src/k8s.io/endpointslice/go.sum | 1 - staging/src/k8s.io/kube-aggregator/go.mod | 2 + .../pkg/apiserver/handler_proxy.go | 2 +- .../src/k8s.io/kube-controller-manager/go.mod | 2 + .../src/k8s.io/kube-controller-manager/go.sum | 1 - staging/src/k8s.io/kube-proxy/go.mod | 2 + staging/src/k8s.io/kube-proxy/go.sum | 1 - staging/src/k8s.io/kube-scheduler/go.mod | 1 + staging/src/k8s.io/kube-scheduler/go.sum | 2 - staging/src/k8s.io/kubectl/go.mod | 2 + .../k8s.io/kubectl/pkg/cmd/attach/attach.go | 2 +- .../src/k8s.io/kubectl/pkg/cmd/exec/exec.go | 2 +- .../pkg/cmd/portforward/portforward.go | 14 +- .../pkg/cmd/portforward/portforward_test.go | 6 +- staging/src/k8s.io/kubelet/go.mod | 19 +- staging/src/k8s.io/kubelet/go.sum | 54 +--- .../pkg/cri/streaming/.import-restrictions | 5 - staging/src/k8s.io/metrics/go.mod | 1 + staging/src/k8s.io/metrics/go.sum | 1 - .../src/k8s.io/pod-security-admission/go.mod | 2 + staging/src/k8s.io/sample-apiserver/go.mod | 2 + staging/src/k8s.io/sample-cli-plugin/go.mod | 1 + staging/src/k8s.io/sample-cli-plugin/go.sum | 1 - staging/src/k8s.io/sample-controller/go.mod | 1 + staging/src/k8s.io/sample-controller/go.sum | 1 - .../.github/PULL_REQUEST_TEMPLATE.md | 2 + staging/src/k8s.io/streaming/CONTRIBUTING.md | 7 + staging/src/k8s.io/streaming/LICENSE | 202 +++++++++++++ staging/src/k8s.io/streaming/OWNERS | 13 + staging/src/k8s.io/streaming/README.md | 38 +++ .../src/k8s.io/streaming/SECURITY_CONTACTS | 17 ++ .../src/k8s.io/streaming/code-of-conduct.md | 3 + staging/src/k8s.io/streaming/doc.go | 18 ++ staging/src/k8s.io/streaming/go.mod | 26 ++ staging/src/k8s.io/streaming/go.sum | 42 +++ .../k8s.io/streaming/pkg/httpstream/doc.go | 19 ++ .../streaming/pkg/httpstream/httpstream.go | 201 ++++++++++++ .../pkg}/httpstream/httpstream_test.go | 0 .../pkg}/httpstream/spdy/connection.go | 2 +- .../pkg}/httpstream/spdy/connection_test.go | 2 +- .../pkg}/httpstream/spdy/roundtripper.go | 261 +++++++++++++--- .../pkg}/httpstream/spdy/roundtripper_test.go | 138 ++++++++- .../pkg}/httpstream/spdy/upgrade.go | 4 +- .../pkg}/httpstream/spdy/upgrade_test.go | 0 .../pkg}/httpstream/wsstream/conn.go | 14 +- .../pkg}/httpstream/wsstream/conn_test.go | 0 .../streaming/pkg/httpstream/wsstream/doc.go | 69 +++++ .../pkg}/httpstream/wsstream/stream.go | 2 +- .../pkg}/httpstream/wsstream/stream_test.go | 0 .../k8s.io/streaming/pkg/runtime/runtime.go | 62 ++++ test/e2e/framework/pod/dial.go | 8 +- test/e2e/framework/pod/exec_util.go | 2 +- test/e2e/storage/drivers/proxy/portproxy.go | 4 +- .../apiserver/portforward/portforward_test.go | 6 +- vendor/modules.txt | 4 + 159 files changed, 3475 insertions(+), 591 deletions(-) create mode 100644 pkg/client/tests/cri_streaming_spdy_compat_test.go create mode 100644 staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/doc.go create mode 100644 staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/spdy.go create mode 100644 staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/wsstream.go delete mode 100644 staging/src/k8s.io/apiserver/pkg/util/wsstream/legacy.go create mode 100644 staging/src/k8s.io/cri-streaming/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 staging/src/k8s.io/cri-streaming/CONTRIBUTING.md create mode 100644 staging/src/k8s.io/cri-streaming/LICENSE create mode 100644 staging/src/k8s.io/cri-streaming/OWNERS create mode 100644 staging/src/k8s.io/cri-streaming/README.md create mode 100644 staging/src/k8s.io/cri-streaming/SECURITY_CONTACTS create mode 100644 staging/src/k8s.io/cri-streaming/code-of-conduct.md create mode 100644 staging/src/k8s.io/cri-streaming/doc.go create mode 100644 staging/src/k8s.io/cri-streaming/go.mod create mode 100644 staging/src/k8s.io/cri-streaming/go.sum create mode 100644 staging/src/k8s.io/cri-streaming/pkg/streaming/.import-restrictions create mode 100644 staging/src/k8s.io/cri-streaming/pkg/streaming/doc.go rename staging/src/k8s.io/{kubelet/pkg/cri => cri-streaming/pkg}/streaming/errors.go (100%) create mode 100644 staging/src/k8s.io/cri-streaming/pkg/streaming/internal/httpresponse/writer.go rename staging/src/k8s.io/{kubelet/pkg/cri => cri-streaming/pkg}/streaming/portforward/constants.go (68%) rename staging/src/k8s.io/{kubelet/pkg/cri => cri-streaming/pkg}/streaming/portforward/httpstream.go (90%) rename staging/src/k8s.io/{kubelet/pkg/cri => cri-streaming/pkg}/streaming/portforward/httpstream_test.go (94%) rename staging/src/k8s.io/{kubelet/pkg/cri => cri-streaming/pkg}/streaming/portforward/portforward.go (79%) rename staging/src/k8s.io/{kubelet/pkg/cri => cri-streaming/pkg}/streaming/portforward/websocket.go (89%) rename staging/src/k8s.io/{kubelet/pkg/cri => cri-streaming/pkg}/streaming/portforward/websocket_test.go (100%) rename staging/src/k8s.io/{kubelet/pkg/cri => cri-streaming/pkg}/streaming/remotecommand/attach.go (69%) create mode 100644 staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/constants.go rename staging/src/k8s.io/{kubelet/pkg/cri => cri-streaming/pkg}/streaming/remotecommand/doc.go (100%) rename staging/src/k8s.io/{kubelet/pkg/cri => cri-streaming/pkg}/streaming/remotecommand/exec.go (63%) rename staging/src/k8s.io/{kubelet/pkg/cri => cri-streaming/pkg}/streaming/remotecommand/httpstream.go (83%) rename staging/src/k8s.io/{kubelet/pkg/cri => cri-streaming/pkg}/streaming/remotecommand/httpstream_test.go (93%) create mode 100644 staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/resize.go create mode 100644 staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/status.go rename staging/src/k8s.io/{kubelet/pkg/cri => cri-streaming/pkg}/streaming/remotecommand/websocket.go (94%) rename staging/src/k8s.io/{kubelet/pkg/cri => cri-streaming/pkg}/streaming/request_cache.go (100%) rename staging/src/k8s.io/{kubelet/pkg/cri => cri-streaming/pkg}/streaming/request_cache_test.go (100%) rename staging/src/k8s.io/{kubelet/pkg/cri => cri-streaming/pkg}/streaming/server.go (92%) rename staging/src/k8s.io/{kubelet/pkg/cri => cri-streaming/pkg}/streaming/server_test.go (83%) delete mode 100644 staging/src/k8s.io/kubelet/pkg/cri/streaming/.import-restrictions create mode 100644 staging/src/k8s.io/streaming/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 staging/src/k8s.io/streaming/CONTRIBUTING.md create mode 100644 staging/src/k8s.io/streaming/LICENSE create mode 100644 staging/src/k8s.io/streaming/OWNERS create mode 100644 staging/src/k8s.io/streaming/README.md create mode 100644 staging/src/k8s.io/streaming/SECURITY_CONTACTS create mode 100644 staging/src/k8s.io/streaming/code-of-conduct.md create mode 100644 staging/src/k8s.io/streaming/doc.go create mode 100644 staging/src/k8s.io/streaming/go.mod create mode 100644 staging/src/k8s.io/streaming/go.sum create mode 100644 staging/src/k8s.io/streaming/pkg/httpstream/doc.go create mode 100644 staging/src/k8s.io/streaming/pkg/httpstream/httpstream.go rename staging/src/k8s.io/{apimachinery/pkg/util => streaming/pkg}/httpstream/httpstream_test.go (100%) rename staging/src/k8s.io/{apimachinery/pkg/util => streaming/pkg}/httpstream/spdy/connection.go (99%) rename staging/src/k8s.io/{apimachinery/pkg/util => streaming/pkg}/httpstream/spdy/connection_test.go (99%) rename staging/src/k8s.io/{apimachinery/pkg/util => streaming/pkg}/httpstream/spdy/roundtripper.go (65%) rename staging/src/k8s.io/{apimachinery/pkg/util => streaming/pkg}/httpstream/spdy/roundtripper_test.go (90%) rename staging/src/k8s.io/{apimachinery/pkg/util => streaming/pkg}/httpstream/spdy/upgrade.go (97%) rename staging/src/k8s.io/{apimachinery/pkg/util => streaming/pkg}/httpstream/spdy/upgrade_test.go (100%) rename staging/src/k8s.io/{apimachinery/pkg/util => streaming/pkg}/httpstream/wsstream/conn.go (97%) rename staging/src/k8s.io/{apimachinery/pkg/util => streaming/pkg}/httpstream/wsstream/conn_test.go (100%) create mode 100644 staging/src/k8s.io/streaming/pkg/httpstream/wsstream/doc.go rename staging/src/k8s.io/{apimachinery/pkg/util => streaming/pkg}/httpstream/wsstream/stream.go (99%) rename staging/src/k8s.io/{apimachinery/pkg/util => streaming/pkg}/httpstream/wsstream/stream_test.go (100%) create mode 100644 staging/src/k8s.io/streaming/pkg/runtime/runtime.go diff --git a/go.mod b/go.mod index 346254d2f14..00ba1958331 100644 --- a/go.mod +++ b/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 ) diff --git a/go.work b/go.work index 55e654bc993..965e38ff8a1 100644 --- a/go.work +++ b/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 ) diff --git a/pkg/client/tests/cri_streaming_spdy_compat_test.go b/pkg/client/tests/cri_streaming_spdy_compat_test.go new file mode 100644 index 00000000000..1a3da5fb7d1 --- /dev/null +++ b/pkg/client/tests/cri_streaming_spdy_compat_test.go @@ -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) + } +} diff --git a/pkg/client/tests/portfoward_test.go b/pkg/client/tests/portfoward_test.go index dd0584738b9..17d9622490b 100644 --- a/pkg/client/tests/portfoward_test.go +++ b/pkg/client/tests/portfoward_test.go @@ -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") + } +} diff --git a/pkg/client/tests/remotecommand_test.go b/pkg/client/tests/remotecommand_test.go index 78b1cf387f5..4969a560a52 100644 --- a/pkg/client/tests/remotecommand_test.go +++ b/pkg/client/tests/remotecommand_test.go @@ -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) diff --git a/pkg/kubelet/kubelet_pods.go b/pkg/kubelet/kubelet_pods.go index ef934853590..8add8fe8d57 100644 --- a/pkg/kubelet/kubelet_pods.go +++ b/pkg/kubelet/kubelet_pods.go @@ -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" diff --git a/pkg/kubelet/kubelet_pods_test.go b/pkg/kubelet/kubelet_pods_test.go index 149b070e158..a61d5136dd1 100644 --- a/pkg/kubelet/kubelet_pods_test.go +++ b/pkg/kubelet/kubelet_pods_test.go @@ -49,8 +49,9 @@ 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/klog/v2" _ "k8s.io/kubernetes/pkg/apis/core/install" "k8s.io/kubernetes/pkg/features" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" diff --git a/pkg/kubelet/server/server.go b/pkg/kubelet/server/server.go index 0e02acaf075..e8c9a06eedc 100644 --- a/pkg/kubelet/server/server.go +++ b/pkg/kubelet/server/server.go @@ -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" diff --git a/pkg/kubelet/server/server_test.go b/pkg/kubelet/server/server_test.go index bb78550cf5d..6c268c6244e 100644 --- a/pkg/kubelet/server/server_test.go +++ b/pkg/kubelet/server/server_test.go @@ -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 } diff --git a/pkg/kubelet/server/server_websocket_test.go b/pkg/kubelet/server/server_websocket_test.go index 3a32091299f..800d469463d 100644 --- a/pkg/kubelet/server/server_websocket_test.go +++ b/pkg/kubelet/server/server_websocket_test.go @@ -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" ) diff --git a/pkg/registry/core/pod/rest/subresources.go b/pkg/registry/core/pod/rest/subresources.go index 6861b240ca5..f5160574bb4 100644 --- a/pkg/registry/core/pod/rest/subresources.go +++ b/pkg/registry/core/pod/rest/subresources.go @@ -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 diff --git a/staging/README.md b/staging/README.md index c3beff5a6e7..f4046cfed3c 100644 --- a/staging/README.md +++ b/staging/README.md @@ -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) diff --git a/staging/publishing/import-restrictions.yaml b/staging/publishing/import-restrictions.yaml index 0b2dbac8de2..a61a5da7e34 100644 --- a/staging/publishing/import-restrictions.yaml +++ b/staging/publishing/import-restrictions.yaml @@ -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 diff --git a/staging/publishing/rules.yaml b/staging/publishing/rules.yaml index b7eb3755149..d78acca1a36 100644 --- a/staging/publishing/rules.yaml +++ b/staging/publishing/rules.yaml @@ -2,6 +2,9 @@ rules: - destination: apimachinery branches: - name: master + dependencies: + - repository: streaming + branch: master source: branch: master dirs: @@ -33,6 +36,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master source: branch: master dirs: @@ -76,6 +81,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master source: @@ -149,6 +156,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master source: branch: master dirs: @@ -191,6 +200,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master - repository: client-go @@ -254,6 +265,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master - repository: client-go @@ -317,6 +330,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master source: branch: master dirs: @@ -360,6 +375,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master - repository: client-go @@ -443,6 +460,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master - repository: client-go @@ -545,6 +564,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master - repository: client-go @@ -672,6 +693,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master - repository: client-go @@ -769,6 +792,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master - repository: client-go @@ -881,6 +906,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: api branch: master - repository: client-go @@ -956,6 +983,8 @@ rules: branch: master - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: client-go branch: master source: @@ -1019,6 +1048,8 @@ rules: branch: master - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: cli-runtime branch: master - repository: client-go @@ -1089,6 +1120,8 @@ rules: dependencies: - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: component-base branch: master - repository: api @@ -1192,6 +1225,8 @@ rules: branch: master - repository: apimachinery branch: master + - repository: streaming + branch: master - repository: client-go branch: master - repository: component-base @@ -1267,24 +1302,41 @@ 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: streaming + branches: + - name: master + source: + branch: master + dirs: + - staging/src/k8s.io/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 diff --git a/staging/src/k8s.io/api/go.mod b/staging/src/k8s.io/api/go.mod index b718dd5ffd7..a75700f836b 100644 --- a/staging/src/k8s.io/api/go.mod +++ b/staging/src/k8s.io/api/go.mod @@ -20,6 +20,7 @@ require ( github.com/kr/pretty v0.3.1 // 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/rogpeppe/go-internal v1.14.1 // indirect github.com/spf13/pflag v1.0.9 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect @@ -35,4 +36,7 @@ require ( sigs.k8s.io/yaml v1.6.0 // indirect ) -replace k8s.io/apimachinery => ../apimachinery +replace ( + k8s.io/apimachinery => ../apimachinery + k8s.io/streaming => ../streaming +) diff --git a/staging/src/k8s.io/api/go.sum b/staging/src/k8s.io/api/go.sum index 5eaf178f228..9f273064208 100644 --- a/staging/src/k8s.io/api/go.sum +++ b/staging/src/k8s.io/api/go.sum @@ -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= diff --git a/staging/src/k8s.io/apiextensions-apiserver/go.mod b/staging/src/k8s.io/apiextensions-apiserver/go.mod index 10a95928fe4..5e21416be86 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/go.mod +++ b/staging/src/k8s.io/apiextensions-apiserver/go.mod @@ -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 ) diff --git a/staging/src/k8s.io/apimachinery/go.mod b/staging/src/k8s.io/apimachinery/go.mod index efbc97d145c..17ec51f3a1a 100644 --- a/staging/src/k8s.io/apimachinery/go.mod +++ b/staging/src/k8s.io/apimachinery/go.mod @@ -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,11 +38,11 @@ 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 github.com/onsi/gomega v1.39.1 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect @@ -51,3 +50,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 diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/doc.go b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/doc.go index 1da83f14b18..5fdc7955fa5 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/doc.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/doc.go @@ -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 diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/doc.go b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/doc.go new file mode 100644 index 00000000000..d03acb0eed1 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/doc.go @@ -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 diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/spdy.go b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/spdy.go new file mode 100644 index 00000000000..37dfe81894c --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/spdy.go @@ -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) + } +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/doc.go b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/doc.go index a57e8df60a3..9b07bfbb137 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/doc.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/doc.go @@ -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 diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/wsstream.go b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/wsstream.go new file mode 100644 index 00000000000..7384864f912 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/wsstream.go @@ -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) +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware.go b/staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware.go index 812168462d3..56f62148347 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware.go @@ -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" diff --git a/staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware_test.go b/staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware_test.go index 33ad02e2012..4a0f821febd 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware_test.go +++ b/staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware_test.go @@ -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 diff --git a/staging/src/k8s.io/apiserver/go.mod b/staging/src/k8s.io/apiserver/go.mod index 925c973d871..1d8354027ab 100644 --- a/staging/src/k8s.io/apiserver/go.mod +++ b/staging/src/k8s.io/apiserver/go.mod @@ -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 ) diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/request/websocket/protocol.go b/staging/src/k8s.io/apiserver/pkg/authentication/request/websocket/protocol.go index ee8c89f5ced..a3a78331563 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/request/websocket/protocol.go +++ b/staging/src/k8s.io/apiserver/pkg/authentication/request/websocket/protocol.go @@ -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." diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go index 55f8b657a3c..cb782f76a3e 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go @@ -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. diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/watch.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/watch.go index 4ac97d9f2f9..9121ec3fadf 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/watch.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/watch.go @@ -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 diff --git a/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtranslator.go b/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtranslator.go index 33bbdc32526..a85a5c5d0ef 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtranslator.go +++ b/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtranslator.go @@ -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 diff --git a/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtranslator_test.go b/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtranslator_test.go index 55ecbda0498..cc6ffd0e2dd 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtranslator_test.go +++ b/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtranslator_test.go @@ -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 diff --git a/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtunnel.go b/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtunnel.go index 7a7b92adaf7..429ab78ebfa 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtunnel.go +++ b/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtunnel.go @@ -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. diff --git a/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtunnel_test.go b/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtunnel_test.go index 7cf57f2d3c6..c979ffbced3 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtunnel_test.go +++ b/staging/src/k8s.io/apiserver/pkg/util/proxy/streamtunnel_test.go @@ -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) { diff --git a/staging/src/k8s.io/apiserver/pkg/util/proxy/translatinghandler_test.go b/staging/src/k8s.io/apiserver/pkg/util/proxy/translatinghandler_test.go index ee5a53ed88a..c4aef1f70c7 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/proxy/translatinghandler_test.go +++ b/staging/src/k8s.io/apiserver/pkg/util/proxy/translatinghandler_test.go @@ -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 diff --git a/staging/src/k8s.io/apiserver/pkg/util/proxy/websocket.go b/staging/src/k8s.io/apiserver/pkg/util/proxy/websocket.go index 798ce176724..837c468108b 100644 --- a/staging/src/k8s.io/apiserver/pkg/util/proxy/websocket.go +++ b/staging/src/k8s.io/apiserver/pkg/util/proxy/websocket.go @@ -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 ( diff --git a/staging/src/k8s.io/apiserver/pkg/util/wsstream/legacy.go b/staging/src/k8s.io/apiserver/pkg/util/wsstream/legacy.go deleted file mode 100644 index 61b4dd48927..00000000000 --- a/staging/src/k8s.io/apiserver/pkg/util/wsstream/legacy.go +++ /dev/null @@ -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 diff --git a/staging/src/k8s.io/cli-runtime/go.mod b/staging/src/k8s.io/cli-runtime/go.mod index c8387570662..88b460763e8 100644 --- a/staging/src/k8s.io/cli-runtime/go.mod +++ b/staging/src/k8s.io/cli-runtime/go.mod @@ -73,4 +73,5 @@ replace ( k8s.io/api => ../api k8s.io/apimachinery => ../apimachinery k8s.io/client-go => ../client-go + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/cli-runtime/go.sum b/staging/src/k8s.io/cli-runtime/go.sum index 4cd2bb6efed..0e0275b2aa6 100644 --- a/staging/src/k8s.io/cli-runtime/go.sum +++ b/staging/src/k8s.io/cli-runtime/go.sum @@ -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= diff --git a/staging/src/k8s.io/client-go/go.mod b/staging/src/k8s.io/client-go/go.mod index a2145ec4f29..42a3c4a9e73 100644 --- a/staging/src/k8s.io/client-go/go.mod +++ b/staging/src/k8s.io/client-go/go.mod @@ -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 ) diff --git a/staging/src/k8s.io/client-go/go.sum b/staging/src/k8s.io/client-go/go.sum index 41ef85b760f..5bf59749b4d 100644 --- a/staging/src/k8s.io/client-go/go.sum +++ b/staging/src/k8s.io/client-go/go.sum @@ -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= diff --git a/staging/src/k8s.io/client-go/tools/portforward/fallback_dialer.go b/staging/src/k8s.io/client-go/tools/portforward/fallback_dialer.go index 7fcc2492bfe..91f8fad4dde 100644 --- a/staging/src/k8s.io/client-go/tools/portforward/fallback_dialer.go +++ b/staging/src/k8s.io/client-go/tools/portforward/fallback_dialer.go @@ -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 +} diff --git a/staging/src/k8s.io/client-go/tools/portforward/portforward.go b/staging/src/k8s.io/client-go/tools/portforward/portforward.go index 126c14e8fa0..11e0df63072 100644 --- a/staging/src/k8s.io/client-go/tools/portforward/portforward.go +++ b/staging/src/k8s.io/client-go/tools/portforward/portforward.go @@ -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 { diff --git a/staging/src/k8s.io/client-go/tools/portforward/tunneling_dialer.go b/staging/src/k8s.io/client-go/tools/portforward/tunneling_dialer.go index 2bef5ecd720..cc9352bfea1 100644 --- a/staging/src/k8s.io/client-go/tools/portforward/tunneling_dialer.go +++ b/staging/src/k8s.io/client-go/tools/portforward/tunneling_dialer.go @@ -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 } diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/fallback_test.go b/staging/src/k8s.io/client-go/tools/remotecommand/fallback_test.go index 0cd775144d6..85600d8963b 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/fallback_test.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/fallback_test.go @@ -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 diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/remotecommand.go b/staging/src/k8s.io/client-go/tools/remotecommand/remotecommand.go index 8663b88a885..ca892f9b748 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/remotecommand.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/remotecommand.go @@ -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: diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/spdy.go b/staging/src/k8s.io/client-go/tools/remotecommand/spdy.go index 2f36e925d49..ebf3c53c3a0 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/spdy.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/spdy.go @@ -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, diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/spdy_test.go b/staging/src/k8s.io/client-go/tools/remotecommand/spdy_test.go index 35cd09a9fd5..2a6e83b4c99 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/spdy_test.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/spdy_test.go @@ -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) { diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/v1.go b/staging/src/k8s.io/client-go/tools/remotecommand/v1.go index 25337e68ad8..5d903b67a2f 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/v1.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/v1.go @@ -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 diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/v2_test.go b/staging/src/k8s.io/client-go/tools/remotecommand/v2_test.go index e22dd685e44..98872c7cc6b 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/v2_test.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/v2_test.go @@ -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 { diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/websocket.go b/staging/src/k8s.io/client-go/tools/remotecommand/websocket.go index ce03c1834b5..f531e7ccffa 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/websocket.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/websocket.go @@ -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 diff --git a/staging/src/k8s.io/client-go/tools/remotecommand/websocket_test.go b/staging/src/k8s.io/client-go/tools/remotecommand/websocket_test.go index ef23d8cb502..ebfcaaade17 100644 --- a/staging/src/k8s.io/client-go/tools/remotecommand/websocket_test.go +++ b/staging/src/k8s.io/client-go/tools/remotecommand/websocket_test.go @@ -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 diff --git a/staging/src/k8s.io/client-go/transport/spdy/spdy.go b/staging/src/k8s.io/client-go/transport/spdy/spdy.go index 9fddc6c5f23..3bb04b77f37 100644 --- a/staging/src/k8s.io/client-go/transport/spdy/spdy.go +++ b/staging/src/k8s.io/client-go/transport/spdy/spdy.go @@ -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} +} diff --git a/staging/src/k8s.io/client-go/transport/websocket/roundtripper.go b/staging/src/k8s.io/client-go/transport/websocket/roundtripper.go index 924518e8bbd..5285d6b144a 100644 --- a/staging/src/k8s.io/client-go/transport/websocket/roundtripper.go +++ b/staging/src/k8s.io/client-go/transport/websocket/roundtripper.go @@ -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 ( diff --git a/staging/src/k8s.io/client-go/transport/websocket/roundtripper_test.go b/staging/src/k8s.io/client-go/transport/websocket/roundtripper_test.go index 903dd0c0c8e..76cc818b169 100644 --- a/staging/src/k8s.io/client-go/transport/websocket/roundtripper_test.go +++ b/staging/src/k8s.io/client-go/transport/websocket/roundtripper_test.go @@ -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) { diff --git a/staging/src/k8s.io/cloud-provider/go.mod b/staging/src/k8s.io/cloud-provider/go.mod index 8abf4f32232..1f3e496047d 100644 --- a/staging/src/k8s.io/cloud-provider/go.mod +++ b/staging/src/k8s.io/cloud-provider/go.mod @@ -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 ) diff --git a/staging/src/k8s.io/cluster-bootstrap/go.mod b/staging/src/k8s.io/cluster-bootstrap/go.mod index a68fead4a8f..19bf4b5d33f 100644 --- a/staging/src/k8s.io/cluster-bootstrap/go.mod +++ b/staging/src/k8s.io/cluster-bootstrap/go.mod @@ -40,4 +40,5 @@ require ( replace ( k8s.io/api => ../api k8s.io/apimachinery => ../apimachinery + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/cluster-bootstrap/go.sum b/staging/src/k8s.io/cluster-bootstrap/go.sum index 430dc5e13c4..42ba6d49c31 100644 --- a/staging/src/k8s.io/cluster-bootstrap/go.sum +++ b/staging/src/k8s.io/cluster-bootstrap/go.sum @@ -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= diff --git a/staging/src/k8s.io/code-generator/go.mod b/staging/src/k8s.io/code-generator/go.mod index 2a4da6a63ce..f47752f78e9 100644 --- a/staging/src/k8s.io/code-generator/go.mod +++ b/staging/src/k8s.io/code-generator/go.mod @@ -36,6 +36,7 @@ require ( github.com/mailru/easyjson v0.7.7 // 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/rogpeppe/go-internal v1.14.1 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.32.0 // indirect @@ -50,4 +51,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 +) diff --git a/staging/src/k8s.io/code-generator/go.sum b/staging/src/k8s.io/code-generator/go.sum index f5be062c43b..b305e916b4e 100644 --- a/staging/src/k8s.io/code-generator/go.sum +++ b/staging/src/k8s.io/code-generator/go.sum @@ -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= diff --git a/staging/src/k8s.io/component-base/go.mod b/staging/src/k8s.io/component-base/go.mod index 971ecf375ac..bfb677cb55f 100644 --- a/staging/src/k8s.io/component-base/go.mod +++ b/staging/src/k8s.io/component-base/go.mod @@ -90,4 +90,5 @@ replace ( k8s.io/api => ../api k8s.io/apimachinery => ../apimachinery k8s.io/client-go => ../client-go + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/component-base/go.sum b/staging/src/k8s.io/component-base/go.sum index 08ceb42eb8b..8c1eccf876e 100644 --- a/staging/src/k8s.io/component-base/go.sum +++ b/staging/src/k8s.io/component-base/go.sum @@ -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= diff --git a/staging/src/k8s.io/component-helpers/go.mod b/staging/src/k8s.io/component-helpers/go.mod index fdd2121e98e..65c8c043afa 100644 --- a/staging/src/k8s.io/component-helpers/go.mod +++ b/staging/src/k8s.io/component-helpers/go.mod @@ -56,4 +56,5 @@ replace ( k8s.io/api => ../api k8s.io/apimachinery => ../apimachinery k8s.io/client-go => ../client-go + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/component-helpers/go.sum b/staging/src/k8s.io/component-helpers/go.sum index 32b912a0ddd..42c33694a25 100644 --- a/staging/src/k8s.io/component-helpers/go.sum +++ b/staging/src/k8s.io/component-helpers/go.sum @@ -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= diff --git a/staging/src/k8s.io/controller-manager/go.mod b/staging/src/k8s.io/controller-manager/go.mod index aa6e1db9034..9463873d91e 100644 --- a/staging/src/k8s.io/controller-manager/go.mod +++ b/staging/src/k8s.io/controller-manager/go.mod @@ -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 ) diff --git a/staging/src/k8s.io/cri-client/go.mod b/staging/src/k8s.io/cri-client/go.mod index f1147751604..2610a5aa65e 100644 --- a/staging/src/k8s.io/cri-client/go.mod +++ b/staging/src/k8s.io/cri-client/go.mod @@ -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 ) diff --git a/staging/src/k8s.io/cri-client/go.sum b/staging/src/k8s.io/cri-client/go.sum index 4c56a0df0ec..e283a59ed7e 100644 --- a/staging/src/k8s.io/cri-client/go.sum +++ b/staging/src/k8s.io/cri-client/go.sum @@ -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= diff --git a/staging/src/k8s.io/cri-streaming/.github/PULL_REQUEST_TEMPLATE.md b/staging/src/k8s.io/cri-streaming/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..e7e5eb834b2 --- /dev/null +++ b/staging/src/k8s.io/cri-streaming/.github/PULL_REQUEST_TEMPLATE.md @@ -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. diff --git a/staging/src/k8s.io/cri-streaming/CONTRIBUTING.md b/staging/src/k8s.io/cri-streaming/CONTRIBUTING.md new file mode 100644 index 00000000000..5c09cae420d --- /dev/null +++ b/staging/src/k8s.io/cri-streaming/CONTRIBUTING.md @@ -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 diff --git a/staging/src/k8s.io/cri-streaming/LICENSE b/staging/src/k8s.io/cri-streaming/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/staging/src/k8s.io/cri-streaming/LICENSE @@ -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. diff --git a/staging/src/k8s.io/cri-streaming/OWNERS b/staging/src/k8s.io/cri-streaming/OWNERS new file mode 100644 index 00000000000..dbfc437c636 --- /dev/null +++ b/staging/src/k8s.io/cri-streaming/OWNERS @@ -0,0 +1,13 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - sig-node-approvers + - sttts + - luxas + - mtaufen +reviewers: + - sig-node-reviewers + - luxas + - sttts +labels: + - sig/node diff --git a/staging/src/k8s.io/cri-streaming/README.md b/staging/src/k8s.io/cri-streaming/README.md new file mode 100644 index 00000000000..52970da1731 --- /dev/null +++ b/staging/src/k8s.io/cri-streaming/README.md @@ -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). diff --git a/staging/src/k8s.io/cri-streaming/SECURITY_CONTACTS b/staging/src/k8s.io/cri-streaming/SECURITY_CONTACTS new file mode 100644 index 00000000000..6df6a4d6a16 --- /dev/null +++ b/staging/src/k8s.io/cri-streaming/SECURITY_CONTACTS @@ -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 diff --git a/staging/src/k8s.io/cri-streaming/code-of-conduct.md b/staging/src/k8s.io/cri-streaming/code-of-conduct.md new file mode 100644 index 00000000000..0d15c00cf32 --- /dev/null +++ b/staging/src/k8s.io/cri-streaming/code-of-conduct.md @@ -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) diff --git a/staging/src/k8s.io/cri-streaming/doc.go b/staging/src/k8s.io/cri-streaming/doc.go new file mode 100644 index 00000000000..1b3699f09bf --- /dev/null +++ b/staging/src/k8s.io/cri-streaming/doc.go @@ -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 diff --git a/staging/src/k8s.io/cri-streaming/go.mod b/staging/src/k8s.io/cri-streaming/go.mod new file mode 100644 index 00000000000..da0c162e62a --- /dev/null +++ b/staging/src/k8s.io/cri-streaming/go.mod @@ -0,0 +1,41 @@ +// This is a generated file. Do not edit directly. + +module k8s.io/cri-streaming + +go 1.25.0 + +godebug default=go1.25 + +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.11 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace ( + k8s.io/cri-api => ../cri-api + k8s.io/streaming => ../streaming +) diff --git a/staging/src/k8s.io/cri-streaming/go.sum b/staging/src/k8s.io/cri-streaming/go.sum new file mode 100644 index 00000000000..72cd5b1b02b --- /dev/null +++ b/staging/src/k8s.io/cri-streaming/go.sum @@ -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.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/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= diff --git a/staging/src/k8s.io/cri-streaming/pkg/streaming/.import-restrictions b/staging/src/k8s.io/cri-streaming/pkg/streaming/.import-restrictions new file mode 100644 index 00000000000..87723dcfc80 --- /dev/null +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/.import-restrictions @@ -0,0 +1,5 @@ +rules: + # prevent import of k8s.io/kubernetes + - selectorRegexp: k8s[.]io/kubernetes + forbiddenPrefixes: + - '' diff --git a/staging/src/k8s.io/cri-streaming/pkg/streaming/doc.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/doc.go new file mode 100644 index 00000000000..9d5d0df0c25 --- /dev/null +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/doc.go @@ -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 diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/errors.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/errors.go similarity index 100% rename from staging/src/k8s.io/kubelet/pkg/cri/streaming/errors.go rename to staging/src/k8s.io/cri-streaming/pkg/streaming/errors.go diff --git a/staging/src/k8s.io/cri-streaming/pkg/streaming/internal/httpresponse/writer.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/internal/httpresponse/writer.go new file mode 100644 index 00000000000..832b59a845d --- /dev/null +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/internal/httpresponse/writer.go @@ -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 + } +} diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/portforward/constants.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/portforward/constants.go similarity index 68% rename from staging/src/k8s.io/kubelet/pkg/cri/streaming/portforward/constants.go rename to staging/src/k8s.io/cri-streaming/pkg/streaming/portforward/constants.go index 62b14f2051a..6a060e241ce 100644 --- a/staging/src/k8s.io/kubelet/pkg/cri/streaming/portforward/constants.go +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/portforward/constants.go @@ -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" +) diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/portforward/httpstream.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/portforward/httpstream.go similarity index 90% rename from staging/src/k8s.io/kubelet/pkg/cri/streaming/portforward/httpstream.go rename to staging/src/k8s.io/cri-streaming/pkg/streaming/portforward/httpstream.go index 9e2bd8ec8d7..9ed58561a9c 100644 --- a/staging/src/k8s.io/kubelet/pkg/cri/streaming/portforward/httpstream.go +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/portforward/httpstream.go @@ -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") } diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/portforward/httpstream_test.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/portforward/httpstream_test.go similarity index 94% rename from staging/src/k8s.io/kubelet/pkg/cri/streaming/portforward/httpstream_test.go rename to staging/src/k8s.io/cri-streaming/pkg/streaming/portforward/httpstream_test.go index f594756dc01..8c8391fa7e1 100644 --- a/staging/src/k8s.io/kubelet/pkg/cri/streaming/portforward/httpstream_test.go +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/portforward/httpstream_test.go @@ -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) } diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/portforward/portforward.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/portforward/portforward.go similarity index 79% rename from staging/src/k8s.io/kubelet/pkg/cri/streaming/portforward/portforward.go rename to staging/src/k8s.io/cri-streaming/pkg/streaming/portforward/portforward.go index 7aa668ca4dc..ac4508d86f6 100644 --- a/staging/src/k8s.io/kubelet/pkg/cri/streaming/portforward/portforward.go +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/portforward/portforward.go @@ -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) diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/portforward/websocket.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/portforward/websocket.go similarity index 89% rename from staging/src/k8s.io/kubelet/pkg/cri/streaming/portforward/websocket.go rename to staging/src/k8s.io/cri-streaming/pkg/streaming/portforward/websocket.go index 3700a7e22ad..93efffc6c64 100644 --- a/staging/src/k8s.io/kubelet/pkg/cri/streaming/portforward/websocket.go +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/portforward/websocket.go @@ -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 } diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/portforward/websocket_test.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/portforward/websocket_test.go similarity index 100% rename from staging/src/k8s.io/kubelet/pkg/cri/streaming/portforward/websocket_test.go rename to staging/src/k8s.io/cri-streaming/pkg/streaming/portforward/websocket_test.go diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/attach.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/attach.go similarity index 69% rename from staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/attach.go rename to staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/attach.go index aa638499a95..7b1c6dd6618 100644 --- a/staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/attach.go +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/attach.go @@ -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, }}) } } diff --git a/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/constants.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/constants.go new file mode 100644 index 00000000000..9deb2b7d749 --- /dev/null +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/constants.go @@ -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} diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/doc.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/doc.go similarity index 100% rename from staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/doc.go rename to staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/doc.go diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/exec.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/exec.go similarity index 63% rename from staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/exec.go rename to staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/exec.go index 5ec6b86a8d0..7b600955a82 100644 --- a/staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/exec.go +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/exec.go @@ -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, }}) } } diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/httpstream.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/httpstream.go similarity index 83% rename from staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/httpstream.go rename to staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/httpstream.go index 185a3db6273..d463bb51af9 100644 --- a/staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/httpstream.go +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/httpstream.go @@ -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 } diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/httpstream_test.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/httpstream_test.go similarity index 93% rename from staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/httpstream_test.go rename to staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/httpstream_test.go index c2781f6994b..1050ddd8563 100644 --- a/staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/httpstream_test.go +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/httpstream_test.go @@ -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) diff --git a/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/resize.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/resize.go new file mode 100644 index 00000000000..25cbe71bfc1 --- /dev/null +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/resize.go @@ -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 +} diff --git a/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/status.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/status.go new file mode 100644 index 00000000000..004dc3f438d --- /dev/null +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/status.go @@ -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"` +} diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/websocket.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/websocket.go similarity index 94% rename from staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/websocket.go rename to staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/websocket.go index 25900888522..3bbeeddb3e6 100644 --- a/staging/src/k8s.io/kubelet/pkg/cri/streaming/remotecommand/websocket.go +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/remotecommand/websocket.go @@ -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 diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/request_cache.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/request_cache.go similarity index 100% rename from staging/src/k8s.io/kubelet/pkg/cri/streaming/request_cache.go rename to staging/src/k8s.io/cri-streaming/pkg/streaming/request_cache.go diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/request_cache_test.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/request_cache_test.go similarity index 100% rename from staging/src/k8s.io/kubelet/pkg/cri/streaming/request_cache_test.go rename to staging/src/k8s.io/cri-streaming/pkg/streaming/request_cache_test.go diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/server.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/server.go similarity index 92% rename from staging/src/k8s.io/kubelet/pkg/cri/streaming/server.go rename to staging/src/k8s.io/cri-streaming/pkg/streaming/server.go index fe5c22b0497..97ecb028287 100644 --- a/staging/src/k8s.io/kubelet/pkg/cri/streaming/server.go +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/server.go @@ -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) } diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/server_test.go b/staging/src/k8s.io/cri-streaming/pkg/streaming/server_test.go similarity index 83% rename from staging/src/k8s.io/kubelet/pkg/cri/streaming/server_test.go rename to staging/src/k8s.io/cri-streaming/pkg/streaming/server_test.go index 181405d030c..799b098a942 100644 --- a/staging/src/k8s.io/kubelet/pkg/cri/streaming/server_test.go +++ b/staging/src/k8s.io/cri-streaming/pkg/streaming/server_test.go @@ -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 + } +} diff --git a/staging/src/k8s.io/csi-translation-lib/go.mod b/staging/src/k8s.io/csi-translation-lib/go.mod index d2e86db0894..174df71996c 100644 --- a/staging/src/k8s.io/csi-translation-lib/go.mod +++ b/staging/src/k8s.io/csi-translation-lib/go.mod @@ -39,4 +39,5 @@ require ( replace ( k8s.io/api => ../api k8s.io/apimachinery => ../apimachinery + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/csi-translation-lib/go.sum b/staging/src/k8s.io/csi-translation-lib/go.sum index 01331d0064e..39fc5373e28 100644 --- a/staging/src/k8s.io/csi-translation-lib/go.sum +++ b/staging/src/k8s.io/csi-translation-lib/go.sum @@ -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= diff --git a/staging/src/k8s.io/dynamic-resource-allocation/go.mod b/staging/src/k8s.io/dynamic-resource-allocation/go.mod index cb3fe2359c3..0fc1f65976d 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/go.mod +++ b/staging/src/k8s.io/dynamic-resource-allocation/go.mod @@ -56,7 +56,6 @@ require ( github.com/stoewer/go-strcase v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/otel v1.40.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect go.opentelemetry.io/otel/trace v1.40.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect @@ -90,7 +89,7 @@ replace ( k8s.io/client-go => ../client-go k8s.io/component-base => ../component-base k8s.io/component-helpers => ../component-helpers - k8s.io/cri-api => ../cri-api k8s.io/kms => ../kms k8s.io/kubelet => ../kubelet + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/dynamic-resource-allocation/go.sum b/staging/src/k8s.io/dynamic-resource-allocation/go.sum index 486471950cc..3a021e6c3f3 100644 --- a/staging/src/k8s.io/dynamic-resource-allocation/go.sum +++ b/staging/src/k8s.io/dynamic-resource-allocation/go.sum @@ -10,7 +10,6 @@ github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HR github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= -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= diff --git a/staging/src/k8s.io/endpointslice/go.mod b/staging/src/k8s.io/endpointslice/go.mod index b4d764822ea..b865d0495ae 100644 --- a/staging/src/k8s.io/endpointslice/go.mod +++ b/staging/src/k8s.io/endpointslice/go.mod @@ -70,4 +70,5 @@ replace ( k8s.io/apimachinery => ../apimachinery k8s.io/client-go => ../client-go k8s.io/component-base => ../component-base + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/endpointslice/go.sum b/staging/src/k8s.io/endpointslice/go.sum index 73f3dee4f0e..3d8fd4cc677 100644 --- a/staging/src/k8s.io/endpointslice/go.sum +++ b/staging/src/k8s.io/endpointslice/go.sum @@ -5,7 +5,6 @@ github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lpr github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= 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/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= diff --git a/staging/src/k8s.io/kube-aggregator/go.mod b/staging/src/k8s.io/kube-aggregator/go.mod index 621c0fd178f..c73df73a874 100644 --- a/staging/src/k8s.io/kube-aggregator/go.mod +++ b/staging/src/k8s.io/kube-aggregator/go.mod @@ -24,6 +24,7 @@ require ( k8s.io/component-base 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/randfill v1.0.0 sigs.k8s.io/structured-merge-diff/v6 v6.3.2 @@ -121,4 +122,5 @@ replace ( k8s.io/code-generator => ../code-generator k8s.io/component-base => ../component-base k8s.io/kms => ../kms + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_proxy.go b/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_proxy.go index ecf1800e05b..ed9cd4592a0 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_proxy.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_proxy.go @@ -22,7 +22,6 @@ import ( "sync/atomic" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/apimachinery/pkg/util/proxy" "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" endpointmetrics "k8s.io/apiserver/pkg/endpoints/metrics" @@ -38,6 +37,7 @@ import ( apiregistrationv1api "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" apiregistrationv1apihelper "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helper" "k8s.io/kube-aggregator/pkg/controllers/status/remote" + "k8s.io/streaming/pkg/httpstream" ) const ( diff --git a/staging/src/k8s.io/kube-controller-manager/go.mod b/staging/src/k8s.io/kube-controller-manager/go.mod index 5e2a5f4a6d2..741ee79f432 100644 --- a/staging/src/k8s.io/kube-controller-manager/go.mod +++ b/staging/src/k8s.io/kube-controller-manager/go.mod @@ -19,6 +19,7 @@ require ( github.com/kr/pretty v0.3.1 // 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/rogpeppe/go-internal v1.14.1 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/net v0.49.0 // indirect @@ -44,4 +45,5 @@ replace ( k8s.io/component-helpers => ../component-helpers k8s.io/controller-manager => ../controller-manager k8s.io/kms => ../kms + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/kube-controller-manager/go.sum b/staging/src/k8s.io/kube-controller-manager/go.sum index 63097c96a5e..33e0569c5b2 100644 --- a/staging/src/k8s.io/kube-controller-manager/go.sum +++ b/staging/src/k8s.io/kube-controller-manager/go.sum @@ -2,7 +2,6 @@ cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= diff --git a/staging/src/k8s.io/kube-proxy/go.mod b/staging/src/k8s.io/kube-proxy/go.mod index 6b641c5ee8e..b2d969bcf04 100644 --- a/staging/src/k8s.io/kube-proxy/go.mod +++ b/staging/src/k8s.io/kube-proxy/go.mod @@ -29,6 +29,7 @@ require ( github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/procfs v0.19.2 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/spf13/cobra v1.10.0 // indirect github.com/spf13/pflag v1.0.9 // indirect github.com/x448/float16 v0.8.4 // indirect @@ -54,4 +55,5 @@ replace ( k8s.io/apimachinery => ../apimachinery k8s.io/client-go => ../client-go k8s.io/component-base => ../component-base + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/kube-proxy/go.sum b/staging/src/k8s.io/kube-proxy/go.sum index b7e9ba78930..828adc6cbbf 100644 --- a/staging/src/k8s.io/kube-proxy/go.sum +++ b/staging/src/k8s.io/kube-proxy/go.sum @@ -2,7 +2,6 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg6 github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= 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/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= diff --git a/staging/src/k8s.io/kube-scheduler/go.mod b/staging/src/k8s.io/kube-scheduler/go.mod index f3879547fe8..c5a84b5559c 100644 --- a/staging/src/k8s.io/kube-scheduler/go.mod +++ b/staging/src/k8s.io/kube-scheduler/go.mod @@ -86,4 +86,5 @@ replace ( k8s.io/dynamic-resource-allocation => ../dynamic-resource-allocation k8s.io/kms => ../kms k8s.io/kubelet => ../kubelet + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/kube-scheduler/go.sum b/staging/src/k8s.io/kube-scheduler/go.sum index cbd256da7e2..afd2c3dc63c 100644 --- a/staging/src/k8s.io/kube-scheduler/go.sum +++ b/staging/src/k8s.io/kube-scheduler/go.sum @@ -9,7 +9,6 @@ github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HR github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= -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= @@ -161,7 +160,6 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76q go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -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.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= diff --git a/staging/src/k8s.io/kubectl/go.mod b/staging/src/k8s.io/kubectl/go.mod index 479201bd467..8bfac490ec9 100644 --- a/staging/src/k8s.io/kubectl/go.mod +++ b/staging/src/k8s.io/kubectl/go.mod @@ -39,6 +39,7 @@ require ( k8s.io/klog/v2 v2.140.0 k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf k8s.io/metrics v0.0.0 + 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/kustomize/kustomize/v5 v5.8.1 @@ -102,4 +103,5 @@ replace ( k8s.io/component-base => ../component-base k8s.io/component-helpers => ../component-helpers k8s.io/metrics => ../metrics + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/attach/attach.go b/staging/src/k8s.io/kubectl/pkg/cmd/attach/attach.go index 147aeb2cf60..933d675c218 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/attach/attach.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/attach/attach.go @@ -28,7 +28,6 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericiooptions" "k8s.io/cli-runtime/pkg/resource" @@ -43,6 +42,7 @@ import ( "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" "k8s.io/kubectl/pkg/util/term" + "k8s.io/streaming/pkg/httpstream" ) var ( diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/exec/exec.go b/staging/src/k8s.io/kubectl/pkg/cmd/exec/exec.go index e648e1191ae..2cdf1efdc66 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/exec/exec.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/exec/exec.go @@ -27,13 +27,13 @@ import ( "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericiooptions" "k8s.io/cli-runtime/pkg/resource" coreclient "k8s.io/client-go/kubernetes/typed/core/v1" restclient "k8s.io/client-go/rest" "k8s.io/client-go/tools/remotecommand" + "k8s.io/streaming/pkg/httpstream" "k8s.io/apimachinery/pkg/api/meta" cmdutil "k8s.io/kubectl/pkg/cmd/util" diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go b/staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go index aaf1d0a3c1c..08a89d7bf18 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward.go @@ -31,7 +31,6 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/cli-runtime/pkg/genericiooptions" "k8s.io/client-go/kubernetes/scheme" @@ -45,6 +44,7 @@ import ( "k8s.io/kubectl/pkg/util/completion" "k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/templates" + streamhttp "k8s.io/streaming/pkg/httpstream" ) // PortForwardOptions contains all the options for running the port-forward cli command. @@ -136,20 +136,20 @@ type defaultPortForwarder struct { genericiooptions.IOStreams } -func createDialer(method string, url *url.URL, opts PortForwardOptions) (httpstream.Dialer, error) { +func createDialer(method string, url *url.URL, opts PortForwardOptions) (streamhttp.Dialer, error) { transport, upgrader, err := spdy.RoundTripperFor(opts.Config) if err != nil { return nil, err } - dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, method, url) + dialer := spdy.NewDialerForStreaming(upgrader, &http.Client{Transport: transport}, method, url) if !cmdutil.PortForwardWebsockets.IsDisabled() { - tunnelingDialer, err := portforward.NewSPDYOverWebsocketDialer(url, opts.Config) + tunnelingDialer, err := portforward.NewSPDYOverWebsocketDialerForStreaming(url, opts.Config) if err != nil { return nil, err } // First attempt tunneling (websocket) dialer, then fallback to spdy dialer. - dialer = portforward.NewFallbackDialer(tunnelingDialer, dialer, func(err error) bool { - return httpstream.IsUpgradeFailure(err) || httpstream.IsHTTPSProxyError(err) + dialer = portforward.NewFallbackDialerForStreaming(tunnelingDialer, dialer, func(err error) bool { + return streamhttp.IsUpgradeFailure(err) || streamhttp.IsHTTPSProxyError(err) }) } return dialer, nil @@ -160,7 +160,7 @@ func (f *defaultPortForwarder) ForwardPorts(method string, url *url.URL, opts Po if err != nil { return err } - fw, err := portforward.NewOnAddresses(dialer, opts.Address, opts.Ports, opts.StopChannel, opts.ReadyChannel, f.Out, f.ErrOut) + fw, err := portforward.NewOnAddressesForStreaming(dialer, opts.Address, opts.Ports, opts.StopChannel, opts.ReadyChannel, f.Out, f.ErrOut) if err != nil { return err } diff --git a/staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward_test.go b/staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward_test.go index d6eddb80c06..b727a1db79e 100644 --- a/staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward_test.go +++ b/staging/src/k8s.io/kubectl/pkg/cmd/portforward/portforward_test.go @@ -1016,7 +1016,7 @@ func TestCreateDialer(t *testing.T) { if err != nil { t.Fatalf("unable to create dialer: %v", err) } - if _, isFallback := dialer.(*portforward.FallbackDialer); !isFallback { + if _, isFallback := dialer.(*portforward.StreamingFallbackDialer); !isFallback { t.Errorf("expected fallback dialer, got %#v", dialer) } // Next, check turning on feature flag explicitly also creates fallback dialer. @@ -1025,7 +1025,7 @@ func TestCreateDialer(t *testing.T) { if err != nil { t.Fatalf("unable to create dialer: %v", err) } - if _, isFallback := dialer.(*portforward.FallbackDialer); !isFallback { + if _, isFallback := dialer.(*portforward.StreamingFallbackDialer); !isFallback { t.Errorf("expected fallback dialer, got %#v", dialer) } // Finally, check explicit disabling does NOT create the fallback dialer. @@ -1034,7 +1034,7 @@ func TestCreateDialer(t *testing.T) { if err != nil { t.Fatalf("unable to create dialer: %v", err) } - if _, isFallback := dialer.(*portforward.FallbackDialer); isFallback { + if _, isFallback := dialer.(*portforward.StreamingFallbackDialer); isFallback { t.Errorf("expected fallback dialer, got %#v", dialer) } } diff --git a/staging/src/k8s.io/kubelet/go.mod b/staging/src/k8s.io/kubelet/go.mod index 0fca4fddd91..d9c7e2195d5 100644 --- a/staging/src/k8s.io/kubelet/go.mod +++ b/staging/src/k8s.io/kubelet/go.mod @@ -7,19 +7,12 @@ go 1.26.0 godebug default=go1.26 require ( - github.com/emicklei/go-restful/v3 v3.13.0 github.com/stretchr/testify v1.11.1 - go.uber.org/goleak v1.3.0 google.golang.org/grpc v1.78.0 google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af k8s.io/api v0.0.0 k8s.io/apimachinery v0.0.0 - k8s.io/apiserver v0.0.0 - k8s.io/client-go v0.0.0 k8s.io/component-base v0.0.0 - k8s.io/cri-api v0.0.0 - k8s.io/klog/v2 v2.140.0 - k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 ) require ( @@ -30,14 +23,11 @@ require ( github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // 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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // 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/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect @@ -47,17 +37,16 @@ require ( github.com/spf13/pflag v1.0.9 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/otel v1.40.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.40.0 // indirect go.opentelemetry.io/otel/trace v1.40.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/net v0.49.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sys v0.40.0 // indirect - golang.org/x/term v0.39.0 // indirect golang.org/x/text v0.33.0 // indirect - golang.org/x/time v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect @@ -68,9 +57,7 @@ require ( replace ( k8s.io/api => ../api k8s.io/apimachinery => ../apimachinery - k8s.io/apiserver => ../apiserver k8s.io/client-go => ../client-go k8s.io/component-base => ../component-base - k8s.io/cri-api => ../cri-api - k8s.io/kms => ../kms + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/kubelet/go.sum b/staging/src/k8s.io/kubelet/go.sum index 8c5a585ae29..8f5e52ff3a5 100644 --- a/staging/src/k8s.io/kubelet/go.sum +++ b/staging/src/k8s.io/kubelet/go.sum @@ -2,13 +2,9 @@ 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/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.30.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= -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/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/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= -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/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= @@ -17,23 +13,17 @@ github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F9 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/coreos/go-oidc v2.5.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= -github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -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/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= @@ -42,37 +32,24 @@ github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= 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/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= 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/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -86,9 +63,7 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/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/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -100,16 +75,13 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWu 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/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -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/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= @@ -121,37 +93,23 @@ github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05Zp github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spf13/cobra v1.10.0 h1:a5/WeUlSDCvV5a45ljW2ZFtV0bTDpkfSAj3uqB6Sc+0= github.com/spf13/cobra v1.10.0/go.mod h1:9dhySC7dnTtEiqzmqfkLj47BslqLCUPMXjG2lj/NgoE= github.com/spf13/pflag v1.0.8/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= -github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= -github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= -go.etcd.io/etcd/api/v3 v3.6.8/go.mod h1:qyQj1HZPUV3B5cbAL8scG62+fyz5dSxxu0w8pn28N6Q= -go.etcd.io/etcd/client/pkg/v3 v3.6.8/go.mod h1:GsiTRUZE2318PggZkAo6sWb6l8JLVrnckTNfbG8PWtw= -go.etcd.io/etcd/client/v3 v3.6.8/go.mod h1:MVG4BpSIuumPi+ELF7wYtySETmoTWBHVcDoHdVupwt8= -go.etcd.io/etcd/pkg/v3 v3.6.8/go.mod h1:TRibVNe+FqJIe1abOAA1PsuQ4wqO87ZaOoprg09Tn8c= -go.etcd.io/etcd/server/v3 v3.6.8/go.mod h1:88dCtwUnSirkUoJbflQxxWXqtBSZa6lSG0Kuej+dois= -go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= 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/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.65.0/go.mod h1:KDgtbWKTQs4bM+VPUr6WlL9m/WXcmkCcBlIzqxPGzmI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.65.0/go.mod h1:c7hN3ddxs/z6q9xwvfLPk+UHlWRQyaeR1LdgfL/66l0= go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= @@ -175,22 +133,18 @@ go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= 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.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.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 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= @@ -205,12 +159,9 @@ google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+ 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/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= -gopkg.in/go-jose/go-jose.v2 v2.6.3/go.mod h1:zzZDPkNNw/c9IE7Z9jr11mBZQhKQTMzoEEIoEdZlFBI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 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/gengo/v2 v2.0.0-20250604051438-85fd79dbfd9f/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= @@ -220,7 +171,6 @@ k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf h1:btPscg4cMql0XdYK2jLsJc k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= 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= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= diff --git a/staging/src/k8s.io/kubelet/pkg/cri/streaming/.import-restrictions b/staging/src/k8s.io/kubelet/pkg/cri/streaming/.import-restrictions deleted file mode 100644 index 10215ff9251..00000000000 --- a/staging/src/k8s.io/kubelet/pkg/cri/streaming/.import-restrictions +++ /dev/null @@ -1,5 +0,0 @@ -rules: - # prevent exposing internal api in streaming packages - - selectorRegexp: k8s[.]io/kubernetes - allowedPrefixes: - - k8s.io/kubernetes/pkg/kubelet/cri diff --git a/staging/src/k8s.io/metrics/go.mod b/staging/src/k8s.io/metrics/go.mod index 53936b911fa..6f2864d9e4c 100644 --- a/staging/src/k8s.io/metrics/go.mod +++ b/staging/src/k8s.io/metrics/go.mod @@ -64,4 +64,5 @@ replace ( k8s.io/apimachinery => ../apimachinery k8s.io/client-go => ../client-go k8s.io/code-generator => ../code-generator + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/metrics/go.sum b/staging/src/k8s.io/metrics/go.sum index f05472b643b..bfd9eb74ab3 100644 --- a/staging/src/k8s.io/metrics/go.sum +++ b/staging/src/k8s.io/metrics/go.sum @@ -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= diff --git a/staging/src/k8s.io/pod-security-admission/go.mod b/staging/src/k8s.io/pod-security-admission/go.mod index 330851ac700..334f344fd66 100644 --- a/staging/src/k8s.io/pod-security-admission/go.mod +++ b/staging/src/k8s.io/pod-security-admission/go.mod @@ -101,6 +101,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 @@ -114,4 +115,5 @@ replace ( k8s.io/client-go => ../client-go k8s.io/component-base => ../component-base k8s.io/kms => ../kms + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/sample-apiserver/go.mod b/staging/src/k8s.io/sample-apiserver/go.mod index eb2a71073c2..b5d6bdc0426 100644 --- a/staging/src/k8s.io/sample-apiserver/go.mod +++ b/staging/src/k8s.io/sample-apiserver/go.mod @@ -105,6 +105,7 @@ require ( k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect k8s.io/klog/v2 v2.140.0 // 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 sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/yaml v1.6.0 // indirect @@ -118,4 +119,5 @@ replace ( k8s.io/code-generator => ../code-generator k8s.io/component-base => ../component-base k8s.io/kms => ../kms + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/sample-cli-plugin/go.mod b/staging/src/k8s.io/sample-cli-plugin/go.mod index e565b925cbc..14dd36b9f48 100644 --- a/staging/src/k8s.io/sample-cli-plugin/go.mod +++ b/staging/src/k8s.io/sample-cli-plugin/go.mod @@ -71,4 +71,5 @@ replace ( k8s.io/apimachinery => ../apimachinery k8s.io/cli-runtime => ../cli-runtime k8s.io/client-go => ../client-go + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/sample-cli-plugin/go.sum b/staging/src/k8s.io/sample-cli-plugin/go.sum index 4cd2bb6efed..0e0275b2aa6 100644 --- a/staging/src/k8s.io/sample-cli-plugin/go.sum +++ b/staging/src/k8s.io/sample-cli-plugin/go.sum @@ -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= diff --git a/staging/src/k8s.io/sample-controller/go.mod b/staging/src/k8s.io/sample-controller/go.mod index 82a8083411a..42a5ef76cec 100644 --- a/staging/src/k8s.io/sample-controller/go.mod +++ b/staging/src/k8s.io/sample-controller/go.mod @@ -62,4 +62,5 @@ replace ( k8s.io/apimachinery => ../apimachinery k8s.io/client-go => ../client-go k8s.io/code-generator => ../code-generator + k8s.io/streaming => ../streaming ) diff --git a/staging/src/k8s.io/sample-controller/go.sum b/staging/src/k8s.io/sample-controller/go.sum index 9e64142de7e..cf862ac8a28 100644 --- a/staging/src/k8s.io/sample-controller/go.sum +++ b/staging/src/k8s.io/sample-controller/go.sum @@ -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= diff --git a/staging/src/k8s.io/streaming/.github/PULL_REQUEST_TEMPLATE.md b/staging/src/k8s.io/streaming/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000000..e7e5eb834b2 --- /dev/null +++ b/staging/src/k8s.io/streaming/.github/PULL_REQUEST_TEMPLATE.md @@ -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. diff --git a/staging/src/k8s.io/streaming/CONTRIBUTING.md b/staging/src/k8s.io/streaming/CONTRIBUTING.md new file mode 100644 index 00000000000..5c09cae420d --- /dev/null +++ b/staging/src/k8s.io/streaming/CONTRIBUTING.md @@ -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 diff --git a/staging/src/k8s.io/streaming/LICENSE b/staging/src/k8s.io/streaming/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/staging/src/k8s.io/streaming/LICENSE @@ -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. diff --git a/staging/src/k8s.io/streaming/OWNERS b/staging/src/k8s.io/streaming/OWNERS new file mode 100644 index 00000000000..dbfc437c636 --- /dev/null +++ b/staging/src/k8s.io/streaming/OWNERS @@ -0,0 +1,13 @@ +# See the OWNERS docs at https://go.k8s.io/owners + +approvers: + - sig-node-approvers + - sttts + - luxas + - mtaufen +reviewers: + - sig-node-reviewers + - luxas + - sttts +labels: + - sig/node diff --git a/staging/src/k8s.io/streaming/README.md b/staging/src/k8s.io/streaming/README.md new file mode 100644 index 00000000000..1a361bf4f34 --- /dev/null +++ b/staging/src/k8s.io/streaming/README.md @@ -0,0 +1,38 @@ +> ⚠️ **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. + +# streaming + +This repository contains the Kubernetes HTTP streaming transport primitives used for: + +- generic stream upgrade negotiation +- SPDY stream connections and round-tripping +- WebSocket channel streaming helpers + +The goal of this module is to provide a dedicated import target for transport utilities shared by CRI streaming, client-go, apiserver, and kubectl. + +## Migration notes + +- The legacy package path `k8s.io/apimachinery/pkg/util/httpstream` was intentionally removed as part of this extraction. +- Consumers must migrate imports to: + - `k8s.io/streaming/pkg/httpstream` + - `k8s.io/streaming/pkg/httpstream/spdy` + - `k8s.io/streaming/pkg/httpstream/wsstream` +- This extraction does not provide compatibility shims at the old apimachinery path. + +## Community, discussion, contribution, and support + +streaming is maintained as part of [SIG API Machinery](https://github.com/kubernetes/community/tree/master/sig-api-machinery) and [SIG Node](https://github.com/kubernetes/community/tree/master/sig-node) areas. + +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). diff --git a/staging/src/k8s.io/streaming/SECURITY_CONTACTS b/staging/src/k8s.io/streaming/SECURITY_CONTACTS new file mode 100644 index 00000000000..6df6a4d6a16 --- /dev/null +++ b/staging/src/k8s.io/streaming/SECURITY_CONTACTS @@ -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 diff --git a/staging/src/k8s.io/streaming/code-of-conduct.md b/staging/src/k8s.io/streaming/code-of-conduct.md new file mode 100644 index 00000000000..0d15c00cf32 --- /dev/null +++ b/staging/src/k8s.io/streaming/code-of-conduct.md @@ -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) diff --git a/staging/src/k8s.io/streaming/doc.go b/staging/src/k8s.io/streaming/doc.go new file mode 100644 index 00000000000..8ec4ceac208 --- /dev/null +++ b/staging/src/k8s.io/streaming/doc.go @@ -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 the staged module root for Kubernetes transport streaming primitives. +package streaming diff --git a/staging/src/k8s.io/streaming/go.mod b/staging/src/k8s.io/streaming/go.mod new file mode 100644 index 00000000000..006245a9e11 --- /dev/null +++ b/staging/src/k8s.io/streaming/go.mod @@ -0,0 +1,26 @@ +// This is a generated file. Do not edit directly. + +module k8s.io/streaming + +go 1.25.0 + +godebug default=go1.25 + +require ( + github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 + github.com/moby/spdystream v0.5.0 + github.com/stretchr/testify v1.11.1 + golang.org/x/net v0.49.0 + k8s.io/klog/v2 v2.140.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/pretty v0.3.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/staging/src/k8s.io/streaming/go.sum b/staging/src/k8s.io/streaming/go.sum new file mode 100644 index 00000000000..7686193d4c5 --- /dev/null +++ b/staging/src/k8s.io/streaming/go.sum @@ -0,0 +1,42 @@ +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/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/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +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.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +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/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= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +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/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/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +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= diff --git a/staging/src/k8s.io/streaming/pkg/httpstream/doc.go b/staging/src/k8s.io/streaming/pkg/httpstream/doc.go new file mode 100644 index 00000000000..1da83f14b18 --- /dev/null +++ b/staging/src/k8s.io/streaming/pkg/httpstream/doc.go @@ -0,0 +1,19 @@ +/* +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 httpstream adds multiplexed streaming support to HTTP requests and +// responses via connection upgrades. +package httpstream diff --git a/staging/src/k8s.io/streaming/pkg/httpstream/httpstream.go b/staging/src/k8s.io/streaming/pkg/httpstream/httpstream.go new file mode 100644 index 00000000000..a7c8d897dc8 --- /dev/null +++ b/staging/src/k8s.io/streaming/pkg/httpstream/httpstream.go @@ -0,0 +1,201 @@ +/* +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 httpstream + +import ( + "errors" + "fmt" + "io" + "net/http" + "strings" + "time" +) + +const ( + HeaderConnection = "Connection" + HeaderUpgrade = "Upgrade" + HeaderProtocolVersion = "X-Stream-Protocol-Version" + HeaderAcceptedProtocolVersions = "X-Accepted-Stream-Protocol-Versions" +) + +// NewStreamHandler defines a function that is called when a new Stream is +// received. If no error is returned, the Stream is accepted; otherwise, +// the stream is rejected. After the reply frame has been sent, replySent is closed. +type NewStreamHandler func(stream Stream, replySent <-chan struct{}) error + +// NoOpNewStreamHandler is a stream handler that accepts a new stream and +// performs no other logic. +func NoOpNewStreamHandler(stream Stream, replySent <-chan struct{}) error { return nil } + +// Dialer knows how to open a streaming connection to a server. +type Dialer interface { + + // Dial opens a streaming connection to a server using one of the protocols + // specified (in order of most preferred to least preferred). + Dial(protocols ...string) (Connection, string, error) +} + +// UpgradeRoundTripper is a type of http.RoundTripper that is able to upgrade +// HTTP requests to support multiplexed bidirectional streams. After RoundTrip() +// is invoked, if the upgrade is successful, clients may retrieve the upgraded +// connection by calling UpgradeRoundTripper.Connection(). +type UpgradeRoundTripper interface { + http.RoundTripper + // NewConnection validates the response and creates a new Connection. + NewConnection(resp *http.Response) (Connection, error) +} + +// ResponseUpgrader knows how to upgrade HTTP requests and responses to +// add streaming support to them. +type ResponseUpgrader interface { + // UpgradeResponse upgrades an HTTP response to one that supports multiplexed + // streams. newStreamHandler will be called asynchronously whenever the + // other end of the upgraded connection creates a new stream. + UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler NewStreamHandler) Connection +} + +// Connection represents an upgraded HTTP connection. +type Connection interface { + // CreateStream creates a new Stream with the supplied headers. + CreateStream(headers http.Header) (Stream, error) + // Close resets all streams and closes the connection. + Close() error + // CloseChan returns a channel that is closed when the underlying connection is closed. + CloseChan() <-chan bool + // SetIdleTimeout sets the amount of time the connection may remain idle before + // it is automatically closed. + SetIdleTimeout(timeout time.Duration) + // RemoveStreams can be used to remove a set of streams from the Connection. + RemoveStreams(streams ...Stream) +} + +// Stream represents a bidirectional communications channel that is part of an +// upgraded connection. +type Stream interface { + io.ReadWriteCloser + // Reset closes both directions of the stream, indicating that neither client + // or server can use it any more. + Reset() error + // Headers returns the headers used to create the stream. + Headers() http.Header + // Identifier returns the stream's ID. + Identifier() uint32 +} + +// UpgradeFailureError encapsulates the cause for why the streaming +// upgrade request failed. Implements error interface. +type UpgradeFailureError struct { + Cause error +} + +func (u *UpgradeFailureError) Error() string { + return fmt.Sprintf("unable to upgrade streaming request: %s", u.Cause) +} + +// IsUpgradeFailure returns true if the passed error is (or wrapped error contains) +// the UpgradeFailureError. +func IsUpgradeFailure(err error) bool { + if err == nil { + return false + } + var upgradeErr *UpgradeFailureError + return errors.As(err, &upgradeErr) +} + +// isHTTPSProxyError returns true if error is Gorilla/Websockets HTTPS Proxy dial error; +// false otherwise (see https://github.com/kubernetes/kubernetes/issues/126134). +func IsHTTPSProxyError(err error) bool { + if err == nil { + return false + } + return strings.Contains(err.Error(), "proxy: unknown scheme: https") +} + +// IsUpgradeRequest returns true if the given request is a connection upgrade request +func IsUpgradeRequest(req *http.Request) bool { + for _, h := range req.Header[http.CanonicalHeaderKey(HeaderConnection)] { + if strings.Contains(strings.ToLower(h), strings.ToLower(HeaderUpgrade)) { + return true + } + } + return false +} + +func negotiateProtocol(clientProtocols, serverProtocols []string) string { + for i := range clientProtocols { + for j := range serverProtocols { + if clientProtocols[i] == serverProtocols[j] { + return clientProtocols[i] + } + } + } + return "" +} + +func commaSeparatedHeaderValues(header []string) []string { + var parsedClientProtocols []string + for i := range header { + for _, clientProtocol := range strings.Split(header[i], ",") { + if proto := strings.Trim(clientProtocol, " "); len(proto) > 0 { + parsedClientProtocols = append(parsedClientProtocols, proto) + } + } + } + return parsedClientProtocols +} + +// Handshake performs a subprotocol negotiation. If the client did request a +// subprotocol, Handshake will select the first common value found in +// serverProtocols, otherwise it will return an error and write an HTTP BadRequest to the response. +// If a match is found, Handshake adds a response header indicating the chosen subprotocol. +// If no match is found, HTTP forbidden is returned, along with a response header containing +// the list of protocols the server can accept. +func Handshake(req *http.Request, w http.ResponseWriter, serverProtocols []string) (string, error) { + if len(serverProtocols) == 0 { + panic(fmt.Errorf("unable to upgrade: serverProtocols is required")) + } + values, ok := req.Header[http.CanonicalHeaderKey(HeaderProtocolVersion)] + if !ok { + err := fmt.Errorf("unable to upgrade: header %s does not exist in request with %d headers", HeaderProtocolVersion, len(req.Header)) + http.Error(w, err.Error(), http.StatusBadRequest) + return "", err + } + if len(values) == 0 { + err := fmt.Errorf("unable to upgrade: header %s is empty", HeaderProtocolVersion) + http.Error(w, err.Error(), http.StatusBadRequest) + return "", err + } + clientProtocols := commaSeparatedHeaderValues(values) + if len(clientProtocols) == 0 { + err := fmt.Errorf("unable to upgrade: header %s contains %s, but no valid protocols", HeaderProtocolVersion, values) + http.Error(w, err.Error(), http.StatusBadRequest) + return "", err + } + + negotiatedProtocol := negotiateProtocol(clientProtocols, serverProtocols) + if len(negotiatedProtocol) == 0 { + for i := range serverProtocols { + w.Header().Add(HeaderAcceptedProtocolVersions, serverProtocols[i]) + } + err := fmt.Errorf("unable to upgrade: unable to negotiate protocol: client supports %v, server accepts %v", clientProtocols, serverProtocols) + http.Error(w, err.Error(), http.StatusForbidden) + return "", err + } + + w.Header().Add(HeaderProtocolVersion, negotiatedProtocol) + return negotiatedProtocol, nil +} diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/httpstream_test.go b/staging/src/k8s.io/streaming/pkg/httpstream/httpstream_test.go similarity index 100% rename from staging/src/k8s.io/apimachinery/pkg/util/httpstream/httpstream_test.go rename to staging/src/k8s.io/streaming/pkg/httpstream/httpstream_test.go diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection.go b/staging/src/k8s.io/streaming/pkg/httpstream/spdy/connection.go similarity index 99% rename from staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection.go rename to staging/src/k8s.io/streaming/pkg/httpstream/spdy/connection.go index 1b91f30516d..4a4003b62b2 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection.go +++ b/staging/src/k8s.io/streaming/pkg/httpstream/spdy/connection.go @@ -23,8 +23,8 @@ import ( "time" "github.com/moby/spdystream" - "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/klog/v2" + "k8s.io/streaming/pkg/httpstream" ) // connection maintains state about a spdystream.Connection and its associated diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection_test.go b/staging/src/k8s.io/streaming/pkg/httpstream/spdy/connection_test.go similarity index 99% rename from staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection_test.go rename to staging/src/k8s.io/streaming/pkg/httpstream/spdy/connection_test.go index 874474581d0..870bb03e414 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection_test.go +++ b/staging/src/k8s.io/streaming/pkg/httpstream/spdy/connection_test.go @@ -27,7 +27,7 @@ import ( "time" "github.com/moby/spdystream" - "k8s.io/apimachinery/pkg/util/httpstream" + "k8s.io/streaming/pkg/httpstream" ) func runProxy(t *testing.T, backendUrl string, proxyUrl chan<- string, proxyDone chan<- struct{}, errCh chan<- error) { diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go b/staging/src/k8s.io/streaming/pkg/httpstream/spdy/roundtripper.go similarity index 65% rename from staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go rename to staging/src/k8s.io/streaming/pkg/httpstream/spdy/roundtripper.go index ed131d112f1..41cd65f64f5 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go +++ b/staging/src/k8s.io/streaming/pkg/httpstream/spdy/roundtripper.go @@ -21,6 +21,7 @@ import ( "context" "crypto/tls" "encoding/base64" + "encoding/json" "errors" "fmt" "io" @@ -28,18 +29,13 @@ import ( "net/http" "net/http/httputil" "net/url" + "os" "strings" "time" "golang.org/x/net/proxy" - apierrors "k8s.io/apimachinery/pkg/api/errors" - 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" - utilnet "k8s.io/apimachinery/pkg/util/net" - apiproxy "k8s.io/apimachinery/pkg/util/proxy" - "k8s.io/apimachinery/third_party/forked/golang/netutil" + "k8s.io/streaming/pkg/httpstream" + utilnet "k8s.io/utils/net" ) // SpdyRoundTripper knows how to upgrade an HTTP request to one that supports @@ -62,7 +58,8 @@ type SpdyRoundTripper struct { // Dialer is the dialer used to connect. Used if non-nil. Dialer *net.Dialer - // proxier knows which proxy to use given a request, defaults to http.ProxyFromEnvironment + // proxier knows which proxy to use given a request, defaults to a proxier that + // preserves NO_PROXY CIDR behavior while delegating to http.ProxyFromEnvironment. // Used primarily for mocking the proxy discovery in tests. proxier func(req *http.Request) (*url.URL, error) @@ -75,9 +72,19 @@ type SpdyRoundTripper struct { upgradeTransport http.RoundTripper } -var _ utilnet.TLSClientConfigHolder = &SpdyRoundTripper{} +type tlsClientConfigHolder interface { + TLSClientConfig() *tls.Config +} + +type roundTripperWrapper interface { + http.RoundTripper + WrappedRoundTripper() http.RoundTripper +} + +type dialFunc func(ctx context.Context, network, addr string) (net.Conn, error) + +var _ tlsClientConfigHolder = &SpdyRoundTripper{} var _ httpstream.UpgradeRoundTripper = &SpdyRoundTripper{} -var _ utilnet.Dialer = &SpdyRoundTripper{} // NewRoundTripper creates a new SpdyRoundTripper that will use the specified // tlsConfig. @@ -106,14 +113,14 @@ func NewRoundTripperWithConfig(cfg RoundTripperConfig) (*SpdyRoundTripper, error if cfg.TLS != nil || cfg.Proxier != nil { return nil, fmt.Errorf("SpdyRoundTripper: UpgradeTransport is mutually exclusive to TLSConfig or Proxier") } - tlsConfig, err := utilnet.TLSClientConfig(cfg.UpgradeTransport) + tlsConfig, err := tlsConfigForTransport(cfg.UpgradeTransport) if err != nil { - return nil, fmt.Errorf("SpdyRoundTripper: Unable to retrieve TLSConfig from UpgradeTransport: %v", err) + return nil, fmt.Errorf("SpdyRoundTripper: unable to retrieve TLS config from UpgradeTransport: %w", err) } cfg.TLS = tlsConfig } if cfg.Proxier == nil { - cfg.Proxier = utilnet.NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment) + cfg.Proxier = newProxierWithNoProxyCIDR(http.ProxyFromEnvironment) } return &SpdyRoundTripper{ tlsConfig: cfg.TLS, @@ -123,6 +130,47 @@ func NewRoundTripperWithConfig(cfg RoundTripperConfig) (*SpdyRoundTripper, error }, nil } +// newProxierWithNoProxyCIDR preserves CIDR matching in NO_PROXY/no_proxy while +// delegating all other behavior to the supplied proxy function. +func newProxierWithNoProxyCIDR(delegate func(req *http.Request) (*url.URL, error)) func(req *http.Request) (*url.URL, error) { + noProxyEnv := os.Getenv("NO_PROXY") + if noProxyEnv == "" { + noProxyEnv = os.Getenv("no_proxy") + } + noProxyRules := strings.Split(noProxyEnv, ",") + + cidrs := make([]*net.IPNet, 0, len(noProxyRules)) + for _, noProxyRule := range noProxyRules { + noProxyRule = strings.TrimSpace(noProxyRule) + if noProxyRule == "" { + continue + } + _, cidr, err := utilnet.ParseCIDRSloppy(noProxyRule) + if err == nil { + cidrs = append(cidrs, cidr) + } + } + + if len(cidrs) == 0 { + return delegate + } + + return func(req *http.Request) (*url.URL, error) { + ip := utilnet.ParseIPSloppy(req.URL.Hostname()) + if ip == nil { + return delegate(req) + } + + for _, cidr := range cidrs { + if cidr.Contains(ip) { + return nil, nil + } + } + + return delegate(req) + } +} + // RoundTripperConfig is a set of options for an SpdyRoundTripper. type RoundTripperConfig struct { // TLS configuration used by the round tripper if UpgradeTransport not present. @@ -144,12 +192,12 @@ func (s *SpdyRoundTripper) TLSClientConfig() *tls.Config { return s.tlsConfig } -// Dial implements k8s.io/apimachinery/pkg/util/net.Dialer. +// Dial opens a network connection for an upgrade request. func (s *SpdyRoundTripper) Dial(req *http.Request) (net.Conn, error) { var conn net.Conn var err error if s.upgradeTransport != nil { - conn, err = apiproxy.DialURL(req.Context(), req.URL, s.upgradeTransport) + conn, err = dialURLWithTransport(req.Context(), req.URL, s.upgradeTransport) } else { conn, err = s.dial(req) } @@ -190,7 +238,7 @@ func (s *SpdyRoundTripper) dial(req *http.Request) (net.Conn, error) { // dialWithHttpProxy dials the host specified by url through an http or an https proxy. func (s *SpdyRoundTripper) dialWithHttpProxy(req *http.Request, proxyURL *url.URL) (net.Conn, error) { // ensure we use a canonical host with proxyReq - targetHost := netutil.CanonicalAddr(req.URL) + targetHost := canonicalAddr(req.URL) // proxying logic adapted from http://blog.h6t.eu/post/74098062923/golang-websocket-with-http-proxy-support proxyReq := http.Request{ @@ -234,8 +282,8 @@ func (s *SpdyRoundTripper) dialWithHttpProxy(req *http.Request, proxyURL *url.UR // dialWithSocks5Proxy dials the host specified by url through a socks5 proxy. func (s *SpdyRoundTripper) dialWithSocks5Proxy(req *http.Request, proxyURL *url.URL) (net.Conn, error) { // ensure we use a canonical host with proxyReq - targetHost := netutil.CanonicalAddr(req.URL) - proxyDialAddr := netutil.CanonicalAddr(proxyURL) + targetHost := canonicalAddr(req.URL) + proxyDialAddr := canonicalAddr(proxyURL) var auth *proxy.Auth if proxyURL.User != nil { @@ -304,7 +352,7 @@ func (s *SpdyRoundTripper) tlsConn(ctx context.Context, rwc net.Conn, targetHost // dialWithoutProxy dials the host specified by url, using TLS if appropriate. func (s *SpdyRoundTripper) dialWithoutProxy(ctx context.Context, url *url.URL) (net.Conn, error) { - dialAddr := netutil.CanonicalAddr(url) + dialAddr := canonicalAddr(url) dialer := s.Dialer if dialer == nil { dialer = &net.Dialer{} @@ -336,7 +384,8 @@ func (s *SpdyRoundTripper) proxyAuth(proxyURL *url.URL) string { // clients may call SpdyRoundTripper.Connection() to retrieve the upgraded // connection. func (s *SpdyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - req = utilnet.CloneRequest(req) + req = req.Clone(req.Context()) + req.Header = req.Header.Clone() req.Header.Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade) req.Header.Add(httpstream.HeaderUpgrade, HeaderSpdy31) @@ -365,35 +414,159 @@ func (s *SpdyRoundTripper) NewConnection(resp *http.Response) (httpstream.Connec upgradeHeader := strings.ToLower(resp.Header.Get(httpstream.HeaderUpgrade)) if (resp.StatusCode != http.StatusSwitchingProtocols) || !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) { defer resp.Body.Close() - responseError := "" responseErrorBytes, err := io.ReadAll(resp.Body) if err != nil { - responseError = "unable to read error from server response" - } else { - // TODO: I don't belong here, I should be abstracted from this class - if obj, _, err := statusCodecs.UniversalDecoder().Decode(responseErrorBytes, nil, &metav1.Status{}); err == nil { - if status, ok := obj.(*metav1.Status); ok { - return nil, &apierrors.StatusError{ErrStatus: *status} - } - } - responseError = string(responseErrorBytes) - responseError = strings.TrimSpace(responseError) + return nil, fmt.Errorf("unable to upgrade connection: unable to read error from server response") } - - return nil, fmt.Errorf("unable to upgrade connection: %s", responseError) + return nil, fmt.Errorf("unable to upgrade connection: %s", upgradeErrorMessage(responseErrorBytes)) } return NewClientConnectionWithPings(s.conn, s.pingPeriod) } -// statusScheme is private scheme for the decoding here until someone fixes the TODO in NewConnection -var statusScheme = runtime.NewScheme() - -// ParameterCodec knows about query parameters used with the meta v1 API spec. -var statusCodecs = serializer.NewCodecFactory(statusScheme) - -func init() { - statusScheme.AddUnversionedTypes(metav1.SchemeGroupVersion, - &metav1.Status{}, - ) +func tlsConfigForTransport(transport http.RoundTripper) (*tls.Config, error) { + if transport == nil { + return nil, nil + } + switch transport := transport.(type) { + case *http.Transport: + return transport.TLSClientConfig, nil + case tlsClientConfigHolder: + return transport.TLSClientConfig(), nil + case roundTripperWrapper: + return tlsConfigForTransport(transport.WrappedRoundTripper()) + default: + return nil, fmt.Errorf("transport %T does not expose TLS client config", transport) + } +} + +func canonicalAddr(url *url.URL) string { + host := url.Hostname() + port := url.Port() + if len(port) == 0 { + switch strings.ToLower(url.Scheme) { + case "http", "ws": + port = "80" + case "https", "wss": + port = "443" + } + } + return net.JoinHostPort(host, port) +} + +func upgradeErrorMessage(responseErrorBytes []byte) string { + type statusLike struct { + Message string `json:"message"` + Error string `json:"error"` + } + + var status statusLike + if err := json.Unmarshal(responseErrorBytes, &status); err == nil { + if msg := strings.TrimSpace(status.Message); msg != "" { + return msg + } + if msg := strings.TrimSpace(status.Error); msg != "" { + return msg + } + } + + msg := strings.TrimSpace(string(responseErrorBytes)) + if msg == "" { + return "empty server response" + } + return msg +} + +func dialURLWithTransport(ctx context.Context, url *url.URL, transport http.RoundTripper) (net.Conn, error) { + dialAddr := canonicalAddr(url) + + dialer, err := dialerFor(transport) + if err != nil { + dialer = nil + } + + switch url.Scheme { + case "http": + if dialer != nil { + return dialer(ctx, "tcp", dialAddr) + } + var d net.Dialer + return d.DialContext(ctx, "tcp", dialAddr) + case "https": + tlsConfig, err := tlsConfigForTransport(transport) + if err != nil { + tlsConfig = nil + } + + if dialer != nil { + netConn, err := dialer(ctx, "tcp", dialAddr) + if err != nil { + return nil, err + } + + if tlsConfig == nil { + tlsConfig = &tls.Config{InsecureSkipVerify: true} + } else if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify { + inferredHost := dialAddr + if host, _, err := net.SplitHostPort(dialAddr); err == nil { + inferredHost = host + } + tlsConfigCopy := tlsConfig.Clone() + tlsConfigCopy.ServerName = inferredHost + tlsConfig = tlsConfigCopy + } + + if supportsHTTP11(tlsConfig.NextProtos) { + tlsConfig = tlsConfig.Clone() + tlsConfig.NextProtos = []string{"http/1.1"} + } + + tlsConn := tls.Client(netConn, tlsConfig) + if err := tlsConn.HandshakeContext(ctx); err != nil { + netConn.Close() + return nil, err + } + return tlsConn, nil + } + + tlsDialer := tls.Dialer{Config: tlsConfig} + return tlsDialer.DialContext(ctx, "tcp", dialAddr) + default: + return nil, fmt.Errorf("unknown scheme: %s", url.Scheme) + } +} + +func dialerFor(transport http.RoundTripper) (dialFunc, error) { + if transport == nil { + return nil, nil + } + + switch transport := transport.(type) { + case *http.Transport: + if transport.DialContext != nil { + return transport.DialContext, nil + } + if transport.Dial != nil { + return func(ctx context.Context, network, addr string) (net.Conn, error) { + return transport.Dial(network, addr) + }, nil + } + return nil, nil + case roundTripperWrapper: + return dialerFor(transport.WrappedRoundTripper()) + default: + return nil, fmt.Errorf("unknown transport type: %T", transport) + } +} + +func supportsHTTP11(nextProtos []string) bool { + if len(nextProtos) == 0 { + return true + } + for _, proto := range nextProtos { + if proto == "http/1.1" { + return true + } + } + return false } diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper_test.go b/staging/src/k8s.io/streaming/pkg/httpstream/spdy/roundtripper_test.go similarity index 90% rename from staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper_test.go rename to staging/src/k8s.io/streaming/pkg/httpstream/spdy/roundtripper_test.go index 8894089b82f..5502c3f0d19 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper_test.go +++ b/staging/src/k8s.io/streaming/pkg/httpstream/spdy/roundtripper_test.go @@ -24,18 +24,19 @@ import ( "net" "net/http" "net/http/httptest" + "net/http/httputil" "net/url" "reflect" "strconv" "strings" + "sync" "testing" "github.com/armon/go-socks5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/util/httpstream" - utilnettesting "k8s.io/apimachinery/pkg/util/net/testing" + "k8s.io/streaming/pkg/httpstream" ) type serverHandlerConfig struct { @@ -78,6 +79,91 @@ func serverHandler(t *testing.T, config serverHandlerConfig) http.HandlerFunc { type serverFunc func(http.Handler) *httptest.Server +type testHTTPProxyHandler struct { + handlerDone sync.WaitGroup + hook func(*http.Request) bool + httpProxy httputil.ReverseProxy + t testing.TB +} + +func newTestHTTPProxyHandler(t testing.TB, hook func(*http.Request) bool) *testHTTPProxyHandler { + return &testHTTPProxyHandler{ + hook: hook, + httpProxy: httputil.ReverseProxy{ + Director: func(req *http.Request) { + req.URL.Scheme = "http" + req.URL.Host = req.Host + }, + }, + t: t, + } +} + +func (h *testHTTPProxyHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + h.handlerDone.Add(1) + defer h.handlerDone.Done() + + if h.hook != nil { + if ok := h.hook(req); !ok { + rw.WriteHeader(http.StatusInternalServerError) + return + } + } + + if req.Method != http.MethodConnect { + h.httpProxy.ServeHTTP(rw, req) + return + } + + sconn, err := net.Dial("tcp", req.Host) + if err != nil { + h.t.Logf("failed to dial proxy backend, host=%s: %v", req.Host, err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + defer sconn.Close() + + hj, ok := rw.(http.Hijacker) + if !ok { + h.t.Logf("response writer cannot hijack for host=%s", req.Host) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + rw.WriteHeader(http.StatusOK) + conn, brw, err := hj.Hijack() + if err != nil { + h.t.Logf("failed to hijack client connection, host=%s: %v", req.Host, err) + return + } + defer conn.Close() + + if err := brw.Flush(); err != nil { + h.t.Logf("failed to flush pending writes, host=%s: %v", req.Host, err) + return + } + if _, err := io.Copy(sconn, io.LimitReader(brw, int64(brw.Reader.Buffered()))); err != nil { + h.t.Logf("failed to flush buffered reads, host=%s: %v", req.Host, err) + return + } + + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + io.Copy(conn, sconn) + }() + go func() { + defer wg.Done() + io.Copy(sconn, conn) + }() + wg.Wait() +} + +func (h *testHTTPProxyHandler) Wait() { + h.handlerDone.Wait() +} + func httpsServerInvalidHostname(t *testing.T) serverFunc { return func(h http.Handler) *httptest.Server { cert, err := tls.X509KeyPair(exampleCert, exampleKey) @@ -336,7 +422,7 @@ func TestRoundTripAndNewConnection(t *testing.T) { var proxyCalledWithAuth bool var proxyCalledWithAuthHeader string if testCase.proxyServerFunc != nil { - proxyHandler := utilnettesting.NewHTTPProxyHandler(t, func(req *http.Request) bool { + proxyHandler := newTestHTTPProxyHandler(t, func(req *http.Request) bool { proxyCalledWithHost = req.Host proxyAuthHeaderName := "Proxy-Authorization" @@ -501,6 +587,52 @@ func TestRoundTripConstuctor(t *testing.T) { } } +func TestProxierWithNoProxyCIDR(t *testing.T) { + t.Setenv("NO_PROXY", "10.0.0.0/8,2001:db8::/32") + t.Setenv("no_proxy", "") + + expectedProxy, err := url.Parse("http://proxy.example:8080") + require.NoError(t, err) + + delegateCalls := 0 + delegate := func(*http.Request) (*url.URL, error) { + delegateCalls++ + return expectedProxy, nil + } + + proxier := newProxierWithNoProxyCIDR(delegate) + + req, err := http.NewRequest(http.MethodGet, "https://10.1.2.3", nil) + require.NoError(t, err) + proxyURL, err := proxier(req) + require.NoError(t, err) + require.Nil(t, proxyURL) + require.Equal(t, 0, delegateCalls) + + req, err = http.NewRequest(http.MethodGet, "https://[2001:db8::1]", nil) + require.NoError(t, err) + proxyURL, err = proxier(req) + require.NoError(t, err) + require.Nil(t, proxyURL) + require.Equal(t, 0, delegateCalls) + + req, err = http.NewRequest(http.MethodGet, "https://192.168.1.10", nil) + require.NoError(t, err) + proxyURL, err = proxier(req) + require.NoError(t, err) + require.NotNil(t, proxyURL) + require.Equal(t, expectedProxy.String(), proxyURL.String()) + require.Equal(t, 1, delegateCalls) + + req, err = http.NewRequest(http.MethodGet, "https://example.com", nil) + require.NoError(t, err) + proxyURL, err = proxier(req) + require.NoError(t, err) + require.NotNil(t, proxyURL) + require.Equal(t, expectedProxy.String(), proxyURL.String()) + require.Equal(t, 2, delegateCalls) +} + type Interceptor struct { Authorization socks5.AuthContext proxyCalledWithHost *string diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/upgrade.go b/staging/src/k8s.io/streaming/pkg/httpstream/spdy/upgrade.go similarity index 97% rename from staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/upgrade.go rename to staging/src/k8s.io/streaming/pkg/httpstream/spdy/upgrade.go index c15e7afcc43..df47029f4a9 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/upgrade.go +++ b/staging/src/k8s.io/streaming/pkg/httpstream/spdy/upgrade.go @@ -26,8 +26,8 @@ import ( "sync/atomic" "time" - "k8s.io/apimachinery/pkg/util/httpstream" - "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/streaming/pkg/httpstream" + "k8s.io/streaming/pkg/runtime" ) const HeaderSpdy31 = "SPDY/3.1" diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/upgrade_test.go b/staging/src/k8s.io/streaming/pkg/httpstream/spdy/upgrade_test.go similarity index 100% rename from staging/src/k8s.io/apimachinery/pkg/util/httpstream/spdy/upgrade_test.go rename to staging/src/k8s.io/streaming/pkg/httpstream/spdy/upgrade_test.go diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/conn.go b/staging/src/k8s.io/streaming/pkg/httpstream/wsstream/conn.go similarity index 97% rename from staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/conn.go rename to staging/src/k8s.io/streaming/pkg/httpstream/wsstream/conn.go index 93c9040ab96..eae80ab4d3d 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/conn.go +++ b/staging/src/k8s.io/streaming/pkg/httpstream/wsstream/conn.go @@ -26,11 +26,9 @@ import ( "golang.org/x/net/websocket" - "k8s.io/apimachinery/pkg/util/httpstream" - "k8s.io/apimachinery/pkg/util/portforward" - "k8s.io/apimachinery/pkg/util/remotecommand" - "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/klog/v2" + "k8s.io/streaming/pkg/httpstream" + "k8s.io/streaming/pkg/runtime" ) const WebSocketProtocolHeader = "Sec-Websocket-Protocol" @@ -65,6 +63,8 @@ const ChannelWebSocketProtocol = "channel.k8s.io" // CLOSE const Base64ChannelWebSocketProtocol = "base64.channel.k8s.io" +const streamCloseSignal = 255 + type codecType int const ( @@ -324,13 +324,13 @@ func (conn *Conn) Close() error { // supports the stream close signal (currently only V5 remotecommand); // false otherwise. func protocolSupportsStreamClose(protocol string) bool { - return protocol == remotecommand.StreamProtocolV5Name + return protocol == "v5.channel.k8s.io" } // protocolSupportsWebsocketTunneling returns true if the passed protocol // is a tunneled Kubernetes spdy protocol; false otherwise. func protocolSupportsWebsocketTunneling(protocol string) bool { - return strings.HasPrefix(protocol, portforward.WebsocketsSPDYTunnelingPrefix) && strings.HasSuffix(protocol, portforward.KubernetesSuffix) + return strings.HasPrefix(protocol, "SPDY/3.1+") && strings.HasSuffix(protocol, ".k8s.io") } // handle implements a websocket handler. @@ -357,7 +357,7 @@ func (conn *Conn) handle(ws *websocket.Conn) { if len(data) == 0 { continue } - if supportsStreamClose && data[0] == remotecommand.StreamClose { + if supportsStreamClose && data[0] == streamCloseSignal { if len(data) != 2 { logger.Error(nil, "Single channel byte should follow stream close signal", "receivedLength", len(data)-1) break diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/conn_test.go b/staging/src/k8s.io/streaming/pkg/httpstream/wsstream/conn_test.go similarity index 100% rename from staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/conn_test.go rename to staging/src/k8s.io/streaming/pkg/httpstream/wsstream/conn_test.go diff --git a/staging/src/k8s.io/streaming/pkg/httpstream/wsstream/doc.go b/staging/src/k8s.io/streaming/pkg/httpstream/wsstream/doc.go new file mode 100644 index 00000000000..a57e8df60a3 --- /dev/null +++ b/staging/src/k8s.io/streaming/pkg/httpstream/wsstream/doc.go @@ -0,0 +1,69 @@ +/* +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 contains utilities for streaming content over WebSockets. +// The Conn type allows callers to multiplex multiple read/write channels over +// a single websocket. +// +// "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 +package wsstream diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/stream.go b/staging/src/k8s.io/streaming/pkg/httpstream/wsstream/stream.go similarity index 99% rename from staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/stream.go rename to staging/src/k8s.io/streaming/pkg/httpstream/wsstream/stream.go index 1e8135e1a70..38cc41a23dc 100644 --- a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/stream.go +++ b/staging/src/k8s.io/streaming/pkg/httpstream/wsstream/stream.go @@ -26,8 +26,8 @@ import ( "golang.org/x/net/websocket" - "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/klog/v2" + "k8s.io/streaming/pkg/runtime" ) // The WebSocket subprotocol "binary.k8s.io" will only send messages to the diff --git a/staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/stream_test.go b/staging/src/k8s.io/streaming/pkg/httpstream/wsstream/stream_test.go similarity index 100% rename from staging/src/k8s.io/apimachinery/pkg/util/httpstream/wsstream/stream_test.go rename to staging/src/k8s.io/streaming/pkg/httpstream/wsstream/stream_test.go diff --git a/staging/src/k8s.io/streaming/pkg/runtime/runtime.go b/staging/src/k8s.io/streaming/pkg/runtime/runtime.go new file mode 100644 index 00000000000..25a70b735e3 --- /dev/null +++ b/staging/src/k8s.io/streaming/pkg/runtime/runtime.go @@ -0,0 +1,62 @@ +/* +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 runtime + +import ( + "context" + "fmt" + + "k8s.io/klog/v2" +) + +// HandleError logs an asynchronous error. +func HandleError(err error) { + if err == nil { + return + } + klog.Background().Error(err, "Unhandled Error") +} + +// HandleErrorWithContext logs an asynchronous error with contextual logging when available. +func HandleErrorWithContext(ctx context.Context, err error, msg string, keysAndValues ...interface{}) { + if err == nil { + return + } + klog.FromContext(ctx).Error(err, msg, keysAndValues...) +} + +// HandleCrash recovers from panic and logs it. +func HandleCrash() { + HandleCrashWithLogger(klog.Background()) +} + +// HandleCrashWithContext recovers from panic and logs it with the context logger. +func HandleCrashWithContext(ctx context.Context, additionalHandlers ...func(context.Context, interface{})) { + if r := recover(); r != nil { + for _, fn := range additionalHandlers { + fn(ctx, r) + } + klog.FromContext(ctx).Error(fmt.Errorf("%v", r), "Observed a panic") + } +} + +// HandleCrashWithLogger recovers from panic and logs it using the provided logger. +func HandleCrashWithLogger(logger klog.Logger) { + if r := recover(); r != nil { + logger.Error(fmt.Errorf("%v", r), "Observed a panic") + } +} diff --git a/test/e2e/framework/pod/dial.go b/test/e2e/framework/pod/dial.go index 7846426d6e1..37aeef0a3a2 100644 --- a/test/e2e/framework/pod/dial.go +++ b/test/e2e/framework/pod/dial.go @@ -29,7 +29,6 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" @@ -37,6 +36,7 @@ import ( "k8s.io/client-go/transport/spdy" "k8s.io/klog/v2" "k8s.io/kubernetes/test/e2e/framework" + "k8s.io/streaming/pkg/httpstream" ) // NewTransport creates a transport which uses the port forward dialer. @@ -90,14 +90,14 @@ func (d *Dialer) DialContainerPort(ctx context.Context, addr Addr) (conn net.Con if err != nil { return nil, fmt.Errorf("create round tripper: %w", err) } - dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", req.URL()) + dialer := spdy.NewDialerForStreaming(upgrader, &http.Client{Transport: transport}, "POST", req.URL()) - tunnelingDialer, err := portforward.NewSPDYOverWebsocketDialer(req.URL(), restConfig) + tunnelingDialer, err := portforward.NewSPDYOverWebsocketDialerForStreaming(req.URL(), restConfig) if err != nil { return nil, err } // First attempt tunneling (websocket) dialer, then fallback to spdy dialer. - dialer = portforward.NewFallbackDialer(tunnelingDialer, dialer, func(err error) bool { + dialer = portforward.NewFallbackDialerForStreaming(tunnelingDialer, dialer, func(err error) bool { if httpstream.IsUpgradeFailure(err) || httpstream.IsHTTPSProxyError(err) { framework.Logf("fallback to secondary dialer from primary dialer err: %v", err) return true diff --git a/test/e2e/framework/pod/exec_util.go b/test/e2e/framework/pod/exec_util.go index 1394661e71b..e4a34fe1d4b 100644 --- a/test/e2e/framework/pod/exec_util.go +++ b/test/e2e/framework/pod/exec_util.go @@ -27,12 +27,12 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/remotecommand" clientexec "k8s.io/client-go/util/exec" "k8s.io/kubernetes/test/e2e/framework" "k8s.io/kubernetes/test/utils/ktesting" + "k8s.io/streaming/pkg/httpstream" "github.com/onsi/gomega" ) diff --git a/test/e2e/storage/drivers/proxy/portproxy.go b/test/e2e/storage/drivers/proxy/portproxy.go index 27b5948823c..6a8274ade80 100644 --- a/test/e2e/storage/drivers/proxy/portproxy.go +++ b/test/e2e/storage/drivers/proxy/portproxy.go @@ -29,13 +29,13 @@ import ( v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "k8s.io/client-go/tools/portforward" "k8s.io/client-go/transport/spdy" "k8s.io/klog/v2" + "k8s.io/streaming/pkg/httpstream" ) // Maximum number of forwarded connections. In practice we don't @@ -99,7 +99,7 @@ func Listen(ctx context.Context, clientset kubernetes.Interface, restConfig *res if err != nil { return nil, fmt.Errorf("create round tripper: %w", err) } - dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", req.URL()) + dialer := spdy.NewDialerForStreaming(upgrader, &http.Client{Transport: transport}, "POST", req.URL()) prefix := fmt.Sprintf("port forwarding for %s", addr) ctx, cancel := context.WithCancel(ctx) diff --git a/test/integration/apiserver/portforward/portforward_test.go b/test/integration/apiserver/portforward/portforward_test.go index db1abb1b10e..c83874e52a0 100644 --- a/test/integration/apiserver/portforward/portforward_test.go +++ b/test/integration/apiserver/portforward/portforward_test.go @@ -43,8 +43,8 @@ import ( "k8s.io/cli-runtime/pkg/genericiooptions" "k8s.io/client-go/kubernetes" featuregatetesting "k8s.io/component-base/featuregate/testing" + kubeletportforward "k8s.io/cri-streaming/pkg/streaming/portforward" "k8s.io/kubectl/pkg/cmd/portforward" - kubeletportforward "k8s.io/kubelet/pkg/cri/streaming/portforward" kastesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" kubefeatures "k8s.io/kubernetes/pkg/features" @@ -58,7 +58,7 @@ func TestPortforward(t *testing.T) { t.Setenv("KUBECTL_PORT_FORWARD_WEBSOCKETS", "true") var podName string - var podUID types.UID + var podUID string backendServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { t.Logf("backend saw request: %v", req.URL.String()) kubeletportforward.ServePortForward( @@ -213,7 +213,7 @@ type dummyPortForwarder struct { t *testing.T } -func (d *dummyPortForwarder) PortForward(ctx context.Context, name string, uid types.UID, port int32, stream io.ReadWriteCloser) error { +func (d *dummyPortForwarder) PortForward(ctx context.Context, name string, uid string, port int32, stream io.ReadWriteCloser) error { d.t.Logf("handling port forward request for %d", port) req, err := http.ReadRequest(bufio.NewReader(stream)) diff --git a/vendor/modules.txt b/vendor/modules.txt index bd023b09bf0..cce0a452d87 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1088,6 +1088,8 @@ gopkg.in/yaml.v3 ## explicit; go 1.26.0 # k8s.io/cri-client v0.0.0 => ./staging/src/k8s.io/cri-client ## explicit; go 1.26.0 +# k8s.io/cri-streaming v0.0.0 => ./staging/src/k8s.io/cri-streaming +## explicit; go 1.25.0 # k8s.io/csi-translation-lib v0.0.0 => ./staging/src/k8s.io/csi-translation-lib ## explicit; go 1.26.0 # k8s.io/dynamic-resource-allocation v0.0.0 => ./staging/src/k8s.io/dynamic-resource-allocation @@ -1173,6 +1175,8 @@ k8s.io/kube-openapi/pkg/validation/validate ## explicit; go 1.26.0 # k8s.io/sample-apiserver v0.0.0 => ./staging/src/k8s.io/sample-apiserver ## explicit; go 1.26.0 +# k8s.io/streaming v0.0.0 => ./staging/src/k8s.io/streaming +## explicit; go 1.25.0 # k8s.io/system-validators v1.12.1 ## explicit; go 1.16 k8s.io/system-validators/validators From 57d7c4d812d6c3cccd6bbca9e6e9701ef63be122 Mon Sep 17 00:00:00 2001 From: Davanum Srinivas Date: Fri, 6 Mar 2026 11:05:26 -0500 Subject: [PATCH 2/5] staging/streaming: replace testify with stdlib in tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove github.com/stretchr/testify from k8s.io/streaming's test files. testify's assert/yaml subpackage pulls in gopkg.in/yaml.v3, whose test dependencies (gopkg.in/check.v1 → kr/pretty → rogpeppe/go-internal) were propagating into k8s.io/apimachinery and k8s.io/api via the streaming dependency. Removes stretchr/testify, gopkg.in/yaml.v3, gopkg.in/check.v1, github.com/kr/pretty, and github.com/rogpeppe/go-internal from streaming/go.mod. Signed-off-by: Davanum Srinivas --- staging/src/k8s.io/api/go.mod | 1 - staging/src/k8s.io/apimachinery/go.mod | 1 + staging/src/k8s.io/code-generator/go.mod | 1 - staging/src/k8s.io/cri-streaming/go.mod | 6 +- staging/src/k8s.io/cri-streaming/go.sum | 4 +- .../src/k8s.io/kube-controller-manager/go.mod | 1 - staging/src/k8s.io/kube-proxy/go.mod | 1 - staging/src/k8s.io/kubelet/go.mod | 1 + staging/src/k8s.io/streaming/go.mod | 15 +--- staging/src/k8s.io/streaming/go.sum | 27 +----- .../pkg/httpstream/spdy/roundtripper_test.go | 86 ++++++++++++++----- .../pkg/httpstream/wsstream/conn_test.go | 15 ++-- vendor/modules.txt | 4 +- 13 files changed, 85 insertions(+), 78 deletions(-) diff --git a/staging/src/k8s.io/api/go.mod b/staging/src/k8s.io/api/go.mod index a75700f836b..8fd744da262 100644 --- a/staging/src/k8s.io/api/go.mod +++ b/staging/src/k8s.io/api/go.mod @@ -20,7 +20,6 @@ require ( github.com/kr/pretty v0.3.1 // 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/rogpeppe/go-internal v1.14.1 // indirect github.com/spf13/pflag v1.0.9 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect diff --git a/staging/src/k8s.io/apimachinery/go.mod b/staging/src/k8s.io/apimachinery/go.mod index 17ec51f3a1a..acd4af58f71 100644 --- a/staging/src/k8s.io/apimachinery/go.mod +++ b/staging/src/k8s.io/apimachinery/go.mod @@ -43,6 +43,7 @@ require ( github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/onsi/ginkgo/v2 v2.28.1 // indirect github.com/onsi/gomega v1.39.1 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect diff --git a/staging/src/k8s.io/code-generator/go.mod b/staging/src/k8s.io/code-generator/go.mod index f47752f78e9..7d0212e6f67 100644 --- a/staging/src/k8s.io/code-generator/go.mod +++ b/staging/src/k8s.io/code-generator/go.mod @@ -36,7 +36,6 @@ require ( github.com/mailru/easyjson v0.7.7 // 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/rogpeppe/go-internal v1.14.1 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/mod v0.32.0 // indirect diff --git a/staging/src/k8s.io/cri-streaming/go.mod b/staging/src/k8s.io/cri-streaming/go.mod index da0c162e62a..0167548038b 100644 --- a/staging/src/k8s.io/cri-streaming/go.mod +++ b/staging/src/k8s.io/cri-streaming/go.mod @@ -2,9 +2,9 @@ module k8s.io/cri-streaming -go 1.25.0 +go 1.26.0 -godebug default=go1.25 +godebug default=go1.26 require ( github.com/emicklei/go-restful/v3 v3.13.0 @@ -31,7 +31,7 @@ require ( 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.11 // indirect + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/staging/src/k8s.io/cri-streaming/go.sum b/staging/src/k8s.io/cri-streaming/go.sum index 72cd5b1b02b..495c21559d8 100644 --- a/staging/src/k8s.io/cri-streaming/go.sum +++ b/staging/src/k8s.io/cri-streaming/go.sum @@ -78,8 +78,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1: 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.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +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= diff --git a/staging/src/k8s.io/kube-controller-manager/go.mod b/staging/src/k8s.io/kube-controller-manager/go.mod index 741ee79f432..5c134104cc8 100644 --- a/staging/src/k8s.io/kube-controller-manager/go.mod +++ b/staging/src/k8s.io/kube-controller-manager/go.mod @@ -19,7 +19,6 @@ require ( github.com/kr/pretty v0.3.1 // 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/rogpeppe/go-internal v1.14.1 // indirect github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect golang.org/x/net v0.49.0 // indirect diff --git a/staging/src/k8s.io/kube-proxy/go.mod b/staging/src/k8s.io/kube-proxy/go.mod index b2d969bcf04..06100cd6d2c 100644 --- a/staging/src/k8s.io/kube-proxy/go.mod +++ b/staging/src/k8s.io/kube-proxy/go.mod @@ -29,7 +29,6 @@ require ( github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.67.5 // indirect github.com/prometheus/procfs v0.19.2 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/spf13/cobra v1.10.0 // indirect github.com/spf13/pflag v1.0.9 // indirect github.com/x448/float16 v0.8.4 // indirect diff --git a/staging/src/k8s.io/kubelet/go.mod b/staging/src/k8s.io/kubelet/go.mod index d9c7e2195d5..93123c73fe8 100644 --- a/staging/src/k8s.io/kubelet/go.mod +++ b/staging/src/k8s.io/kubelet/go.mod @@ -48,6 +48,7 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.0.0-20260304202019-5b3e3fdb0acf // indirect + k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect diff --git a/staging/src/k8s.io/streaming/go.mod b/staging/src/k8s.io/streaming/go.mod index 006245a9e11..453c2ac9065 100644 --- a/staging/src/k8s.io/streaming/go.mod +++ b/staging/src/k8s.io/streaming/go.mod @@ -2,25 +2,16 @@ module k8s.io/streaming -go 1.25.0 +go 1.26.0 -godebug default=go1.25 +godebug default=go1.26 require ( github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/moby/spdystream v0.5.0 - github.com/stretchr/testify v1.11.1 golang.org/x/net v0.49.0 k8s.io/klog/v2 v2.140.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/pretty v0.3.1 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) +require github.com/go-logr/logr v1.4.3 // indirect diff --git a/staging/src/k8s.io/streaming/go.sum b/staging/src/k8s.io/streaming/go.sum index 7686193d4c5..261a98c0c53 100644 --- a/staging/src/k8s.io/streaming/go.sum +++ b/staging/src/k8s.io/streaming/go.sum @@ -1,41 +1,16 @@ 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/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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -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.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -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/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= golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= 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/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/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= -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= diff --git a/staging/src/k8s.io/streaming/pkg/httpstream/spdy/roundtripper_test.go b/staging/src/k8s.io/streaming/pkg/httpstream/spdy/roundtripper_test.go index 5502c3f0d19..e3b9935e0fc 100644 --- a/staging/src/k8s.io/streaming/pkg/httpstream/spdy/roundtripper_test.go +++ b/staging/src/k8s.io/streaming/pkg/httpstream/spdy/roundtripper_test.go @@ -33,8 +33,6 @@ import ( "testing" "github.com/armon/go-socks5" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "k8s.io/streaming/pkg/httpstream" ) @@ -592,7 +590,9 @@ func TestProxierWithNoProxyCIDR(t *testing.T) { t.Setenv("no_proxy", "") expectedProxy, err := url.Parse("http://proxy.example:8080") - require.NoError(t, err) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } delegateCalls := 0 delegate := func(*http.Request) (*url.URL, error) { @@ -603,34 +603,70 @@ func TestProxierWithNoProxyCIDR(t *testing.T) { proxier := newProxierWithNoProxyCIDR(delegate) req, err := http.NewRequest(http.MethodGet, "https://10.1.2.3", nil) - require.NoError(t, err) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } proxyURL, err := proxier(req) - require.NoError(t, err) - require.Nil(t, proxyURL) - require.Equal(t, 0, delegateCalls) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if proxyURL != nil { + t.Fatalf("expected nil proxyURL, got %v", proxyURL) + } + if delegateCalls != 0 { + t.Fatalf("expected delegateCalls=0, got %d", delegateCalls) + } req, err = http.NewRequest(http.MethodGet, "https://[2001:db8::1]", nil) - require.NoError(t, err) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } proxyURL, err = proxier(req) - require.NoError(t, err) - require.Nil(t, proxyURL) - require.Equal(t, 0, delegateCalls) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if proxyURL != nil { + t.Fatalf("expected nil proxyURL, got %v", proxyURL) + } + if delegateCalls != 0 { + t.Fatalf("expected delegateCalls=0, got %d", delegateCalls) + } req, err = http.NewRequest(http.MethodGet, "https://192.168.1.10", nil) - require.NoError(t, err) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } proxyURL, err = proxier(req) - require.NoError(t, err) - require.NotNil(t, proxyURL) - require.Equal(t, expectedProxy.String(), proxyURL.String()) - require.Equal(t, 1, delegateCalls) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if proxyURL == nil { + t.Fatalf("expected non-nil proxyURL") + } + if proxyURL.String() != expectedProxy.String() { + t.Fatalf("expected proxyURL %q, got %q", expectedProxy.String(), proxyURL.String()) + } + if delegateCalls != 1 { + t.Fatalf("expected delegateCalls=1, got %d", delegateCalls) + } req, err = http.NewRequest(http.MethodGet, "https://example.com", nil) - require.NoError(t, err) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } proxyURL, err = proxier(req) - require.NoError(t, err) - require.NotNil(t, proxyURL) - require.Equal(t, expectedProxy.String(), proxyURL.String()) - require.Equal(t, 2, delegateCalls) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if proxyURL == nil { + t.Fatalf("expected non-nil proxyURL") + } + if proxyURL.String() != expectedProxy.String() { + t.Fatalf("expected proxyURL %q, got %q", expectedProxy.String(), proxyURL.String()) + } + if delegateCalls != 2 { + t.Fatalf("expected delegateCalls=2, got %d", delegateCalls) + } } type Interceptor struct { @@ -911,13 +947,17 @@ func TestRoundTripPassesContextToDialer(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) - require.NoError(t, err) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } spdyTransport, err := NewRoundTripper(&tls.Config{}) if err != nil { t.Fatalf("error creating SpdyRoundTripper: %v", err) } _, err = spdyTransport.Dial(req) - assert.EqualError(t, err, "dial tcp 127.0.0.1:1233: operation was canceled") + if err == nil || err.Error() != "dial tcp 127.0.0.1:1233: operation was canceled" { + t.Errorf("expected error %q, got %v", "dial tcp 127.0.0.1:1233: operation was canceled", err) + } }) } } diff --git a/staging/src/k8s.io/streaming/pkg/httpstream/wsstream/conn_test.go b/staging/src/k8s.io/streaming/pkg/httpstream/wsstream/conn_test.go index 252b91758f5..a4122a62579 100644 --- a/staging/src/k8s.io/streaming/pkg/httpstream/wsstream/conn_test.go +++ b/staging/src/k8s.io/streaming/pkg/httpstream/wsstream/conn_test.go @@ -25,8 +25,6 @@ import ( "sync" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "golang.org/x/net/websocket" ) @@ -370,12 +368,16 @@ func TestIsWebSocketRequestWithStreamCloseProtocol(t *testing.T) { for name, test := range tests { req, err := http.NewRequest(http.MethodGet, "http://www.example.com/", nil) - require.NoError(t, err) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } for key, value := range test.headers { req.Header.Add(key, value) } actual := IsWebSocketRequestWithStreamCloseProtocol(req) - assert.Equal(t, test.expected, actual, "%s: expected (%t), got (%t)", name, test.expected, actual) + if actual != test.expected { + t.Errorf("%s: expected (%t), got (%t)", name, test.expected, actual) + } } } @@ -412,7 +414,8 @@ func TestProtocolSupportsStreamClose(t *testing.T) { for name, test := range tests { actual := protocolSupportsStreamClose(test.protocol) - assert.Equal(t, test.expected, actual, - "%s: expected (%t), got (%t)", name, test.expected, actual) + if actual != test.expected { + t.Errorf("%s: expected (%t), got (%t)", name, test.expected, actual) + } } } diff --git a/vendor/modules.txt b/vendor/modules.txt index cce0a452d87..e2a9f8a3542 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1089,7 +1089,7 @@ gopkg.in/yaml.v3 # k8s.io/cri-client v0.0.0 => ./staging/src/k8s.io/cri-client ## explicit; go 1.26.0 # k8s.io/cri-streaming v0.0.0 => ./staging/src/k8s.io/cri-streaming -## explicit; go 1.25.0 +## explicit; go 1.26.0 # k8s.io/csi-translation-lib v0.0.0 => ./staging/src/k8s.io/csi-translation-lib ## explicit; go 1.26.0 # k8s.io/dynamic-resource-allocation v0.0.0 => ./staging/src/k8s.io/dynamic-resource-allocation @@ -1176,7 +1176,7 @@ k8s.io/kube-openapi/pkg/validation/validate # k8s.io/sample-apiserver v0.0.0 => ./staging/src/k8s.io/sample-apiserver ## explicit; go 1.26.0 # k8s.io/streaming v0.0.0 => ./staging/src/k8s.io/streaming -## explicit; go 1.25.0 +## explicit; go 1.26.0 # k8s.io/system-validators v1.12.1 ## explicit; go 1.16 k8s.io/system-validators/validators From 66f49068b32917afcf77078cac44e60bd394adf3 Mon Sep 17 00:00:00 2001 From: Davanum Srinivas Date: Fri, 6 Mar 2026 13:09:30 -0500 Subject: [PATCH 3/5] fixup: remove unused klog import; drop rogpeppe from apimachinery Signed-off-by: Davanum Srinivas --- pkg/kubelet/kubelet_pods_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/kubelet/kubelet_pods_test.go b/pkg/kubelet/kubelet_pods_test.go index a61d5136dd1..e67723354b2 100644 --- a/pkg/kubelet/kubelet_pods_test.go +++ b/pkg/kubelet/kubelet_pods_test.go @@ -51,7 +51,6 @@ import ( runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" "k8s.io/cri-streaming/pkg/streaming/portforward" "k8s.io/cri-streaming/pkg/streaming/remotecommand" - "k8s.io/klog/v2" _ "k8s.io/kubernetes/pkg/apis/core/install" "k8s.io/kubernetes/pkg/features" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" From fdfffefe48505aa785cecabc65efac5d7284123a Mon Sep 17 00:00:00 2001 From: Davanum Srinivas Date: Fri, 6 Mar 2026 15:08:10 -0500 Subject: [PATCH 4/5] staging: update OWNERS for cri-streaming and streaming modules Signed-off-by: Davanum Srinivas --- staging/src/k8s.io/cri-streaming/OWNERS | 19 ++++++++++++------- staging/src/k8s.io/streaming/OWNERS | 17 +++++++++-------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/staging/src/k8s.io/cri-streaming/OWNERS b/staging/src/k8s.io/cri-streaming/OWNERS index dbfc437c636..7215aca6b29 100644 --- a/staging/src/k8s.io/cri-streaming/OWNERS +++ b/staging/src/k8s.io/cri-streaming/OWNERS @@ -1,13 +1,18 @@ # See the OWNERS docs at https://go.k8s.io/owners approvers: - - sig-node-approvers - - sttts - - luxas - - mtaufen + - dims + - mikebrow + - saschagrunert + - aojea + - seans3 + - liggitt reviewers: - - sig-node-reviewers - - luxas - - sttts + - dims + - mikebrow + - saschagrunert + - aojea + - seans3 + - liggitt labels: - sig/node diff --git a/staging/src/k8s.io/streaming/OWNERS b/staging/src/k8s.io/streaming/OWNERS index dbfc437c636..4f42bd4d629 100644 --- a/staging/src/k8s.io/streaming/OWNERS +++ b/staging/src/k8s.io/streaming/OWNERS @@ -1,13 +1,14 @@ # See the OWNERS docs at https://go.k8s.io/owners approvers: - - sig-node-approvers - - sttts - - luxas - - mtaufen + - aojea + - dims + - liggitt + - seans3 reviewers: - - sig-node-reviewers - - luxas - - sttts + - aojea + - dims + - liggitt + - seans3 labels: - - sig/node + - sig/api-machinery From b3c1418312d30cbcf09ff59da946886a57ec0608 Mon Sep 17 00:00:00 2001 From: Davanum Srinivas Date: Thu, 12 Mar 2026 11:59:15 -0400 Subject: [PATCH 5/5] staging/publishing: order streaming before dependents Signed-off-by: Davanum Srinivas --- staging/publishing/rules.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/staging/publishing/rules.yaml b/staging/publishing/rules.yaml index d78acca1a36..4f9dce0696b 100644 --- a/staging/publishing/rules.yaml +++ b/staging/publishing/rules.yaml @@ -1,4 +1,12 @@ rules: +- destination: streaming + branches: + - name: master + source: + branch: master + dirs: + - staging/src/k8s.io/streaming + library: true - destination: apimachinery branches: - name: master @@ -1315,14 +1323,6 @@ rules: dirs: - staging/src/k8s.io/cri-streaming library: true -- destination: streaming - branches: - - name: master - source: - branch: master - dirs: - - staging/src/k8s.io/streaming - library: true - destination: kubelet branches: - name: master