mirror of https://github.com/k3s-io/k3s
Merge pull request #41543 from dshulyak/decouple_remotecommand
Automatic merge from submit-queue (batch tested with PRs 44406, 41543, 44071, 44374, 44299) Decouple remotecommand Refactored unversioned/remotecommand to decouple it from undesirable dependencies: - term package now is not required, and functionality required to resize terminal size can be plugged in directly in kubectl - in order to remove dependency on kubelet package - constants from kubelet/server/remotecommand were moved to separate util package (pkg/util/remotecommand) - remotecommand_test.go moved to pkg/client/tests modulepull/6/head
commit
4653a9b280
|
@ -14,6 +14,7 @@ go_test(
|
|||
"fake_client_test.go",
|
||||
"listwatch_test.go",
|
||||
"portfoward_test.go",
|
||||
"remotecommand_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
|
@ -26,10 +27,15 @@ go_test(
|
|||
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
|
||||
"//pkg/client/unversioned/remotecommand:go_default_library",
|
||||
"//pkg/kubelet/server/portforward:go_default_library",
|
||||
"//pkg/kubelet/server/remotecommand:go_default_library",
|
||||
"//vendor:github.com/stretchr/testify/require",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/fields",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/httpstream",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/remotecommand",
|
||||
"//vendor:k8s.io/apimachinery/pkg/watch",
|
||||
"//vendor:k8s.io/client-go/pkg/api/install",
|
||||
"//vendor:k8s.io/client-go/rest",
|
||||
|
|
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
package remotecommand
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -34,11 +34,12 @@ import (
|
|||
"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"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
remoteclient "k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
)
|
||||
|
||||
type fakeExecutor struct {
|
||||
|
@ -55,11 +56,11 @@ type fakeExecutor struct {
|
|||
exec bool
|
||||
}
|
||||
|
||||
func (ex *fakeExecutor) ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error {
|
||||
func (ex *fakeExecutor) ExecInContainer(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 {
|
||||
return ex.run(name, uid, container, cmd, in, out, err, tty)
|
||||
}
|
||||
|
||||
func (ex *fakeExecutor) AttachContainer(name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan term.Size) error {
|
||||
func (ex *fakeExecutor) AttachContainer(name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remoteclient.TerminalSize) error {
|
||||
return ex.run(name, uid, container, nil, in, out, err, tty)
|
||||
}
|
||||
|
||||
|
@ -151,8 +152,8 @@ func TestStream(t *testing.T) {
|
|||
TestName: "error",
|
||||
Error: "bail",
|
||||
Stdout: "a",
|
||||
ClientProtocols: []string{remotecommand.StreamProtocolV2Name},
|
||||
ServerProtocols: []string{remotecommand.StreamProtocolV2Name},
|
||||
ClientProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
|
||||
ServerProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
|
||||
},
|
||||
{
|
||||
TestName: "in/out/err",
|
||||
|
@ -160,8 +161,8 @@ func TestStream(t *testing.T) {
|
|||
Stdout: "b",
|
||||
Stderr: "c",
|
||||
MessageCount: 100,
|
||||
ClientProtocols: []string{remotecommand.StreamProtocolV2Name},
|
||||
ServerProtocols: []string{remotecommand.StreamProtocolV2Name},
|
||||
ClientProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
|
||||
ServerProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
|
||||
},
|
||||
{
|
||||
TestName: "in/out/tty",
|
||||
|
@ -169,8 +170,8 @@ func TestStream(t *testing.T) {
|
|||
Stdout: "b",
|
||||
Tty: true,
|
||||
MessageCount: 100,
|
||||
ClientProtocols: []string{remotecommand.StreamProtocolV2Name},
|
||||
ServerProtocols: []string{remotecommand.StreamProtocolV2Name},
|
||||
ClientProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
|
||||
ServerProtocols: []string{remotecommandconsts.StreamProtocolV2Name},
|
||||
},
|
||||
{
|
||||
// 1.0 kubectl, 1.0 kubelet
|
||||
|
@ -188,7 +189,7 @@ func TestStream(t *testing.T) {
|
|||
Stderr: "c",
|
||||
MessageCount: 1,
|
||||
ClientProtocols: []string{},
|
||||
ServerProtocols: []string{remotecommand.StreamProtocolV2Name, remotecommand.StreamProtocolV1Name},
|
||||
ServerProtocols: []string{remotecommandconsts.StreamProtocolV2Name, remotecommandconsts.StreamProtocolV1Name},
|
||||
},
|
||||
{
|
||||
// 1.1+ kubectl, 1.0 kubelet
|
||||
|
@ -196,7 +197,7 @@ func TestStream(t *testing.T) {
|
|||
Stdout: "b",
|
||||
Stderr: "c",
|
||||
MessageCount: 1,
|
||||
ClientProtocols: []string{remotecommand.StreamProtocolV2Name, remotecommand.StreamProtocolV1Name},
|
||||
ClientProtocols: []string{remotecommandconsts.StreamProtocolV2Name, remotecommandconsts.StreamProtocolV1Name},
|
||||
ServerProtocols: []string{},
|
||||
},
|
||||
}
|
||||
|
@ -254,12 +255,12 @@ func TestStream(t *testing.T) {
|
|||
conf := &restclient.Config{
|
||||
Host: server.URL,
|
||||
}
|
||||
e, err := NewExecutor(conf, "POST", req.URL())
|
||||
e, err := remoteclient.NewExecutor(conf, "POST", req.URL())
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", name, err)
|
||||
continue
|
||||
}
|
||||
err = e.Stream(StreamOptions{
|
||||
err = e.Stream(remoteclient.StreamOptions{
|
||||
SupportedProtocols: testCase.ClientProtocols,
|
||||
Stdin: streamIn,
|
||||
Stdout: streamOut,
|
||||
|
@ -351,7 +352,7 @@ func TestDial(t *testing.T) {
|
|||
called = true
|
||||
return rt
|
||||
}
|
||||
exec, err := NewStreamExecutor(upgrader, testFn, "POST", &url.URL{Host: "something.com", Scheme: "https"})
|
||||
exec, err := remoteclient.NewStreamExecutor(upgrader, testFn, "POST", &url.URL{Host: "something.com", Scheme: "https"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
|
@ -14,6 +14,7 @@ go_library(
|
|||
"doc.go",
|
||||
"errorstream.go",
|
||||
"remotecommand.go",
|
||||
"resize.go",
|
||||
"v1.go",
|
||||
"v2.go",
|
||||
"v3.go",
|
||||
|
@ -22,13 +23,12 @@ go_library(
|
|||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/kubelet/server/remotecommand:go_default_library",
|
||||
"//pkg/util/exec:go_default_library",
|
||||
"//pkg/util/term:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/httpstream",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/httpstream/spdy",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/remotecommand",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
|
||||
"//vendor:k8s.io/client-go/rest",
|
||||
"//vendor:k8s.io/client-go/transport",
|
||||
|
@ -38,7 +38,6 @@ go_library(
|
|||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"remotecommand_test.go",
|
||||
"v2_test.go",
|
||||
"v4_test.go",
|
||||
],
|
||||
|
@ -46,15 +45,8 @@ go_test(
|
|||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/kubelet/server/remotecommand:go_default_library",
|
||||
"//pkg/util/term:go_default_library",
|
||||
"//vendor:github.com/stretchr/testify/require",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/httpstream",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
||||
"//vendor:k8s.io/client-go/rest",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -26,10 +26,9 @@ import (
|
|||
|
||||
"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"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
)
|
||||
|
||||
// StreamOptions holds information pertaining to the current streaming session: supported stream
|
||||
|
@ -41,7 +40,7 @@ type StreamOptions struct {
|
|||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
Tty bool
|
||||
TerminalSizeQueue term.TerminalSizeQueue
|
||||
TerminalSizeQueue TerminalSizeQueue
|
||||
}
|
||||
|
||||
// Executor is an interface for transporting shell-style streams.
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
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 remotecommand
|
||||
|
||||
// TermimanlSize and TerminalSizeQueue was a part of k8s.io/kubernetes/pkg/util/term
|
||||
// and were moved in order to decouple client from other term dependencies
|
||||
|
||||
// TerminalSize represents the width and height of a terminal.
|
||||
type TerminalSize struct {
|
||||
Width uint16
|
||||
Height uint16
|
||||
}
|
||||
|
||||
// TerminalSizeQueue is capable of returning terminal resize events as they occur.
|
||||
type TerminalSizeQueue interface {
|
||||
// Next returns the new terminal size after the terminal has been resized. It returns nil when
|
||||
// monitoring has been stopped.
|
||||
Next() *TerminalSize
|
||||
}
|
|
@ -66,7 +66,6 @@ func (p *streamProtocolV3) handleResizes() {
|
|||
if p.resizeStream == nil || p.TerminalSizeQueue == nil {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import (
|
|||
"sync"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
"k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/util/exec"
|
||||
)
|
||||
|
||||
|
|
|
@ -93,7 +93,6 @@ go_library(
|
|||
"//pkg/kubectl/cmd/util/editor:go_default_library",
|
||||
"//pkg/kubectl/metricsutil:go_default_library",
|
||||
"//pkg/kubectl/resource:go_default_library",
|
||||
"//pkg/kubelet/server/remotecommand:go_default_library",
|
||||
"//pkg/kubelet/types:go_default_library",
|
||||
"//pkg/printers:go_default_library",
|
||||
"//pkg/printers/internalversion:go_default_library",
|
||||
|
@ -128,6 +127,7 @@ go_library(
|
|||
"//vendor:k8s.io/apimachinery/pkg/util/json",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/jsonmergepatch",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/mergepatch",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/remotecommand",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/sets",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/strategicpatch",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/validation",
|
||||
|
@ -203,6 +203,7 @@ go_test(
|
|||
"//pkg/apis/extensions:go_default_library",
|
||||
"//pkg/apis/policy:go_default_library",
|
||||
"//pkg/apis/rbac:go_default_library",
|
||||
"//pkg/client/unversioned/remotecommand:go_default_library",
|
||||
"//pkg/kubectl:go_default_library",
|
||||
"//pkg/kubectl/cmd/testing:go_default_library",
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
|
|
|
@ -27,6 +27,7 @@ import (
|
|||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
||||
|
@ -34,9 +35,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/kubectl/resource"
|
||||
remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/util/i18n"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -91,19 +90,19 @@ func NewCmdAttach(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer)
|
|||
|
||||
// RemoteAttach defines the interface accepted by the Attach command - provided for test stubbing
|
||||
type RemoteAttach interface {
|
||||
Attach(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue term.TerminalSizeQueue) error
|
||||
Attach(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error
|
||||
}
|
||||
|
||||
// DefaultRemoteAttach is the standard implementation of attaching
|
||||
type DefaultRemoteAttach struct{}
|
||||
|
||||
func (*DefaultRemoteAttach) Attach(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue term.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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return exec.Stream(remotecommand.StreamOptions{
|
||||
SupportedProtocols: remotecommandserver.SupportedStreamingProtocols,
|
||||
SupportedProtocols: remotecommandconsts.SupportedStreamingProtocols,
|
||||
Stdin: stdin,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
|
@ -242,7 +241,7 @@ func (p *AttachOptions) Run() error {
|
|||
// save p.Err so we can print the command prompt message below
|
||||
stderr := p.Err
|
||||
|
||||
var sizeQueue term.TerminalSizeQueue
|
||||
var sizeQueue remotecommand.TerminalSizeQueue
|
||||
if t.Raw {
|
||||
if size := t.GetSize(); size != nil {
|
||||
// fake resizing +1 and then back to normal so that attach-detach-reattach will result in the
|
||||
|
|
|
@ -34,9 +34,9 @@ import (
|
|||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
)
|
||||
|
||||
type fakeRemoteAttach struct {
|
||||
|
@ -45,7 +45,7 @@ type fakeRemoteAttach struct {
|
|||
err error
|
||||
}
|
||||
|
||||
func (f *fakeRemoteAttach) Attach(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue term.TerminalSizeQueue) error {
|
||||
func (f *fakeRemoteAttach) Attach(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error {
|
||||
f.method = method
|
||||
f.url = url
|
||||
return f.err
|
||||
|
|
|
@ -25,13 +25,13 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/util/i18n"
|
||||
"k8s.io/kubernetes/pkg/util/interrupt"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
|
@ -86,19 +86,19 @@ func NewCmdExec(f cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *c
|
|||
|
||||
// RemoteExecutor defines the interface accepted by the Exec command - provided for test stubbing
|
||||
type RemoteExecutor interface {
|
||||
Execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue term.TerminalSizeQueue) error
|
||||
Execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error
|
||||
}
|
||||
|
||||
// DefaultRemoteExecutor is the standard implementation of remote command execution
|
||||
type DefaultRemoteExecutor struct{}
|
||||
|
||||
func (*DefaultRemoteExecutor) Execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue term.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)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return exec.Stream(remotecommand.StreamOptions{
|
||||
SupportedProtocols: remotecommandserver.SupportedStreamingProtocols,
|
||||
SupportedProtocols: remotecommandconsts.SupportedStreamingProtocols,
|
||||
Stdin: stdin,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
|
@ -284,7 +284,7 @@ func (p *ExecOptions) Run() error {
|
|||
// ensure we can recover the terminal while attached
|
||||
t := p.setupTTY()
|
||||
|
||||
var sizeQueue term.TerminalSizeQueue
|
||||
var sizeQueue remotecommand.TerminalSizeQueue
|
||||
if t.Raw {
|
||||
// this call spawns a goroutine to monitor/update the terminal size
|
||||
sizeQueue = t.MonitorSize(t.GetSize())
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
)
|
||||
|
@ -43,7 +44,7 @@ type fakeRemoteExecutor struct {
|
|||
execErr error
|
||||
}
|
||||
|
||||
func (f *fakeRemoteExecutor) Execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue term.TerminalSizeQueue) error {
|
||||
func (f *fakeRemoteExecutor) Execute(method string, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error {
|
||||
f.method = method
|
||||
f.url = url
|
||||
return f.execErr
|
||||
|
|
|
@ -43,6 +43,7 @@ go_library(
|
|||
"//pkg/capabilities:go_default_library",
|
||||
"//pkg/client/clientset_generated/clientset:go_default_library",
|
||||
"//pkg/client/listers/core/v1:go_default_library",
|
||||
"//pkg/client/unversioned/remotecommand:go_default_library",
|
||||
"//pkg/cloudprovider:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//pkg/fieldpath:go_default_library",
|
||||
|
@ -99,7 +100,6 @@ go_library(
|
|||
"//pkg/util/oom:go_default_library",
|
||||
"//pkg/util/procfs:go_default_library",
|
||||
"//pkg/util/removeall:go_default_library",
|
||||
"//pkg/util/term:go_default_library",
|
||||
"//pkg/version:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//pkg/volume/util:go_default_library",
|
||||
|
|
|
@ -28,12 +28,12 @@ go_library(
|
|||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/client/unversioned/remotecommand:go_default_library",
|
||||
"//pkg/kubelet/api/v1alpha1/runtime:go_default_library",
|
||||
"//pkg/kubelet/events:go_default_library",
|
||||
"//pkg/kubelet/util/format:go_default_library",
|
||||
"//pkg/kubelet/util/ioutils:go_default_library",
|
||||
"//pkg/util/hash:go_default_library",
|
||||
"//pkg/util/term:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//third_party/forked/golang/expansion:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
|
|
|
@ -18,13 +18,13 @@ package container
|
|||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
)
|
||||
|
||||
// handleResizing spawns a goroutine that processes the resize channel, calling resizeFunc for each
|
||||
// term.Size received from the channel. The resize channel must be closed elsewhere to stop the
|
||||
// remotecommand.TerminalSize received from the channel. The resize channel must be closed elsewhere to stop the
|
||||
// goroutine.
|
||||
func HandleResizing(resize <-chan term.Size, resizeFunc func(size term.Size)) {
|
||||
func HandleResizing(resize <-chan remotecommand.TerminalSize, resizeFunc func(size remotecommand.TerminalSize)) {
|
||||
if resize == nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -28,8 +28,8 @@ import (
|
|||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/flowcontrol"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
|
@ -128,7 +128,7 @@ type DirectStreamingRuntime interface {
|
|||
// Runs the command in the container of the specified pod using nsenter.
|
||||
// Attaches the processes stdin, stdout, and stderr. Optionally uses a
|
||||
// tty.
|
||||
ExecInContainer(containerID ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error
|
||||
ExecInContainer(containerID ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error
|
||||
// Forward the specified port from the specified pod to the stream.
|
||||
PortForward(pod *Pod, port int32, stream io.ReadWriteCloser) error
|
||||
// ContainerAttach encapsulates the attaching to containers for testability
|
||||
|
@ -160,7 +160,7 @@ type ImageService interface {
|
|||
}
|
||||
|
||||
type ContainerAttacher interface {
|
||||
AttachContainer(id ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) (err error)
|
||||
AttachContainer(id ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) (err error)
|
||||
}
|
||||
|
||||
type ContainerCommandRunner interface {
|
||||
|
|
|
@ -20,8 +20,8 @@ go_library(
|
|||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/client/unversioned/remotecommand:go_default_library",
|
||||
"//pkg/kubelet/container:go_default_library",
|
||||
"//pkg/util/term:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//vendor:github.com/golang/mock/gomock",
|
||||
"//vendor:github.com/stretchr/testify/mock",
|
||||
|
|
|
@ -27,8 +27,8 @@ import (
|
|||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/flowcontrol"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
. "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
|
@ -311,7 +311,7 @@ func (f *FakeRuntime) GetPodStatus(uid types.UID, name, namespace string) (*PodS
|
|||
return &status, f.Err
|
||||
}
|
||||
|
||||
func (f *FakeDirectStreamingRuntime) ExecInContainer(containerID ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error {
|
||||
func (f *FakeDirectStreamingRuntime) ExecInContainer(containerID ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
|
@ -326,7 +326,7 @@ func (f *FakeDirectStreamingRuntime) ExecInContainer(containerID ContainerID, cm
|
|||
return f.Err
|
||||
}
|
||||
|
||||
func (f *FakeDirectStreamingRuntime) AttachContainer(containerID ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) error {
|
||||
func (f *FakeDirectStreamingRuntime) AttachContainer(containerID ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
|
|
|
@ -24,8 +24,8 @@ import (
|
|||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/util/flowcontrol"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
. "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
|
@ -90,12 +90,12 @@ func (r *Mock) GetPodStatus(uid types.UID, name, namespace string) (*PodStatus,
|
|||
return args.Get(0).(*PodStatus), args.Error(1)
|
||||
}
|
||||
|
||||
func (r *Mock) ExecInContainer(containerID ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error {
|
||||
func (r *Mock) ExecInContainer(containerID ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
args := r.Called(containerID, cmd, stdin, stdout, stderr, tty)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
func (r *Mock) AttachContainer(containerID ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) error {
|
||||
func (r *Mock) AttachContainer(containerID ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
args := r.Called(containerID, stdin, stdout, stderr, tty)
|
||||
return args.Error(0)
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ go_library(
|
|||
deps = [
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/apis/componentconfig:go_default_library",
|
||||
"//pkg/client/unversioned/remotecommand:go_default_library",
|
||||
"//pkg/kubelet/api:go_default_library",
|
||||
"//pkg/kubelet/api/v1alpha1/runtime:go_default_library",
|
||||
"//pkg/kubelet/cm:go_default_library",
|
||||
|
@ -48,7 +49,6 @@ go_library(
|
|||
"//pkg/kubelet/util/cache:go_default_library",
|
||||
"//pkg/kubelet/util/ioutils:go_default_library",
|
||||
"//pkg/util/hash:go_default_library",
|
||||
"//pkg/util/term:go_default_library",
|
||||
"//vendor:github.com/blang/semver",
|
||||
"//vendor:github.com/docker/engine-api/types",
|
||||
"//vendor:github.com/docker/engine-api/types/container",
|
||||
|
|
|
@ -24,11 +24,11 @@ import (
|
|||
"time"
|
||||
|
||||
dockertypes "github.com/docker/engine-api/types"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockertools"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/streaming"
|
||||
"k8s.io/kubernetes/pkg/kubelet/util/ioutils"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
)
|
||||
|
||||
type streamingRuntime struct {
|
||||
|
@ -38,12 +38,12 @@ type streamingRuntime struct {
|
|||
|
||||
var _ streaming.Runtime = &streamingRuntime{}
|
||||
|
||||
func (r *streamingRuntime) Exec(containerID string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan term.Size) error {
|
||||
func (r *streamingRuntime) Exec(containerID string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
return r.exec(containerID, cmd, in, out, err, tty, resize, 0)
|
||||
}
|
||||
|
||||
// Internal version of Exec adds a timeout.
|
||||
func (r *streamingRuntime) exec(containerID string, cmd []string, in io.Reader, out, errw io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error {
|
||||
func (r *streamingRuntime) exec(containerID string, cmd []string, in io.Reader, out, errw io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
container, err := checkContainerStatus(r.client, containerID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -51,7 +51,7 @@ func (r *streamingRuntime) exec(containerID string, cmd []string, in io.Reader,
|
|||
return r.execHandler.ExecInContainer(r.client, container, cmd, in, out, errw, tty, resize, timeout)
|
||||
}
|
||||
|
||||
func (r *streamingRuntime) Attach(containerID string, in io.Reader, out, errw io.WriteCloser, tty bool, resize <-chan term.Size) error {
|
||||
func (r *streamingRuntime) Attach(containerID string, in io.Reader, out, errw io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
_, err := checkContainerStatus(r.client, containerID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -28,6 +28,7 @@ go_library(
|
|||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/client/unversioned/remotecommand:go_default_library",
|
||||
"//pkg/credentialprovider:go_default_library",
|
||||
"//pkg/kubelet/cm:go_default_library",
|
||||
"//pkg/kubelet/container:go_default_library",
|
||||
|
|
|
@ -52,6 +52,7 @@ import (
|
|||
"k8s.io/client-go/util/flowcontrol"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/kubelet/cm"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
dockersecurity "k8s.io/kubernetes/pkg/kubelet/dockertools/securitycontext"
|
||||
|
@ -73,7 +74,6 @@ import (
|
|||
"k8s.io/kubernetes/pkg/util/selinux"
|
||||
utilstrings "k8s.io/kubernetes/pkg/util/strings"
|
||||
"k8s.io/kubernetes/pkg/util/tail"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
utilversion "k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
|
@ -1297,7 +1297,7 @@ func (d *dockerExitError) ExitStatus() int {
|
|||
}
|
||||
|
||||
// ExecInContainer runs the command inside the container identified by containerID.
|
||||
func (dm *DockerManager) ExecInContainer(containerID kubecontainer.ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error {
|
||||
func (dm *DockerManager) ExecInContainer(containerID kubecontainer.ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
if dm.execHandler == nil {
|
||||
return errors.New("unable to exec without an exec handler")
|
||||
}
|
||||
|
@ -1313,16 +1313,16 @@ func (dm *DockerManager) ExecInContainer(containerID kubecontainer.ContainerID,
|
|||
return dm.execHandler.ExecInContainer(dm.client, container, cmd, stdin, stdout, stderr, tty, resize, timeout)
|
||||
}
|
||||
|
||||
func (dm *DockerManager) AttachContainer(containerID kubecontainer.ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) error {
|
||||
func (dm *DockerManager) AttachContainer(containerID kubecontainer.ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
return AttachContainer(dm.client, containerID.ID, stdin, stdout, stderr, tty, resize)
|
||||
}
|
||||
|
||||
// Temporarily export this function to share with dockershim.
|
||||
// TODO: clean this up.
|
||||
func AttachContainer(client DockerInterface, containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) error {
|
||||
func AttachContainer(client DockerInterface, containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
// Have to start this before the call to client.AttachToContainer because client.AttachToContainer is a blocking
|
||||
// call :-( Otherwise, resize events don't get processed and the terminal never resizes.
|
||||
kubecontainer.HandleResizing(resize, func(size term.Size) {
|
||||
kubecontainer.HandleResizing(resize, func(size remotecommand.TerminalSize) {
|
||||
client.ResizeContainerTTY(containerID, int(size.Height), int(size.Width))
|
||||
})
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
|
||||
dockertypes "github.com/docker/engine-api/types"
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
utilexec "k8s.io/kubernetes/pkg/util/exec"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
|
@ -32,14 +33,14 @@ import (
|
|||
|
||||
// ExecHandler knows how to execute a command in a running Docker container.
|
||||
type ExecHandler interface {
|
||||
ExecInContainer(client DockerInterface, container *dockertypes.ContainerJSON, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error
|
||||
ExecInContainer(client DockerInterface, container *dockertypes.ContainerJSON, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error
|
||||
}
|
||||
|
||||
// NsenterExecHandler executes commands in Docker containers using nsenter.
|
||||
type NsenterExecHandler struct{}
|
||||
|
||||
// TODO should we support nsenter in a container, running with elevated privs and --pid=host?
|
||||
func (*NsenterExecHandler) ExecInContainer(client DockerInterface, container *dockertypes.ContainerJSON, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error {
|
||||
func (*NsenterExecHandler) ExecInContainer(client DockerInterface, container *dockertypes.ContainerJSON, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
nsenter, err := exec.LookPath("nsenter")
|
||||
if err != nil {
|
||||
return fmt.Errorf("exec unavailable - unable to locate nsenter")
|
||||
|
@ -64,7 +65,7 @@ func (*NsenterExecHandler) ExecInContainer(client DockerInterface, container *do
|
|||
// make sure to close the stdout stream
|
||||
defer stdout.Close()
|
||||
|
||||
kubecontainer.HandleResizing(resize, func(size term.Size) {
|
||||
kubecontainer.HandleResizing(resize, func(size remotecommand.TerminalSize) {
|
||||
term.SetSize(p.Fd(), size)
|
||||
})
|
||||
|
||||
|
@ -110,7 +111,7 @@ func (*NsenterExecHandler) ExecInContainer(client DockerInterface, container *do
|
|||
// NativeExecHandler executes commands in Docker containers using Docker's exec API.
|
||||
type NativeExecHandler struct{}
|
||||
|
||||
func (*NativeExecHandler) ExecInContainer(client DockerInterface, container *dockertypes.ContainerJSON, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error {
|
||||
func (*NativeExecHandler) ExecInContainer(client DockerInterface, container *dockertypes.ContainerJSON, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
createOpts := dockertypes.ExecConfig{
|
||||
Cmd: cmd,
|
||||
AttachStdin: stdin != nil,
|
||||
|
@ -125,7 +126,7 @@ func (*NativeExecHandler) ExecInContainer(client DockerInterface, container *doc
|
|||
|
||||
// Have to start this before the call to client.StartExec because client.StartExec is a blocking
|
||||
// call :-( Otherwise, resize events don't get processed and the terminal never resizes.
|
||||
kubecontainer.HandleResizing(resize, func(size term.Size) {
|
||||
kubecontainer.HandleResizing(resize, func(size remotecommand.TerminalSize) {
|
||||
client.ResizeExecTTY(execObj.ID, int(size.Height), int(size.Width))
|
||||
})
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/api/v1/validation"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/fieldpath"
|
||||
"k8s.io/kubernetes/pkg/kubelet/cm"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
|
@ -50,11 +51,10 @@ import (
|
|||
"k8s.io/kubernetes/pkg/kubelet/images"
|
||||
"k8s.io/kubernetes/pkg/kubelet/qos"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/portforward"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/kubelet/status"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
"k8s.io/kubernetes/pkg/kubelet/util/format"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
"k8s.io/kubernetes/pkg/volume/util/volumehelper"
|
||||
"k8s.io/kubernetes/third_party/forked/golang/expansion"
|
||||
|
@ -1399,7 +1399,7 @@ func (kl *Kubelet) RunInContainer(podFullName string, podUID types.UID, containe
|
|||
|
||||
// ExecInContainer executes a command in a container, connecting the supplied
|
||||
// stdin/stdout/stderr to the command's IO streams.
|
||||
func (kl *Kubelet) ExecInContainer(podFullName string, podUID types.UID, containerName string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error {
|
||||
func (kl *Kubelet) ExecInContainer(podFullName string, podUID types.UID, containerName string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
streamingRuntime, ok := kl.containerRuntime.(kubecontainer.DirectStreamingRuntime)
|
||||
if !ok {
|
||||
return fmt.Errorf("streaming methods not supported by runtime")
|
||||
|
@ -1417,7 +1417,7 @@ func (kl *Kubelet) ExecInContainer(podFullName string, podUID types.UID, contain
|
|||
|
||||
// AttachContainer uses the container runtime to attach the given streams to
|
||||
// the given container.
|
||||
func (kl *Kubelet) AttachContainer(podFullName string, podUID types.UID, containerName string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) error {
|
||||
func (kl *Kubelet) AttachContainer(podFullName string, podUID types.UID, containerName string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
streamingRuntime, ok := kl.containerRuntime.(kubecontainer.DirectStreamingRuntime)
|
||||
if !ok {
|
||||
return fmt.Errorf("streaming methods not supported by runtime")
|
||||
|
@ -1454,7 +1454,7 @@ func (kl *Kubelet) PortForward(podFullName string, podUID types.UID, port int32,
|
|||
}
|
||||
|
||||
// GetExec gets the URL the exec will be served from, or nil if the Kubelet will serve it.
|
||||
func (kl *Kubelet) GetExec(podFullName string, podUID types.UID, containerName string, cmd []string, streamOpts remotecommand.Options) (*url.URL, error) {
|
||||
func (kl *Kubelet) GetExec(podFullName string, podUID types.UID, containerName string, cmd []string, streamOpts remotecommandserver.Options) (*url.URL, error) {
|
||||
switch streamingRuntime := kl.containerRuntime.(type) {
|
||||
case kubecontainer.DirectStreamingRuntime:
|
||||
// Kubelet will serve the exec directly.
|
||||
|
@ -1474,7 +1474,7 @@ func (kl *Kubelet) GetExec(podFullName string, podUID types.UID, containerName s
|
|||
}
|
||||
|
||||
// GetAttach gets the URL the attach will be served from, or nil if the Kubelet will serve it.
|
||||
func (kl *Kubelet) GetAttach(podFullName string, podUID types.UID, containerName string, streamOpts remotecommand.Options) (*url.URL, error) {
|
||||
func (kl *Kubelet) GetAttach(podFullName string, podUID types.UID, containerName string, streamOpts remotecommandserver.Options) (*url.URL, error) {
|
||||
switch streamingRuntime := kl.containerRuntime.(type) {
|
||||
case kubecontainer.DirectStreamingRuntime:
|
||||
// Kubelet will serve the attach directly.
|
||||
|
|
|
@ -24,6 +24,7 @@ go_library(
|
|||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/client/unversioned/remotecommand:go_default_library",
|
||||
"//pkg/credentialprovider:go_default_library",
|
||||
"//pkg/kubelet/container:go_default_library",
|
||||
"//pkg/kubelet/events:go_default_library",
|
||||
|
|
|
@ -49,6 +49,7 @@ import (
|
|||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/client-go/util/flowcontrol"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
"k8s.io/kubernetes/pkg/kubelet/events"
|
||||
|
@ -2068,14 +2069,14 @@ func newRktExitError(e error) error {
|
|||
return e
|
||||
}
|
||||
|
||||
func (r *Runtime) AttachContainer(containerID kubecontainer.ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) error {
|
||||
func (r *Runtime) AttachContainer(containerID kubecontainer.ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
return fmt.Errorf("unimplemented")
|
||||
}
|
||||
|
||||
// Note: In rkt, the container ID is in the form of "UUID:appName", where UUID is
|
||||
// the rkt UUID, and appName is the container name.
|
||||
// TODO(yifan): If the rkt is using lkvm as the stage1 image, then this function will fail.
|
||||
func (r *Runtime) ExecInContainer(containerID kubecontainer.ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error {
|
||||
func (r *Runtime) ExecInContainer(containerID kubecontainer.ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
glog.V(4).Infof("Rkt execing in container.")
|
||||
|
||||
id, err := parseContainerID(containerID)
|
||||
|
@ -2096,7 +2097,7 @@ func (r *Runtime) ExecInContainer(containerID kubecontainer.ContainerID, cmd []s
|
|||
// make sure to close the stdout stream
|
||||
defer stdout.Close()
|
||||
|
||||
kubecontainer.HandleResizing(resize, func(size term.Size) {
|
||||
kubecontainer.HandleResizing(resize, func(size remotecommand.TerminalSize) {
|
||||
term.SetSize(p.Fd(), size)
|
||||
})
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ go_library(
|
|||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/api/v1/validation:go_default_library",
|
||||
"//pkg/client/unversioned/remotecommand:go_default_library",
|
||||
"//pkg/kubelet/cm:go_default_library",
|
||||
"//pkg/kubelet/container:go_default_library",
|
||||
"//pkg/kubelet/server/portforward:go_default_library",
|
||||
|
@ -28,7 +29,6 @@ go_library(
|
|||
"//pkg/kubelet/server/streaming:go_default_library",
|
||||
"//pkg/util/configz:go_default_library",
|
||||
"//pkg/util/limitwriter:go_default_library",
|
||||
"//pkg/util/term:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//vendor:github.com/emicklei/go-restful",
|
||||
"//vendor:github.com/golang/glog",
|
||||
|
@ -40,6 +40,7 @@ go_library(
|
|||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/remotecommand",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/authenticator",
|
||||
"//vendor:k8s.io/apiserver/pkg/authentication/user",
|
||||
|
@ -62,13 +63,13 @@ go_test(
|
|||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/client/unversioned/remotecommand:go_default_library",
|
||||
"//pkg/kubelet/cm:go_default_library",
|
||||
"//pkg/kubelet/container:go_default_library",
|
||||
"//pkg/kubelet/container/testing:go_default_library",
|
||||
"//pkg/kubelet/server/portforward:go_default_library",
|
||||
"//pkg/kubelet/server/remotecommand:go_default_library",
|
||||
"//pkg/kubelet/server/stats:go_default_library",
|
||||
"//pkg/util/term:go_default_library",
|
||||
"//pkg/volume:go_default_library",
|
||||
"//vendor:github.com/google/cadvisor/info/v1",
|
||||
"//vendor:github.com/google/cadvisor/info/v2",
|
||||
|
|
|
@ -11,7 +11,6 @@ go_library(
|
|||
name = "go_default_library",
|
||||
srcs = [
|
||||
"attach.go",
|
||||
"constants.go",
|
||||
"doc.go",
|
||||
"exec.go",
|
||||
"httpstream.go",
|
||||
|
@ -20,14 +19,15 @@ go_library(
|
|||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/client/unversioned/remotecommand:go_default_library",
|
||||
"//pkg/util/exec:go_default_library",
|
||||
"//pkg/util/term:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/httpstream",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/httpstream/spdy",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/remotecommand",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
|
||||
"//vendor:k8s.io/apiserver/pkg/server/httplog",
|
||||
"//vendor:k8s.io/apiserver/pkg/util/wsstream",
|
||||
|
|
|
@ -26,14 +26,14 @@ import (
|
|||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
)
|
||||
|
||||
// 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(name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan term.Size) error
|
||||
AttachContainer(name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error
|
||||
}
|
||||
|
||||
// ServeAttach handles requests to attach to a container. After creating/receiving the required
|
||||
|
|
|
@ -25,21 +25,17 @@ import (
|
|||
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/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
utilexec "k8s.io/kubernetes/pkg/util/exec"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
)
|
||||
|
||||
const (
|
||||
NonZeroExitCodeReason = metav1.StatusReason("NonZeroExitCode")
|
||||
ExitCodeCauseType = metav1.CauseType("ExitCode")
|
||||
)
|
||||
|
||||
// Executor knows how to execute a command in a container in a pod.
|
||||
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(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error
|
||||
ExecInContainer(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
|
||||
}
|
||||
|
||||
// ServeExec handles requests to execute a command in a container. After
|
||||
|
@ -59,11 +55,11 @@ func ServeExec(w http.ResponseWriter, req *http.Request, executor Executor, podN
|
|||
rc := exitErr.ExitStatus()
|
||||
ctx.writeStatus(&apierrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Reason: NonZeroExitCodeReason,
|
||||
Reason: remotecommandconsts.NonZeroExitCodeReason,
|
||||
Details: &metav1.StatusDetails{
|
||||
Causes: []metav1.StatusCause{
|
||||
{
|
||||
Type: ExitCodeCauseType,
|
||||
Type: remotecommandconsts.ExitCodeCauseType,
|
||||
Message: fmt.Sprintf("%d", rc),
|
||||
},
|
||||
},
|
||||
|
|
|
@ -28,10 +28,11 @@ import (
|
|||
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/runtime"
|
||||
"k8s.io/apiserver/pkg/util/wsstream"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
@ -78,7 +79,7 @@ type context struct {
|
|||
stderrStream io.WriteCloser
|
||||
writeStatus func(status *apierrors.StatusError) error
|
||||
resizeStream io.ReadCloser
|
||||
resizeChan chan term.Size
|
||||
resizeChan chan remotecommand.TerminalSize
|
||||
tty bool
|
||||
}
|
||||
|
||||
|
@ -114,7 +115,7 @@ func createStreams(req *http.Request, w http.ResponseWriter, opts *Options, supp
|
|||
}
|
||||
|
||||
if ctx.resizeStream != nil {
|
||||
ctx.resizeChan = make(chan term.Size)
|
||||
ctx.resizeChan = make(chan remotecommand.TerminalSize)
|
||||
go handleResizeEvents(ctx.resizeStream, ctx.resizeChan)
|
||||
}
|
||||
|
||||
|
@ -148,16 +149,16 @@ func createHttpStreamStreams(req *http.Request, w http.ResponseWriter, opts *Opt
|
|||
|
||||
var handler protocolHandler
|
||||
switch protocol {
|
||||
case StreamProtocolV4Name:
|
||||
case remotecommandconsts.StreamProtocolV4Name:
|
||||
handler = &v4ProtocolHandler{}
|
||||
case StreamProtocolV3Name:
|
||||
case remotecommandconsts.StreamProtocolV3Name:
|
||||
handler = &v3ProtocolHandler{}
|
||||
case StreamProtocolV2Name:
|
||||
case remotecommandconsts.StreamProtocolV2Name:
|
||||
handler = &v2ProtocolHandler{}
|
||||
case "":
|
||||
glog.V(4).Infof("Client did not request protocol negotiaion. Falling back to %q", StreamProtocolV1Name)
|
||||
glog.V(4).Infof("Client did not request protocol negotiaion. Falling back to %q", remotecommandconsts.StreamProtocolV1Name)
|
||||
fallthrough
|
||||
case StreamProtocolV1Name:
|
||||
case remotecommandconsts.StreamProtocolV1Name:
|
||||
handler = &v1ProtocolHandler{}
|
||||
}
|
||||
|
||||
|
@ -409,12 +410,12 @@ WaitForStreams:
|
|||
// supportsTerminalResizing returns false because v1ProtocolHandler doesn't support it.
|
||||
func (*v1ProtocolHandler) supportsTerminalResizing() bool { return false }
|
||||
|
||||
func handleResizeEvents(stream io.Reader, channel chan<- term.Size) {
|
||||
func handleResizeEvents(stream io.Reader, channel chan<- remotecommand.TerminalSize) {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
decoder := json.NewDecoder(stream)
|
||||
for {
|
||||
size := term.Size{}
|
||||
size := remotecommand.TerminalSize{}
|
||||
if err := decoder.Decode(&size); err != nil {
|
||||
break
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
|
@ -50,15 +51,15 @@ import (
|
|||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/api/v1/validation"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/kubelet/cm"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/portforward"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/stats"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/streaming"
|
||||
"k8s.io/kubernetes/pkg/util/configz"
|
||||
"k8s.io/kubernetes/pkg/util/limitwriter"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
|
@ -170,8 +171,8 @@ type HostInterface interface {
|
|||
GetRunningPods() ([]*v1.Pod, error)
|
||||
GetPodByName(namespace, name string) (*v1.Pod, bool)
|
||||
RunInContainer(name string, uid types.UID, container string, cmd []string) ([]byte, error)
|
||||
ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error
|
||||
AttachContainer(name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan term.Size) error
|
||||
ExecInContainer(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
|
||||
AttachContainer(name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error
|
||||
GetKubeletContainerLogs(podFullName, containerName string, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) error
|
||||
ServeLogs(w http.ResponseWriter, req *http.Request)
|
||||
PortForward(name string, uid types.UID, port int32, stream io.ReadWriteCloser) error
|
||||
|
@ -184,8 +185,8 @@ type HostInterface interface {
|
|||
ImagesFsInfo() (cadvisorapiv2.FsInfo, error)
|
||||
RootFsInfo() (cadvisorapiv2.FsInfo, error)
|
||||
ListVolumesForPod(podUID types.UID) (map[string]volume.Volume, bool)
|
||||
GetExec(podFullName string, podUID types.UID, containerName string, cmd []string, streamOpts remotecommand.Options) (*url.URL, error)
|
||||
GetAttach(podFullName string, podUID types.UID, containerName string, streamOpts remotecommand.Options) (*url.URL, error)
|
||||
GetExec(podFullName string, podUID types.UID, containerName string, cmd []string, streamOpts remotecommandserver.Options) (*url.URL, error)
|
||||
GetAttach(podFullName string, podUID types.UID, containerName string, streamOpts remotecommandserver.Options) (*url.URL, error)
|
||||
GetPortForward(podName, podNamespace string, podUID types.UID, portForwardOpts portforward.V4Options) (*url.URL, error)
|
||||
}
|
||||
|
||||
|
@ -612,7 +613,7 @@ func getPortForwardRequestParams(req *restful.Request) portForwardRequestParams
|
|||
// getAttach handles requests to attach to a container.
|
||||
func (s *Server) getAttach(request *restful.Request, response *restful.Response) {
|
||||
params := getExecRequestParams(request)
|
||||
streamOpts, err := remotecommand.NewOptions(request.Request)
|
||||
streamOpts, err := remotecommandserver.NewOptions(request.Request)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
response.WriteError(http.StatusBadRequest, err)
|
||||
|
@ -635,7 +636,7 @@ func (s *Server) getAttach(request *restful.Request, response *restful.Response)
|
|||
return
|
||||
}
|
||||
|
||||
remotecommand.ServeAttach(response.ResponseWriter,
|
||||
remotecommandserver.ServeAttach(response.ResponseWriter,
|
||||
request.Request,
|
||||
s.host,
|
||||
podFullName,
|
||||
|
@ -643,14 +644,14 @@ func (s *Server) getAttach(request *restful.Request, response *restful.Response)
|
|||
params.containerName,
|
||||
streamOpts,
|
||||
s.host.StreamingConnectionIdleTimeout(),
|
||||
remotecommand.DefaultStreamCreationTimeout,
|
||||
remotecommand.SupportedStreamingProtocols)
|
||||
remotecommandconsts.DefaultStreamCreationTimeout,
|
||||
remotecommandconsts.SupportedStreamingProtocols)
|
||||
}
|
||||
|
||||
// getExec handles requests to run a command inside a container.
|
||||
func (s *Server) getExec(request *restful.Request, response *restful.Response) {
|
||||
params := getExecRequestParams(request)
|
||||
streamOpts, err := remotecommand.NewOptions(request.Request)
|
||||
streamOpts, err := remotecommandserver.NewOptions(request.Request)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
response.WriteError(http.StatusBadRequest, err)
|
||||
|
@ -673,7 +674,7 @@ func (s *Server) getExec(request *restful.Request, response *restful.Response) {
|
|||
return
|
||||
}
|
||||
|
||||
remotecommand.ServeExec(response.ResponseWriter,
|
||||
remotecommandserver.ServeExec(response.ResponseWriter,
|
||||
request.Request,
|
||||
s.host,
|
||||
podFullName,
|
||||
|
@ -682,8 +683,8 @@ func (s *Server) getExec(request *restful.Request, response *restful.Response) {
|
|||
params.cmd,
|
||||
streamOpts,
|
||||
s.host.StreamingConnectionIdleTimeout(),
|
||||
remotecommand.DefaultStreamCreationTimeout,
|
||||
remotecommand.SupportedStreamingProtocols)
|
||||
remotecommandconsts.DefaultStreamCreationTimeout,
|
||||
remotecommandconsts.SupportedStreamingProtocols)
|
||||
}
|
||||
|
||||
// getRun handles requests to run a command inside a container.
|
||||
|
@ -757,7 +758,7 @@ func (s *Server) getPortForward(request *restful.Request, response *restful.Resp
|
|||
params.podUID,
|
||||
portForwardOptions,
|
||||
s.host.StreamingConnectionIdleTimeout(),
|
||||
remotecommand.DefaultStreamCreationTimeout,
|
||||
remotecommandconsts.DefaultStreamCreationTimeout,
|
||||
portforward.SupportedProtocols)
|
||||
}
|
||||
|
||||
|
|
|
@ -49,13 +49,13 @@ import (
|
|||
utiltesting "k8s.io/client-go/util/testing"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/kubelet/cm"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
kubecontainertesting "k8s.io/kubernetes/pkg/kubelet/container/testing"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/portforward"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/stats"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
"k8s.io/kubernetes/pkg/volume"
|
||||
)
|
||||
|
||||
|
@ -132,11 +132,11 @@ func (fk *fakeKubelet) RunInContainer(podFullName string, uid types.UID, contain
|
|||
return fk.runFunc(podFullName, uid, containerName, cmd)
|
||||
}
|
||||
|
||||
func (fk *fakeKubelet) ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error {
|
||||
func (fk *fakeKubelet) ExecInContainer(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 {
|
||||
return fk.execFunc(name, uid, container, cmd, in, out, err, tty)
|
||||
}
|
||||
|
||||
func (fk *fakeKubelet) AttachContainer(name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan term.Size) error {
|
||||
func (fk *fakeKubelet) AttachContainer(name string, uid types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
return fk.attachFunc(name, uid, container, in, out, err, tty)
|
||||
}
|
||||
|
||||
|
@ -144,11 +144,11 @@ func (fk *fakeKubelet) PortForward(name string, uid types.UID, port int32, strea
|
|||
return fk.portForwardFunc(name, uid, port, stream)
|
||||
}
|
||||
|
||||
func (fk *fakeKubelet) GetExec(podFullName string, podUID types.UID, containerName string, cmd []string, streamOpts remotecommand.Options) (*url.URL, error) {
|
||||
func (fk *fakeKubelet) GetExec(podFullName string, podUID types.UID, containerName string, cmd []string, streamOpts remotecommandserver.Options) (*url.URL, error) {
|
||||
return fk.redirectURL, nil
|
||||
}
|
||||
|
||||
func (fk *fakeKubelet) GetAttach(podFullName string, podUID types.UID, containerName string, streamOpts remotecommand.Options) (*url.URL, error) {
|
||||
func (fk *fakeKubelet) GetAttach(podFullName string, podUID types.UID, containerName string, streamOpts remotecommandserver.Options) (*url.URL, error) {
|
||||
return fk.redirectURL, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -17,14 +17,15 @@ go_library(
|
|||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/client/unversioned/remotecommand:go_default_library",
|
||||
"//pkg/kubelet/api/v1alpha1/runtime:go_default_library",
|
||||
"//pkg/kubelet/server/portforward:go_default_library",
|
||||
"//pkg/kubelet/server/remotecommand:go_default_library",
|
||||
"//pkg/util/term:go_default_library",
|
||||
"//vendor:github.com/emicklei/go-restful",
|
||||
"//vendor:google.golang.org/grpc",
|
||||
"//vendor:google.golang.org/grpc/codes",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/remotecommand",
|
||||
"//vendor:k8s.io/client-go/util/clock",
|
||||
],
|
||||
)
|
||||
|
@ -41,10 +42,9 @@ go_test(
|
|||
"//pkg/client/unversioned/remotecommand:go_default_library",
|
||||
"//pkg/kubelet/api/v1alpha1/runtime:go_default_library",
|
||||
"//pkg/kubelet/server/portforward:go_default_library",
|
||||
"//pkg/kubelet/server/remotecommand:go_default_library",
|
||||
"//pkg/util/term:go_default_library",
|
||||
"//vendor:github.com/stretchr/testify/assert",
|
||||
"//vendor:github.com/stretchr/testify/require",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/remotecommand",
|
||||
"//vendor:k8s.io/client-go/pkg/api",
|
||||
"//vendor:k8s.io/client-go/rest",
|
||||
"//vendor:k8s.io/client-go/util/clock",
|
||||
|
|
|
@ -31,10 +31,11 @@ import (
|
|||
restful "github.com/emicklei/go-restful"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
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/util/term"
|
||||
remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
)
|
||||
|
||||
// The library interface to serve the stream requests.
|
||||
|
@ -59,8 +60,8 @@ type Server interface {
|
|||
|
||||
// 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
|
||||
Attach(containerID string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan term.Size) error
|
||||
Exec(containerID string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error
|
||||
Attach(containerID string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error
|
||||
PortForward(podSandboxID string, port int32, stream io.ReadWriteCloser) error
|
||||
}
|
||||
|
||||
|
@ -95,8 +96,8 @@ type Config struct {
|
|||
// some fields like Addr must still be provided.
|
||||
var DefaultConfig = Config{
|
||||
StreamIdleTimeout: 4 * time.Hour,
|
||||
StreamCreationTimeout: remotecommand.DefaultStreamCreationTimeout,
|
||||
SupportedRemoteCommandProtocols: remotecommand.SupportedStreamingProtocols,
|
||||
StreamCreationTimeout: remotecommandconsts.DefaultStreamCreationTimeout,
|
||||
SupportedRemoteCommandProtocols: remotecommandconsts.SupportedStreamingProtocols,
|
||||
SupportedPortForwardProtocols: portforward.SupportedProtocols,
|
||||
}
|
||||
|
||||
|
@ -236,14 +237,14 @@ func (s *server) serveExec(req *restful.Request, resp *restful.Response) {
|
|||
return
|
||||
}
|
||||
|
||||
streamOpts := &remotecommand.Options{
|
||||
streamOpts := &remotecommandserver.Options{
|
||||
Stdin: exec.Stdin,
|
||||
Stdout: true,
|
||||
Stderr: !exec.Tty,
|
||||
TTY: exec.Tty,
|
||||
}
|
||||
|
||||
remotecommand.ServeExec(
|
||||
remotecommandserver.ServeExec(
|
||||
resp.ResponseWriter,
|
||||
req.Request,
|
||||
s.runtime,
|
||||
|
@ -270,13 +271,13 @@ func (s *server) serveAttach(req *restful.Request, resp *restful.Response) {
|
|||
return
|
||||
}
|
||||
|
||||
streamOpts := &remotecommand.Options{
|
||||
streamOpts := &remotecommandserver.Options{
|
||||
Stdin: attach.Stdin,
|
||||
Stdout: true,
|
||||
Stderr: !attach.Tty,
|
||||
TTY: attach.Tty,
|
||||
}
|
||||
remotecommand.ServeAttach(
|
||||
remotecommandserver.ServeAttach(
|
||||
resp.ResponseWriter,
|
||||
req.Request,
|
||||
s.runtime,
|
||||
|
@ -326,15 +327,15 @@ type criAdapter struct {
|
|||
Runtime
|
||||
}
|
||||
|
||||
var _ remotecommand.Executor = &criAdapter{}
|
||||
var _ remotecommand.Attacher = &criAdapter{}
|
||||
var _ remotecommandserver.Executor = &criAdapter{}
|
||||
var _ remotecommandserver.Attacher = &criAdapter{}
|
||||
var _ portforward.PortForwarder = &criAdapter{}
|
||||
|
||||
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 {
|
||||
func (a *criAdapter) ExecInContainer(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 {
|
||||
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 {
|
||||
func (a *criAdapter) AttachContainer(podName string, podUID types.UID, container string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
return a.Attach(container, in, out, err, tty, resize)
|
||||
}
|
||||
|
||||
|
|
|
@ -30,13 +30,12 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
remotecommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/client-go/pkg/api"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
||||
kubeletportforward "k8s.io/kubernetes/pkg/kubelet/server/portforward"
|
||||
kubeletremotecommand "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
"k8s.io/kubernetes/pkg/util/term"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -302,12 +301,11 @@ func runRemoteCommandTest(t *testing.T, commandType string) {
|
|||
require.NoError(t, err)
|
||||
|
||||
opts := remotecommand.StreamOptions{
|
||||
SupportedProtocols: kubeletremotecommand.SupportedStreamingProtocols,
|
||||
SupportedProtocols: remotecommandconsts.SupportedStreamingProtocols,
|
||||
Stdin: stdinR,
|
||||
Stdout: stdoutW,
|
||||
Stderr: stderrW,
|
||||
Tty: false,
|
||||
TerminalSizeQueue: nil,
|
||||
}
|
||||
require.NoError(t, exec.Stream(opts))
|
||||
}()
|
||||
|
@ -367,13 +365,13 @@ type fakeRuntime struct {
|
|||
t *testing.T
|
||||
}
|
||||
|
||||
func (f *fakeRuntime) Exec(containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) error {
|
||||
func (f *fakeRuntime) Exec(containerID string, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
assert.Equal(f.t, testContainerID, containerID)
|
||||
doServerStreams(f.t, "exec", stdin, stdout, stderr)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeRuntime) Attach(containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) error {
|
||||
func (f *fakeRuntime) Attach(containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
assert.Equal(f.t, testContainerID, containerID)
|
||||
doServerStreams(f.t, "attach", stdin, stdout, stderr)
|
||||
return nil
|
||||
|
|
|
@ -19,6 +19,7 @@ go_library(
|
|||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/client/unversioned/remotecommand:go_default_library",
|
||||
"//pkg/util/interrupt:go_default_library",
|
||||
"//vendor:github.com/docker/docker/pkg/term",
|
||||
"//vendor:github.com/mitchellh/go-wordwrap",
|
||||
|
|
|
@ -21,17 +21,12 @@ import (
|
|||
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
)
|
||||
|
||||
// Size represents the width and height of a terminal.
|
||||
type Size struct {
|
||||
Width uint16
|
||||
Height uint16
|
||||
}
|
||||
|
||||
// GetSize returns the current size of the user's terminal. If it isn't a terminal,
|
||||
// nil is returned.
|
||||
func (t TTY) GetSize() *Size {
|
||||
func (t TTY) GetSize() *remotecommand.TerminalSize {
|
||||
outFd, isTerminal := term.GetFdInfo(t.Out)
|
||||
if !isTerminal {
|
||||
return nil
|
||||
|
@ -40,19 +35,19 @@ func (t TTY) GetSize() *Size {
|
|||
}
|
||||
|
||||
// GetSize returns the current size of the terminal associated with fd.
|
||||
func GetSize(fd uintptr) *Size {
|
||||
func GetSize(fd uintptr) *remotecommand.TerminalSize {
|
||||
winsize, err := term.GetWinsize(fd)
|
||||
if err != nil {
|
||||
runtime.HandleError(fmt.Errorf("unable to get terminal size: %v", err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Size{Width: winsize.Width, Height: winsize.Height}
|
||||
return &remotecommand.TerminalSize{Width: winsize.Width, Height: winsize.Height}
|
||||
}
|
||||
|
||||
// MonitorSize monitors the terminal's size. It returns a TerminalSizeQueue primed with
|
||||
// initialSizes, or nil if there's no TTY present.
|
||||
func (t *TTY) MonitorSize(initialSizes ...*Size) TerminalSizeQueue {
|
||||
func (t *TTY) MonitorSize(initialSizes ...*remotecommand.TerminalSize) remotecommand.TerminalSizeQueue {
|
||||
outFd, isTerminal := term.GetFdInfo(t.Out)
|
||||
if !isTerminal {
|
||||
return nil
|
||||
|
@ -62,7 +57,7 @@ func (t *TTY) MonitorSize(initialSizes ...*Size) TerminalSizeQueue {
|
|||
t: *t,
|
||||
// make it buffered so we can send the initial terminal sizes without blocking, prior to starting
|
||||
// the streaming below
|
||||
resizeChan: make(chan Size, len(initialSizes)),
|
||||
resizeChan: make(chan remotecommand.TerminalSize, len(initialSizes)),
|
||||
stopResizing: make(chan struct{}),
|
||||
}
|
||||
|
||||
|
@ -71,27 +66,20 @@ func (t *TTY) MonitorSize(initialSizes ...*Size) TerminalSizeQueue {
|
|||
return t.sizeQueue
|
||||
}
|
||||
|
||||
// TerminalSizeQueue is capable of returning terminal resize events as they occur.
|
||||
type TerminalSizeQueue interface {
|
||||
// Next returns the new terminal size after the terminal has been resized. It returns nil when
|
||||
// monitoring has been stopped.
|
||||
Next() *Size
|
||||
}
|
||||
|
||||
// sizeQueue implements TerminalSizeQueue
|
||||
// sizeQueue implements remotecommand.TerminalSizeQueue
|
||||
type sizeQueue struct {
|
||||
t TTY
|
||||
// resizeChan receives a Size each time the user's terminal is resized.
|
||||
resizeChan chan Size
|
||||
resizeChan chan remotecommand.TerminalSize
|
||||
stopResizing chan struct{}
|
||||
}
|
||||
|
||||
// make sure sizeQueue implements the TerminalSizeQueue interface
|
||||
var _ TerminalSizeQueue = &sizeQueue{}
|
||||
// make sure sizeQueue implements the resize.TerminalSizeQueue interface
|
||||
var _ remotecommand.TerminalSizeQueue = &sizeQueue{}
|
||||
|
||||
// monitorSize primes resizeChan with initialSizes and then monitors for resize events. With each
|
||||
// new event, it sends the current terminal size to resizeChan.
|
||||
func (s *sizeQueue) monitorSize(outFd uintptr, initialSizes ...*Size) {
|
||||
func (s *sizeQueue) monitorSize(outFd uintptr, initialSizes ...*remotecommand.TerminalSize) {
|
||||
// send the initial sizes
|
||||
for i := range initialSizes {
|
||||
if initialSizes[i] != nil {
|
||||
|
@ -99,7 +87,7 @@ func (s *sizeQueue) monitorSize(outFd uintptr, initialSizes ...*Size) {
|
|||
}
|
||||
}
|
||||
|
||||
resizeEvents := make(chan Size, 1)
|
||||
resizeEvents := make(chan remotecommand.TerminalSize, 1)
|
||||
|
||||
monitorResizeEvents(outFd, resizeEvents, s.stopResizing)
|
||||
|
||||
|
@ -130,7 +118,7 @@ func (s *sizeQueue) monitorSize(outFd uintptr, initialSizes ...*Size) {
|
|||
|
||||
// Next returns the new terminal size after the terminal has been resized. It returns nil when
|
||||
// monitoring has been stopped.
|
||||
func (s *sizeQueue) Next() *Size {
|
||||
func (s *sizeQueue) Next() *remotecommand.TerminalSize {
|
||||
size, ok := <-s.resizeChan
|
||||
if !ok {
|
||||
return nil
|
||||
|
|
|
@ -24,12 +24,13 @@ import (
|
|||
"syscall"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
)
|
||||
|
||||
// monitorResizeEvents spawns a goroutine that waits for SIGWINCH signals (these indicate the
|
||||
// terminal has resized). After receiving a SIGWINCH, this gets the terminal size and tries to send
|
||||
// it to the resizeEvents channel. The goroutine stops when the stop channel is closed.
|
||||
func monitorResizeEvents(fd uintptr, resizeEvents chan<- Size, stop chan struct{}) {
|
||||
func monitorResizeEvents(fd uintptr, resizeEvents chan<- remotecommand.TerminalSize, stop chan struct{}) {
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
|
|
|
@ -20,9 +20,10 @@ package term
|
|||
|
||||
import (
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
)
|
||||
|
||||
// SetSize sets the terminal size associated with fd.
|
||||
func SetSize(fd uintptr, size Size) error {
|
||||
func SetSize(fd uintptr, size remotecommand.TerminalSize) error {
|
||||
return term.SetWinsize(fd, &term.Winsize{Height: size.Height, Width: size.Width})
|
||||
}
|
||||
|
|
|
@ -16,7 +16,11 @@ limitations under the License.
|
|||
|
||||
package remotecommand
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultStreamCreationTimeout = 30 * time.Second
|
||||
|
@ -41,6 +45,9 @@ const (
|
|||
// attachment/execution. It is the 4th version of the subprotocol and
|
||||
// adds support for exit codes.
|
||||
StreamProtocolV4Name = "v4.channel.k8s.io"
|
||||
|
||||
NonZeroExitCodeReason = metav1.StatusReason("NonZeroExitCode")
|
||||
ExitCodeCauseType = metav1.CauseType("ExitCode")
|
||||
)
|
||||
|
||||
var SupportedStreamingProtocols = []string{StreamProtocolV4Name, StreamProtocolV3Name, StreamProtocolV2Name, StreamProtocolV1Name}
|
|
@ -69,7 +69,6 @@ go_library(
|
|||
"//pkg/kubelet/api/v1alpha1/stats:go_default_library",
|
||||
"//pkg/kubelet/events:go_default_library",
|
||||
"//pkg/kubelet/metrics:go_default_library",
|
||||
"//pkg/kubelet/server/remotecommand:go_default_library",
|
||||
"//pkg/kubelet/server/stats:go_default_library",
|
||||
"//pkg/kubelet/sysctl:go_default_library",
|
||||
"//pkg/kubelet/util/format:go_default_library",
|
||||
|
@ -118,6 +117,7 @@ go_library(
|
|||
"//vendor:k8s.io/apimachinery/pkg/util/intstr",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/net",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/rand",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/remotecommand",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/sets",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/uuid",
|
||||
|
|
|
@ -23,11 +23,11 @@ import (
|
|||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
remocommandconsts "k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
|
||||
remotecommandserver "k8s.io/kubernetes/pkg/kubelet/server/remotecommand"
|
||||
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
@ -140,7 +140,7 @@ func execute(method string, url *url.URL, config *restclient.Config, stdin io.Re
|
|||
return err
|
||||
}
|
||||
return exec.Stream(remotecommand.StreamOptions{
|
||||
SupportedProtocols: remotecommandserver.SupportedStreamingProtocols,
|
||||
SupportedProtocols: remocommandconsts.SupportedStreamingProtocols,
|
||||
Stdin: stdin,
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
|
|
|
@ -16775,3 +16775,10 @@ go_library(
|
|||
"//vendor:k8s.io/client-go/pkg/api",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "k8s.io/apimachinery/pkg/util/remotecommand",
|
||||
srcs = ["k8s.io/apimachinery/pkg/util/remotecommand/constants.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = ["//vendor:k8s.io/apimachinery/pkg/apis/meta/v1"],
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue