diff --git a/tools/remotecommand/BUILD b/tools/remotecommand/BUILD index 66fc8010..1435e31e 100644 --- a/tools/remotecommand/BUILD +++ b/tools/remotecommand/BUILD @@ -41,11 +41,11 @@ go_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/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/runtime: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/spdy:go_default_library", "//vendor/k8s.io/client-go/util/exec:go_default_library", ], ) diff --git a/tools/remotecommand/remotecommand.go b/tools/remotecommand/remotecommand.go index b3d6ad0a..bcbe9fcd 100644 --- a/tools/remotecommand/remotecommand.go +++ b/tools/remotecommand/remotecommand.go @@ -25,10 +25,10 @@ import ( "github.com/golang/glog" "k8s.io/apimachinery/pkg/util/httpstream" - "k8s.io/apimachinery/pkg/util/httpstream/spdy" "k8s.io/apimachinery/pkg/util/remotecommand" restclient "k8s.io/client-go/rest" "k8s.io/client-go/transport" + spdy "k8s.io/client-go/transport/spdy" ) // StreamOptions holds information pertaining to the current streaming session: supported stream @@ -51,12 +51,6 @@ type Executor interface { Stream(options StreamOptions) error } -// SPDYUpgrader validates a response from the server after a SPDY upgrade. -type SPDYUpgrader interface { - // NewConnection validates the response and creates a new Connection. - NewConnection(resp *http.Response) (httpstream.Connection, error) -} - type streamCreator interface { CreateStream(headers http.Header) (httpstream.Stream, error) } @@ -67,17 +61,31 @@ type streamProtocolHandler interface { // streamExecutor handles transporting standard shell streams over an httpstream connection. type streamExecutor struct { - upgrader SPDYUpgrader + upgrader spdy.Upgrader transport http.RoundTripper - method string - url *url.URL + method string + url *url.URL + protocols []string } // NewSPDYExecutor connects to the provided server and upgrades the connection to // multiplexed bidirectional streams. func NewSPDYExecutor(config *restclient.Config, method string, url *url.URL) (Executor, error) { - wrapper, upgradeRoundTripper, err := SPDYRoundTripperFor(config) + return NewSPDYExecutorForProtocols( + config, method, url, + remotecommand.StreamProtocolV4Name, + remotecommand.StreamProtocolV3Name, + remotecommand.StreamProtocolV2Name, + remotecommand.StreamProtocolV1Name, + ) +} + +// NewSPDYExecutorForProtocols connects to the provided server and upgrades the connection to +// 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 { return nil, err } @@ -87,66 +95,10 @@ func NewSPDYExecutor(config *restclient.Config, method string, url *url.URL) (Ex transport: wrapper, method: method, url: url, + protocols: protocols, }, nil } -type spdyDialer struct { - client *http.Client - upgrader SPDYUpgrader - method string - url *url.URL -} - -func NewSPDYDialer(upgrader SPDYUpgrader, client *http.Client, method string, url *url.URL) (httpstream.Dialer, error) { - return &spdyDialer{ - client: client, - upgrader: upgrader, - method: method, - url: url, - }, nil -} - -func (d *spdyDialer) 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 NegotiateSPDYConnection(d.upgrader, d.client, req, protocols...) -} - -// SPDYRoundTripperFor returns a round tripper to use with SPDY. -func SPDYRoundTripperFor(config *restclient.Config) (http.RoundTripper, SPDYUpgrader, 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 -} - -// NegotiateSPDYConnection 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 SPDYRoundTripperFor. -func NegotiateSPDYConnection(upgrader SPDYUpgrader, 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 -} - // 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 { @@ -155,14 +107,11 @@ func (e *streamExecutor) Stream(options StreamOptions) error { return fmt.Errorf("error creating request: %v", err) } - conn, protocol, err := NegotiateSPDYConnection( + conn, protocol, err := spdy.Negotiate( e.upgrader, &http.Client{Transport: e.transport}, req, - remotecommand.StreamProtocolV4Name, - remotecommand.StreamProtocolV3Name, - remotecommand.StreamProtocolV2Name, - remotecommand.StreamProtocolV1Name, + e.protocols..., ) if err != nil { return err diff --git a/transport/spdy/BUILD b/transport/spdy/BUILD new file mode 100644 index 00000000..184f58fb --- /dev/null +++ b/transport/spdy/BUILD @@ -0,0 +1,19 @@ +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", + ], +) diff --git a/transport/spdy/spdy.go b/transport/spdy/spdy.go new file mode 100644 index 00000000..e0eb468b --- /dev/null +++ b/transport/spdy/spdy.go @@ -0,0 +1,94 @@ +/* +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 +}