2016-10-21 21:21:29 +00:00
|
|
|
/*
|
|
|
|
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 streaming
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/tls"
|
|
|
|
"errors"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2016-11-04 18:50:51 +00:00
|
|
|
"path"
|
2016-10-21 21:21:29 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
restful "github.com/emicklei/go-restful"
|
|
|
|
|
2016-11-04 18:50:51 +00:00
|
|
|
"k8s.io/kubernetes/pkg/api"
|
2016-10-21 21:21:29 +00:00
|
|
|
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
|
|
|
"k8s.io/kubernetes/pkg/kubelet/server/portforward"
|
|
|
|
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
|
|
|
"k8s.io/kubernetes/pkg/types"
|
|
|
|
"k8s.io/kubernetes/pkg/util/term"
|
|
|
|
)
|
|
|
|
|
|
|
|
// The library interface to serve the stream requests.
|
|
|
|
type Server interface {
|
|
|
|
http.Handler
|
|
|
|
|
2016-11-04 18:50:51 +00:00
|
|
|
// Get the serving URL for the requests.
|
2016-10-21 21:21:29 +00:00
|
|
|
// Requests must not be nil. Responses may be nil iff an error is returned.
|
|
|
|
GetExec(*runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error)
|
2016-11-09 00:36:11 +00:00
|
|
|
GetAttach(req *runtimeapi.AttachRequest) (*runtimeapi.AttachResponse, error)
|
2016-10-21 21:21:29 +00:00
|
|
|
GetPortForward(*runtimeapi.PortForwardRequest) (*runtimeapi.PortForwardResponse, error)
|
|
|
|
|
|
|
|
// Start the server.
|
|
|
|
// addr is the address to serve on (address:port) stayUp indicates whether the server should
|
|
|
|
// listen until Stop() is called, or automatically stop after all expected connections are
|
|
|
|
// closed. Calling Get{Exec,Attach,PortForward} increments the expected connection count.
|
|
|
|
// Function does not return until the server is stopped.
|
|
|
|
Start(stayUp bool) error
|
|
|
|
// Stop the server, and terminate any open connections.
|
|
|
|
Stop() error
|
|
|
|
}
|
|
|
|
|
|
|
|
// The interface to execute the commands and provide the streams.
|
|
|
|
type Runtime interface {
|
|
|
|
Exec(containerID string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan term.Size) error
|
2016-11-09 00:36:11 +00:00
|
|
|
Attach(containerID string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan term.Size) error
|
2016-10-21 21:21:29 +00:00
|
|
|
PortForward(podSandboxID string, port int32, stream io.ReadWriteCloser) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// Config defines the options used for running the stream server.
|
|
|
|
type Config struct {
|
|
|
|
// The host:port address the server will listen on.
|
|
|
|
Addr string
|
2016-11-04 18:50:51 +00:00
|
|
|
// The optional base URL for constructing streaming URLs. If empty, the baseURL will be
|
|
|
|
// constructed from the serve address.
|
|
|
|
BaseURL *url.URL
|
2016-10-21 21:21:29 +00:00
|
|
|
|
|
|
|
// How long to leave idle connections open for.
|
|
|
|
StreamIdleTimeout time.Duration
|
|
|
|
// How long to wait for clients to create streams. Only used for SPDY streaming.
|
|
|
|
StreamCreationTimeout time.Duration
|
|
|
|
|
|
|
|
// The streaming protocols the server supports (understands and permits). See
|
|
|
|
// k8s.io/kubernetes/pkg/kubelet/server/remotecommand/constants.go for available protocols.
|
|
|
|
// Only used for SPDY streaming.
|
|
|
|
SupportedProtocols []string
|
|
|
|
|
|
|
|
// The config for serving over TLS. If nil, TLS will not be used.
|
|
|
|
TLSConfig *tls.Config
|
|
|
|
}
|
|
|
|
|
|
|
|
// DefaultConfig provides default values for server Config. The DefaultConfig is partial, so
|
|
|
|
// some fields like Addr must still be provided.
|
|
|
|
var DefaultConfig = Config{
|
|
|
|
StreamIdleTimeout: 4 * time.Hour,
|
|
|
|
StreamCreationTimeout: remotecommand.DefaultStreamCreationTimeout,
|
|
|
|
SupportedProtocols: remotecommand.SupportedStreamingProtocols,
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(timstclair): Add auth(n/z) interface & handling.
|
|
|
|
func NewServer(config Config, runtime Runtime) (Server, error) {
|
|
|
|
s := &server{
|
|
|
|
config: config,
|
|
|
|
runtime: &criAdapter{runtime},
|
|
|
|
}
|
|
|
|
|
2016-11-04 18:50:51 +00:00
|
|
|
if s.config.BaseURL == nil {
|
|
|
|
s.config.BaseURL = &url.URL{
|
|
|
|
Scheme: "http",
|
|
|
|
Host: s.config.Addr,
|
|
|
|
}
|
|
|
|
if s.config.TLSConfig != nil {
|
|
|
|
s.config.BaseURL.Scheme = "https"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-21 21:21:29 +00:00
|
|
|
ws := &restful.WebService{}
|
|
|
|
endpoints := []struct {
|
|
|
|
path string
|
|
|
|
handler restful.RouteFunction
|
|
|
|
}{
|
|
|
|
{"/exec/{containerID}", s.serveExec},
|
|
|
|
{"/attach/{containerID}", s.serveAttach},
|
|
|
|
{"/portforward/{podSandboxID}", s.servePortForward},
|
|
|
|
}
|
2016-11-04 18:50:51 +00:00
|
|
|
// If serving relative to a base path, set that here.
|
|
|
|
pathPrefix := path.Dir(s.config.BaseURL.Path)
|
2016-10-21 21:21:29 +00:00
|
|
|
for _, e := range endpoints {
|
|
|
|
for _, method := range []string{"GET", "POST"} {
|
|
|
|
ws.Route(ws.
|
|
|
|
Method(method).
|
2016-11-04 18:50:51 +00:00
|
|
|
Path(path.Join(pathPrefix, e.path)).
|
2016-10-21 21:21:29 +00:00
|
|
|
To(e.handler))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
handler := restful.NewContainer()
|
|
|
|
handler.Add(ws)
|
|
|
|
s.handler = handler
|
|
|
|
|
|
|
|
return s, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type server struct {
|
|
|
|
config Config
|
|
|
|
runtime *criAdapter
|
|
|
|
handler http.Handler
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *server) GetExec(req *runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error) {
|
|
|
|
url := s.buildURL("exec", req.GetContainerId(), streamOpts{
|
|
|
|
stdin: req.GetStdin(),
|
|
|
|
stdout: true,
|
|
|
|
stderr: !req.GetTty(), // For TTY connections, both stderr is combined with stdout.
|
|
|
|
tty: req.GetTty(),
|
|
|
|
command: req.GetCmd(),
|
|
|
|
})
|
|
|
|
return &runtimeapi.ExecResponse{
|
|
|
|
Url: &url,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2016-11-09 00:36:11 +00:00
|
|
|
func (s *server) GetAttach(req *runtimeapi.AttachRequest) (*runtimeapi.AttachResponse, error) {
|
2016-10-21 21:21:29 +00:00
|
|
|
url := s.buildURL("attach", req.GetContainerId(), streamOpts{
|
|
|
|
stdin: req.GetStdin(),
|
|
|
|
stdout: true,
|
2016-11-09 00:36:11 +00:00
|
|
|
stderr: !req.GetTty(), // For TTY connections, both stderr is combined with stdout.
|
|
|
|
tty: req.GetTty(),
|
2016-10-21 21:21:29 +00:00
|
|
|
})
|
|
|
|
return &runtimeapi.AttachResponse{
|
|
|
|
Url: &url,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *server) GetPortForward(req *runtimeapi.PortForwardRequest) (*runtimeapi.PortForwardResponse, error) {
|
|
|
|
url := s.buildURL("portforward", req.GetPodSandboxId(), streamOpts{})
|
|
|
|
return &runtimeapi.PortForwardResponse{
|
|
|
|
Url: &url,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *server) Start(stayUp bool) error {
|
|
|
|
if !stayUp {
|
|
|
|
// TODO(timstclair): Implement this.
|
|
|
|
return errors.New("stayUp=false is not yet implemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
server := &http.Server{
|
|
|
|
Addr: s.config.Addr,
|
|
|
|
Handler: s.handler,
|
|
|
|
TLSConfig: s.config.TLSConfig,
|
|
|
|
}
|
|
|
|
if s.config.TLSConfig != nil {
|
|
|
|
return server.ListenAndServeTLS("", "") // Use certs from TLSConfig.
|
|
|
|
} else {
|
|
|
|
return server.ListenAndServe()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *server) Stop() error {
|
|
|
|
// TODO(timstclair): Implement this.
|
|
|
|
return errors.New("not yet implemented")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
s.handler.ServeHTTP(w, r)
|
|
|
|
}
|
|
|
|
|
|
|
|
type streamOpts struct {
|
|
|
|
stdin bool
|
|
|
|
stdout bool
|
|
|
|
stderr bool
|
|
|
|
tty bool
|
|
|
|
|
|
|
|
command []string
|
|
|
|
port []int32
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
|
|
|
urlParamStdin = api.ExecStdinParam
|
|
|
|
urlParamStdout = api.ExecStdoutParam
|
|
|
|
urlParamStderr = api.ExecStderrParam
|
|
|
|
urlParamTTY = api.ExecTTYParam
|
|
|
|
urlParamCommand = api.ExecCommandParamm
|
|
|
|
)
|
|
|
|
|
|
|
|
func (s *server) buildURL(method, id string, opts streamOpts) string {
|
2016-11-04 18:50:51 +00:00
|
|
|
loc := &url.URL{
|
|
|
|
Path: path.Join(method, id),
|
2016-10-21 21:21:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
query := url.Values{}
|
|
|
|
if opts.stdin {
|
|
|
|
query.Add(urlParamStdin, "1")
|
|
|
|
}
|
|
|
|
if opts.stdout {
|
|
|
|
query.Add(urlParamStdout, "1")
|
|
|
|
}
|
|
|
|
if opts.stderr {
|
|
|
|
query.Add(urlParamStderr, "1")
|
|
|
|
}
|
|
|
|
if opts.tty {
|
|
|
|
query.Add(urlParamTTY, "1")
|
|
|
|
}
|
|
|
|
for _, c := range opts.command {
|
|
|
|
query.Add(urlParamCommand, c)
|
|
|
|
}
|
|
|
|
loc.RawQuery = query.Encode()
|
|
|
|
|
2016-11-04 18:50:51 +00:00
|
|
|
return s.config.BaseURL.ResolveReference(loc).String()
|
2016-10-21 21:21:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *server) serveExec(req *restful.Request, resp *restful.Response) {
|
|
|
|
containerID := req.PathParameter("containerID")
|
|
|
|
if containerID == "" {
|
|
|
|
resp.WriteError(http.StatusBadRequest, errors.New("missing required containerID path parameter"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-12-09 02:54:02 +00:00
|
|
|
streamOpts, err := remotecommand.NewOptions(req.Request)
|
|
|
|
if err != nil {
|
|
|
|
resp.WriteError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
cmd := req.Request.URL.Query()[api.ExecCommandParamm]
|
|
|
|
|
2016-10-21 21:21:29 +00:00
|
|
|
remotecommand.ServeExec(
|
|
|
|
resp.ResponseWriter,
|
|
|
|
req.Request,
|
|
|
|
s.runtime,
|
|
|
|
"", // unused: podName
|
|
|
|
"", // unusued: podUID
|
|
|
|
containerID,
|
2016-12-09 02:54:02 +00:00
|
|
|
cmd,
|
|
|
|
streamOpts,
|
2016-10-21 21:21:29 +00:00
|
|
|
s.config.StreamIdleTimeout,
|
|
|
|
s.config.StreamCreationTimeout,
|
|
|
|
s.config.SupportedProtocols)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *server) serveAttach(req *restful.Request, resp *restful.Response) {
|
|
|
|
containerID := req.PathParameter("containerID")
|
|
|
|
if containerID == "" {
|
|
|
|
resp.WriteError(http.StatusBadRequest, errors.New("missing required containerID path parameter"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-12-09 02:54:02 +00:00
|
|
|
streamOpts, err := remotecommand.NewOptions(req.Request)
|
|
|
|
if err != nil {
|
|
|
|
resp.WriteError(http.StatusBadRequest, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-10-21 21:21:29 +00:00
|
|
|
remotecommand.ServeAttach(
|
|
|
|
resp.ResponseWriter,
|
|
|
|
req.Request,
|
|
|
|
s.runtime,
|
|
|
|
"", // unused: podName
|
|
|
|
"", // unusued: podUID
|
|
|
|
containerID,
|
2016-12-09 02:54:02 +00:00
|
|
|
streamOpts,
|
2016-10-21 21:21:29 +00:00
|
|
|
s.config.StreamIdleTimeout,
|
|
|
|
s.config.StreamCreationTimeout,
|
|
|
|
s.config.SupportedProtocols)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *server) servePortForward(req *restful.Request, resp *restful.Response) {
|
|
|
|
podSandboxID := req.PathParameter("podSandboxID")
|
|
|
|
if podSandboxID == "" {
|
|
|
|
resp.WriteError(http.StatusBadRequest, errors.New("missing required podSandboxID path parameter"))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
portforward.ServePortForward(
|
|
|
|
resp.ResponseWriter,
|
|
|
|
req.Request,
|
|
|
|
s.runtime,
|
|
|
|
podSandboxID,
|
|
|
|
"", // unused: podUID
|
|
|
|
s.config.StreamIdleTimeout,
|
|
|
|
s.config.StreamCreationTimeout)
|
|
|
|
}
|
|
|
|
|
|
|
|
// criAdapter wraps the Runtime functions to conform to the remotecommand interfaces.
|
|
|
|
// The adapter binds the container ID to the container name argument, and the pod sandbox ID to the pod name.
|
|
|
|
type criAdapter struct {
|
|
|
|
Runtime
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ remotecommand.Executor = &criAdapter{}
|
|
|
|
var _ remotecommand.Attacher = &criAdapter{}
|
|
|
|
var _ portforward.PortForwarder = &criAdapter{}
|
|
|
|
|
2016-06-23 17:44:26 +00:00
|
|
|
func (a *criAdapter) ExecInContainer(podName string, podUID types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error {
|
2016-10-21 21:21:29 +00:00
|
|
|
return a.Exec(container, cmd, in, out, err, tty, resize)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *criAdapter) AttachContainer(podName string, podUID types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan term.Size) error {
|
2016-11-09 00:36:11 +00:00
|
|
|
return a.Attach(container, in, out, err, tty, resize)
|
2016-10-21 21:21:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (a *criAdapter) PortForward(podName string, podUID types.UID, port uint16, stream io.ReadWriteCloser) error {
|
|
|
|
return a.Runtime.PortForward(podName, int32(port), stream)
|
|
|
|
}
|