Commit 77b6b126 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #48641 from smarterclayton/refactor_exec

Automatic merge from submit-queue (batch tested with PRs 48594, 47042, 48801, 48641, 48243) Prepare to introduce websockets for exec and portforward Refactor the code in remotecommand to better represent the structure of what is common between portforward and exec. Ref #48633
parents 5ed87346 cf026a33
...@@ -436,6 +436,7 @@ staging/src/k8s.io/client-go/rest/watch ...@@ -436,6 +436,7 @@ staging/src/k8s.io/client-go/rest/watch
staging/src/k8s.io/client-go/tools/auth staging/src/k8s.io/client-go/tools/auth
staging/src/k8s.io/client-go/tools/metrics staging/src/k8s.io/client-go/tools/metrics
staging/src/k8s.io/client-go/tools/remotecommand staging/src/k8s.io/client-go/tools/remotecommand
staging/src/k8s.io/client-go/transport/spdy
staging/src/k8s.io/client-go/util/cert staging/src/k8s.io/client-go/util/cert
staging/src/k8s.io/client-go/util/homedir staging/src/k8s.io/client-go/util/homedir
staging/src/k8s.io/client-go/util/workqueue staging/src/k8s.io/client-go/util/workqueue
......
...@@ -40,6 +40,7 @@ go_test( ...@@ -40,6 +40,7 @@ go_test(
"//vendor/k8s.io/client-go/tools/cache:go_default_library", "//vendor/k8s.io/client-go/tools/cache:go_default_library",
"//vendor/k8s.io/client-go/tools/portforward:go_default_library", "//vendor/k8s.io/client-go/tools/portforward:go_default_library",
"//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library",
"//vendor/k8s.io/client-go/transport/spdy:go_default_library",
"//vendor/k8s.io/client-go/util/testing:go_default_library", "//vendor/k8s.io/client-go/util/testing:go_default_library",
], ],
) )
......
...@@ -33,7 +33,7 @@ import ( ...@@ -33,7 +33,7 @@ import (
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
. "k8s.io/client-go/tools/portforward" . "k8s.io/client-go/tools/portforward"
"k8s.io/client-go/tools/remotecommand" "k8s.io/client-go/transport/spdy"
"k8s.io/kubernetes/pkg/kubelet/server/portforward" "k8s.io/kubernetes/pkg/kubelet/server/portforward"
) )
...@@ -131,16 +131,17 @@ func TestForwardPorts(t *testing.T) { ...@@ -131,16 +131,17 @@ func TestForwardPorts(t *testing.T) {
for testName, test := range tests { for testName, test := range tests {
server := httptest.NewServer(fakePortForwardServer(t, testName, test.serverSends, test.clientSends)) server := httptest.NewServer(fakePortForwardServer(t, testName, test.serverSends, test.clientSends))
url, _ := url.Parse(server.URL) transport, upgrader, err := spdy.RoundTripperFor(&restclient.Config{})
exec, err := remotecommand.NewExecutor(&restclient.Config{}, "POST", url)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
url, _ := url.Parse(server.URL)
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", url)
stopChan := make(chan struct{}, 1) stopChan := make(chan struct{}, 1)
readyChan := make(chan struct{}) readyChan := make(chan struct{})
pf, err := New(exec, test.ports, stopChan, readyChan, os.Stdout, os.Stderr) pf, err := New(dialer, test.ports, stopChan, readyChan, os.Stdout, os.Stderr)
if err != nil { if err != nil {
t.Fatalf("%s: unexpected error calling New: %v", testName, err) t.Fatalf("%s: unexpected error calling New: %v", testName, err)
} }
...@@ -201,17 +202,18 @@ func TestForwardPortsReturnsErrorWhenAllBindsFailed(t *testing.T) { ...@@ -201,17 +202,18 @@ func TestForwardPortsReturnsErrorWhenAllBindsFailed(t *testing.T) {
server := httptest.NewServer(fakePortForwardServer(t, "allBindsFailed", nil, nil)) server := httptest.NewServer(fakePortForwardServer(t, "allBindsFailed", nil, nil))
defer server.Close() defer server.Close()
url, _ := url.Parse(server.URL) transport, upgrader, err := spdy.RoundTripperFor(&restclient.Config{})
exec, err := remotecommand.NewExecutor(&restclient.Config{}, "POST", url)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
url, _ := url.Parse(server.URL)
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", url)
stopChan1 := make(chan struct{}, 1) stopChan1 := make(chan struct{}, 1)
defer close(stopChan1) defer close(stopChan1)
readyChan1 := make(chan struct{}) readyChan1 := make(chan struct{})
pf1, err := New(exec, []string{"5555"}, stopChan1, readyChan1, os.Stdout, os.Stderr) pf1, err := New(dialer, []string{"5555"}, stopChan1, readyChan1, os.Stdout, os.Stderr)
if err != nil { if err != nil {
t.Fatalf("error creating pf1: %v", err) t.Fatalf("error creating pf1: %v", err)
} }
...@@ -220,7 +222,7 @@ func TestForwardPortsReturnsErrorWhenAllBindsFailed(t *testing.T) { ...@@ -220,7 +222,7 @@ func TestForwardPortsReturnsErrorWhenAllBindsFailed(t *testing.T) {
stopChan2 := make(chan struct{}, 1) stopChan2 := make(chan struct{}, 1)
readyChan2 := make(chan struct{}) readyChan2 := make(chan struct{})
pf2, err := New(exec, []string{"5555"}, stopChan2, readyChan2, os.Stdout, os.Stderr) pf2, err := New(dialer, []string{"5555"}, stopChan2, readyChan2, os.Stdout, os.Stderr)
if err != nil { if err != nil {
t.Fatalf("error creating pf2: %v", err) t.Fatalf("error creating pf2: %v", err)
} }
......
...@@ -37,6 +37,7 @@ import ( ...@@ -37,6 +37,7 @@ import (
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand" remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
remoteclient "k8s.io/client-go/tools/remotecommand" remoteclient "k8s.io/client-go/tools/remotecommand"
"k8s.io/client-go/transport/spdy"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi" "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand" "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
...@@ -255,17 +256,16 @@ func TestStream(t *testing.T) { ...@@ -255,17 +256,16 @@ func TestStream(t *testing.T) {
conf := &restclient.Config{ conf := &restclient.Config{
Host: server.URL, Host: server.URL,
} }
e, err := remoteclient.NewExecutor(conf, "POST", req.URL()) e, err := remoteclient.NewSPDYExecutorForProtocols(conf, "POST", req.URL(), testCase.ClientProtocols...)
if err != nil { if err != nil {
t.Errorf("%s: unexpected error: %v", name, err) t.Errorf("%s: unexpected error: %v", name, err)
continue continue
} }
err = e.Stream(remoteclient.StreamOptions{ err = e.Stream(remoteclient.StreamOptions{
SupportedProtocols: testCase.ClientProtocols, Stdin: streamIn,
Stdin: streamIn, Stdout: streamOut,
Stdout: streamOut, Stderr: streamErr,
Stderr: streamErr, Tty: testCase.Tty,
Tty: testCase.Tty,
}) })
hasErr := err != nil hasErr := err != nil
...@@ -311,11 +311,13 @@ type fakeUpgrader struct { ...@@ -311,11 +311,13 @@ type fakeUpgrader struct {
conn httpstream.Connection conn httpstream.Connection
err, connErr error err, connErr error
checkResponse bool checkResponse bool
called bool
t *testing.T t *testing.T
} }
func (u *fakeUpgrader) RoundTrip(req *http.Request) (*http.Response, error) { func (u *fakeUpgrader) RoundTrip(req *http.Request) (*http.Response, error) {
u.called = true
u.req = req u.req = req
return u.resp, u.err return u.resp, u.err
} }
...@@ -344,27 +346,16 @@ func TestDial(t *testing.T) { ...@@ -344,27 +346,16 @@ func TestDial(t *testing.T) {
Body: ioutil.NopCloser(&bytes.Buffer{}), Body: ioutil.NopCloser(&bytes.Buffer{}),
}, },
} }
var called bool dialer := spdy.NewDialer(upgrader, &http.Client{Transport: upgrader}, "POST", &url.URL{Host: "something.com", Scheme: "https"})
testFn := func(rt http.RoundTripper) http.RoundTripper { conn, protocol, err := dialer.Dial("protocol1")
if rt != upgrader {
t.Fatalf("unexpected round tripper: %#v", rt)
}
called = true
return rt
}
exec, err := remoteclient.NewStreamExecutor(upgrader, testFn, "POST", &url.URL{Host: "something.com", Scheme: "https"})
if err != nil {
t.Fatal(err)
}
conn, protocol, err := exec.Dial("protocol1")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if conn != upgrader.conn { if conn != upgrader.conn {
t.Errorf("unexpected connection: %#v", conn) t.Errorf("unexpected connection: %#v", conn)
} }
if !called { if !upgrader.called {
t.Errorf("wrapper not called") t.Errorf("request not called")
} }
_ = protocol _ = protocol
} }
...@@ -131,7 +131,6 @@ go_library( ...@@ -131,7 +131,6 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/jsonmergepatch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/jsonmergepatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/mergepatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/remotecommand:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
...@@ -145,6 +144,7 @@ go_library( ...@@ -145,6 +144,7 @@ go_library(
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library", "//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
"//vendor/k8s.io/client-go/tools/portforward:go_default_library", "//vendor/k8s.io/client-go/tools/portforward:go_default_library",
"//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library",
"//vendor/k8s.io/client-go/transport/spdy:go_default_library",
], ],
) )
......
...@@ -28,7 +28,6 @@ import ( ...@@ -28,7 +28,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors"
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand" "k8s.io/client-go/tools/remotecommand"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
...@@ -97,17 +96,16 @@ type RemoteAttach interface { ...@@ -97,17 +96,16 @@ type RemoteAttach interface {
type DefaultRemoteAttach struct{} type DefaultRemoteAttach struct{}
func (*DefaultRemoteAttach) Attach(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error { func (*DefaultRemoteAttach) Attach(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error {
exec, err := remotecommand.NewExecutor(config, method, url) exec, err := remotecommand.NewSPDYExecutor(config, method, url)
if err != nil { if err != nil {
return err return err
} }
return exec.Stream(remotecommand.StreamOptions{ return exec.Stream(remotecommand.StreamOptions{
SupportedProtocols: remotecommandconsts.SupportedStreamingProtocols, Stdin: stdin,
Stdin: stdin, Stdout: stdout,
Stdout: stdout, Stderr: stderr,
Stderr: stderr, Tty: tty,
Tty: tty, TerminalSizeQueue: terminalSizeQueue,
TerminalSizeQueue: terminalSizeQueue,
}) })
} }
......
...@@ -25,7 +25,6 @@ import ( ...@@ -25,7 +25,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand" "k8s.io/client-go/tools/remotecommand"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
...@@ -101,17 +100,16 @@ type RemoteExecutor interface { ...@@ -101,17 +100,16 @@ type RemoteExecutor interface {
type DefaultRemoteExecutor struct{} type DefaultRemoteExecutor struct{}
func (*DefaultRemoteExecutor) Execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error { func (*DefaultRemoteExecutor) Execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error {
exec, err := remotecommand.NewExecutor(config, method, url) exec, err := remotecommand.NewSPDYExecutor(config, method, url)
if err != nil { if err != nil {
return err return err
} }
return exec.Stream(remotecommand.StreamOptions{ return exec.Stream(remotecommand.StreamOptions{
SupportedProtocols: remotecommandconsts.SupportedStreamingProtocols, Stdin: stdin,
Stdin: stdin, Stdout: stdout,
Stdout: stdout, Stderr: stderr,
Stderr: stderr, Tty: tty,
Tty: tty, TerminalSizeQueue: terminalSizeQueue,
TerminalSizeQueue: terminalSizeQueue,
}) })
} }
......
...@@ -19,6 +19,7 @@ package cmd ...@@ -19,6 +19,7 @@ package cmd
import ( import (
"fmt" "fmt"
"io" "io"
"net/http"
"net/url" "net/url"
"os" "os"
"os/signal" "os/signal"
...@@ -28,7 +29,7 @@ import ( ...@@ -28,7 +29,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/portforward" "k8s.io/client-go/tools/portforward"
"k8s.io/client-go/tools/remotecommand" "k8s.io/client-go/transport/spdy"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
...@@ -102,10 +103,11 @@ type defaultPortForwarder struct { ...@@ -102,10 +103,11 @@ type defaultPortForwarder struct {
} }
func (f *defaultPortForwarder) ForwardPorts(method string, url *url.URL, opts PortForwardOptions) error { func (f *defaultPortForwarder) ForwardPorts(method string, url *url.URL, opts PortForwardOptions) error {
dialer, err := remotecommand.NewExecutor(opts.Config, method, url) transport, upgrader, err := spdy.RoundTripperFor(opts.Config)
if err != nil { if err != nil {
return err return err
} }
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, method, url)
fw, err := portforward.New(dialer, opts.Ports, opts.StopChannel, opts.ReadyChannel, f.cmdOut, f.cmdErr) fw, err := portforward.New(dialer, opts.Ports, opts.StopChannel, opts.ReadyChannel, f.cmdOut, f.cmdErr)
if err != nil { if err != nil {
return err return err
......
...@@ -45,9 +45,9 @@ go_test( ...@@ -45,9 +45,9 @@ go_test(
"//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/github.com/stretchr/testify/require:go_default_library", "//vendor/github.com/stretchr/testify/require:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/remotecommand:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/tools/remotecommand:go_default_library", "//vendor/k8s.io/client-go/tools/remotecommand:go_default_library",
"//vendor/k8s.io/client-go/transport/spdy:go_default_library",
], ],
) )
......
...@@ -30,9 +30,9 @@ import ( ...@@ -30,9 +30,9 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand" "k8s.io/client-go/tools/remotecommand"
"k8s.io/client-go/transport/spdy"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
kubeletportforward "k8s.io/kubernetes/pkg/kubelet/server/portforward" kubeletportforward "k8s.io/kubernetes/pkg/kubelet/server/portforward"
...@@ -237,9 +237,10 @@ func TestServePortForward(t *testing.T) { ...@@ -237,9 +237,10 @@ func TestServePortForward(t *testing.T) {
reqURL, err := url.Parse(resp.Url) reqURL, err := url.Parse(resp.Url)
require.NoError(t, err) require.NoError(t, err)
exec, err := remotecommand.NewExecutor(&restclient.Config{}, "POST", reqURL) transport, upgrader, err := spdy.RoundTripperFor(&restclient.Config{})
require.NoError(t, err) require.NoError(t, err)
streamConn, _, err := exec.Dial(kubeletportforward.ProtocolV1Name) dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", reqURL)
streamConn, _, err := dialer.Dial(kubeletportforward.ProtocolV1Name)
require.NoError(t, err) require.NoError(t, err)
defer streamConn.Close() defer streamConn.Close()
...@@ -297,15 +298,14 @@ func runRemoteCommandTest(t *testing.T, commandType string) { ...@@ -297,15 +298,14 @@ func runRemoteCommandTest(t *testing.T, commandType string) {
go func() { go func() {
defer wg.Done() defer wg.Done()
exec, err := remotecommand.NewExecutor(&restclient.Config{}, "POST", reqURL) exec, err := remotecommand.NewSPDYExecutor(&restclient.Config{}, "POST", reqURL)
require.NoError(t, err) require.NoError(t, err)
opts := remotecommand.StreamOptions{ opts := remotecommand.StreamOptions{
SupportedProtocols: remotecommandconsts.SupportedStreamingProtocols, Stdin: stdinR,
Stdin: stdinR, Stdout: stdoutW,
Stdout: stdoutW, Stderr: stderrW,
Stderr: stderrW, Tty: false,
Tty: false,
} }
require.NoError(t, exec.Stream(opts)) require.NoError(t, exec.Stream(opts))
}() }()
......
...@@ -41,11 +41,11 @@ go_library( ...@@ -41,11 +41,11 @@ go_library(
"//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/httpstream:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/httpstream:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/remotecommand:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/remotecommand:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library", "//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/client-go/transport:go_default_library", "//vendor/k8s.io/client-go/transport:go_default_library",
"//vendor/k8s.io/client-go/transport/spdy:go_default_library",
"//vendor/k8s.io/client-go/util/exec:go_default_library", "//vendor/k8s.io/client-go/util/exec:go_default_library",
], ],
) )
...@@ -25,22 +25,21 @@ import ( ...@@ -25,22 +25,21 @@ import (
"github.com/golang/glog" "github.com/golang/glog"
"k8s.io/apimachinery/pkg/util/httpstream" "k8s.io/apimachinery/pkg/util/httpstream"
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
"k8s.io/apimachinery/pkg/util/remotecommand" "k8s.io/apimachinery/pkg/util/remotecommand"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/client-go/transport" "k8s.io/client-go/transport"
spdy "k8s.io/client-go/transport/spdy"
) )
// StreamOptions holds information pertaining to the current streaming session: supported stream // StreamOptions holds information pertaining to the current streaming session: supported stream
// protocols, input/output streams, if the client is requesting a TTY, and a terminal size queue to // protocols, input/output streams, if the client is requesting a TTY, and a terminal size queue to
// support terminal resizing. // support terminal resizing.
type StreamOptions struct { type StreamOptions struct {
SupportedProtocols []string Stdin io.Reader
Stdin io.Reader Stdout io.Writer
Stdout io.Writer Stderr io.Writer
Stderr io.Writer Tty bool
Tty bool TerminalSizeQueue TerminalSizeQueue
TerminalSizeQueue TerminalSizeQueue
} }
// Executor is an interface for transporting shell-style streams. // Executor is an interface for transporting shell-style streams.
...@@ -52,107 +51,68 @@ type Executor interface { ...@@ -52,107 +51,68 @@ type Executor interface {
Stream(options StreamOptions) error Stream(options StreamOptions) error
} }
// StreamExecutor supports the ability to dial an httpstream connection and the ability to type streamCreator interface {
// run a command line stream protocol over that dialer. CreateStream(headers http.Header) (httpstream.Stream, error)
type StreamExecutor interface { }
Executor
httpstream.Dialer type streamProtocolHandler interface {
stream(conn streamCreator) error
} }
// streamExecutor handles transporting standard shell streams over an httpstream connection. // streamExecutor handles transporting standard shell streams over an httpstream connection.
type streamExecutor struct { type streamExecutor struct {
upgrader httpstream.UpgradeRoundTripper upgrader spdy.Upgrader
transport http.RoundTripper transport http.RoundTripper
method string method string
url *url.URL url *url.URL
protocols []string
} }
// NewExecutor connects to the provided server and upgrades the connection to // NewSPDYExecutor connects to the provided server and upgrades the connection to
// multiplexed bidirectional streams. The current implementation uses SPDY, // multiplexed bidirectional streams.
// but this could be replaced with HTTP/2 once it's available, or something else. func NewSPDYExecutor(config *restclient.Config, method string, url *url.URL) (Executor, error) {
// TODO: the common code between this and portforward could be abstracted. return NewSPDYExecutorForProtocols(
func NewExecutor(config *restclient.Config, method string, url *url.URL) (StreamExecutor, error) { config, method, url,
tlsConfig, err := restclient.TLSConfigFor(config) remotecommand.StreamProtocolV4Name,
if err != nil { remotecommand.StreamProtocolV3Name,
return nil, err remotecommand.StreamProtocolV2Name,
} remotecommand.StreamProtocolV1Name,
)
}
upgradeRoundTripper := spdy.NewRoundTripper(tlsConfig, true) // NewSPDYExecutorForProtocols connects to the provided server and upgrades the connection to
wrapper, err := restclient.HTTPWrappersForConfig(config, upgradeRoundTripper) // multiplexed bidirectional streams using only the provided protocols. Exposed for testing, most
// callers should use NewSPDYExecutor.
func NewSPDYExecutorForProtocols(config *restclient.Config, method string, url *url.URL, protocols ...string) (Executor, error) {
wrapper, upgradeRoundTripper, err := spdy.RoundTripperFor(config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
wrapper = transport.DebugWrappers(wrapper)
return &streamExecutor{ return &streamExecutor{
upgrader: upgradeRoundTripper, upgrader: upgradeRoundTripper,
transport: wrapper, transport: wrapper,
method: method, method: method,
url: url, url: url,
protocols: protocols,
}, nil }, nil
} }
// NewStreamExecutor upgrades the request so that it supports multiplexed bidirectional // Stream opens a protocol streamer to the server and streams until a client closes
// streams. This method takes a stream upgrader and an optional function that is invoked // the connection or the server disconnects.
// to wrap the round tripper. This method may be used by clients that are lower level than func (e *streamExecutor) Stream(options StreamOptions) error {
// Kubernetes clients or need to provide their own upgrade round tripper.
func NewStreamExecutor(upgrader httpstream.UpgradeRoundTripper, fn func(http.RoundTripper) http.RoundTripper, method string, url *url.URL) (StreamExecutor, error) {
rt := http.RoundTripper(upgrader)
if fn != nil {
rt = fn(rt)
}
return &streamExecutor{
upgrader: upgrader,
transport: rt,
method: method,
url: url,
}, nil
}
// Dial opens a connection to a remote server and attempts to negotiate a SPDY
// connection. Upon success, it returns the connection and the protocol
// selected by the server.
func (e *streamExecutor) Dial(protocols ...string) (httpstream.Connection, string, error) {
rt := transport.DebugWrappers(e.transport)
// TODO the client probably shouldn't be created here, as it doesn't allow
// flexibility to allow callers to configure it.
client := &http.Client{Transport: rt}
req, err := http.NewRequest(e.method, e.url.String(), nil) req, err := http.NewRequest(e.method, e.url.String(), nil)
if err != nil { if err != nil {
return nil, "", fmt.Errorf("error creating request: %v", err) return fmt.Errorf("error creating request: %v", err)
}
for i := range protocols {
req.Header.Add(httpstream.HeaderProtocolVersion, protocols[i])
}
resp, err := client.Do(req)
if err != nil {
return nil, "", fmt.Errorf("error sending request: %v", err)
} }
defer resp.Body.Close()
conn, err := e.upgrader.NewConnection(resp) conn, protocol, err := spdy.Negotiate(
if err != nil { e.upgrader,
return nil, "", err &http.Client{Transport: e.transport},
} req,
e.protocols...,
return conn, resp.Header.Get(httpstream.HeaderProtocolVersion), nil )
}
type streamCreator interface {
CreateStream(headers http.Header) (httpstream.Stream, error)
}
type streamProtocolHandler interface {
stream(conn streamCreator) error
}
// Stream opens a protocol streamer to the server and streams until a client closes
// the connection or the server disconnects.
func (e *streamExecutor) Stream(options StreamOptions) error {
conn, protocol, err := e.Dial(options.SupportedProtocols...)
if err != nil { if err != nil {
return err return err
} }
......
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["spdy.go"],
tags = ["automanaged"],
deps = [
"//vendor/k8s.io/apimachinery/pkg/util/httpstream:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
],
)
/*
Copyright 2017 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 (
"fmt"
"net/http"
"net/url"
"k8s.io/apimachinery/pkg/util/httpstream"
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
restclient "k8s.io/client-go/rest"
)
// Upgrader validates a response from the server after a SPDY upgrade.
type Upgrader interface {
// NewConnection validates the response and creates a new Connection.
NewConnection(resp *http.Response) (httpstream.Connection, error)
}
// RoundTripperFor returns a round tripper and upgrader to use with SPDY.
func RoundTripperFor(config *restclient.Config) (http.RoundTripper, Upgrader, error) {
tlsConfig, err := restclient.TLSConfigFor(config)
if err != nil {
return nil, nil, err
}
upgradeRoundTripper := spdy.NewRoundTripper(tlsConfig, true)
wrapper, err := restclient.HTTPWrappersForConfig(config, upgradeRoundTripper)
if err != nil {
return nil, nil, err
}
return wrapper, upgradeRoundTripper, nil
}
// dialer implements the httpstream.Dialer interface.
type dialer struct {
client *http.Client
upgrader Upgrader
method string
url *url.URL
}
var _ httpstream.Dialer = &dialer{}
// NewDialer will create a dialer that connects to the provided URL and upgrades the connection to SPDY.
func NewDialer(upgrader Upgrader, client *http.Client, method string, url *url.URL) httpstream.Dialer {
return &dialer{
client: client,
upgrader: upgrader,
method: method,
url: url,
}
}
func (d *dialer) Dial(protocols ...string) (httpstream.Connection, string, error) {
req, err := http.NewRequest(d.method, d.url.String(), nil)
if err != nil {
return nil, "", fmt.Errorf("error creating request: %v", err)
}
return Negotiate(d.upgrader, d.client, req, protocols...)
}
// Negotiate opens a connection to a remote server and attempts to negotiate
// a SPDY connection. Upon success, it returns the connection and the protocol selected by
// the server. The client transport must use the upgradeRoundTripper - see RoundTripperFor.
func Negotiate(upgrader Upgrader, client *http.Client, req *http.Request, protocols ...string) (httpstream.Connection, string, error) {
for i := range protocols {
req.Header.Add(httpstream.HeaderProtocolVersion, protocols[i])
}
resp, err := client.Do(req)
if err != nil {
return nil, "", fmt.Errorf("error sending request: %v", err)
}
defer resp.Body.Close()
conn, err := upgrader.NewConnection(resp)
if err != nil {
return nil, "", err
}
return conn, resp.Header.Get(httpstream.HeaderProtocolVersion), nil
}
...@@ -123,7 +123,6 @@ go_library( ...@@ -123,7 +123,6 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/rand:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/remotecommand:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
......
...@@ -24,7 +24,6 @@ import ( ...@@ -24,7 +24,6 @@ import (
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
remocommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
restclient "k8s.io/client-go/rest" restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand" "k8s.io/client-go/tools/remotecommand"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
...@@ -135,15 +134,14 @@ func (f *Framework) ExecShellInPodWithFullOutput(podName string, cmd string) (st ...@@ -135,15 +134,14 @@ func (f *Framework) ExecShellInPodWithFullOutput(podName string, cmd string) (st
} }
func execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool) error { func execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool) error {
exec, err := remotecommand.NewExecutor(config, method, url) exec, err := remotecommand.NewSPDYExecutor(config, method, url)
if err != nil { if err != nil {
return err return err
} }
return exec.Stream(remotecommand.StreamOptions{ return exec.Stream(remotecommand.StreamOptions{
SupportedProtocols: remocommandconsts.SupportedStreamingProtocols, Stdin: stdin,
Stdin: stdin, Stdout: stdout,
Stdout: stdout, Stderr: stderr,
Stderr: stderr, Tty: tty,
Tty: tty,
}) })
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment