Fresh dep ensure

This commit is contained in:
Mike Cronce
2018-11-26 13:23:56 -05:00
parent 93cb8a04d7
commit 407478ab9a
9016 changed files with 551394 additions and 279685 deletions

58
vendor/k8s.io/kubernetes/pkg/kubectl/cmd/attach/BUILD generated vendored Normal file
View File

@ -0,0 +1,58 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["attach.go"],
importpath = "k8s.io/kubernetes/pkg/kubectl/cmd/attach",
visibility = ["//visibility:public"],
deps = [
"//pkg/kubectl/cmd/exec:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//pkg/kubectl/util/i18n:go_default_library",
"//pkg/kubectl/util/templates:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions/resource:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/tools/remotecommand:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["attach_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/kubectl/cmd/exec:go_default_library",
"//pkg/kubectl/cmd/testing:go_default_library",
"//pkg/kubectl/polymorphichelpers:go_default_library",
"//pkg/kubectl/scheme:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/cli-runtime/pkg/genericclioptions:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/rest/fake:go_default_library",
"//staging/src/k8s.io/client-go/tools/remotecommand:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,340 @@
/*
Copyright 2014 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 attach
import (
"fmt"
"io"
"net/url"
"time"
"github.com/spf13/cobra"
"k8s.io/klog"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/kubernetes/pkg/kubectl/cmd/exec"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
"k8s.io/kubernetes/pkg/kubectl/util/templates"
)
var (
attachExample = templates.Examples(i18n.T(`
# Get output from running pod 123456-7890, using the first container by default
kubectl attach 123456-7890
# Get output from ruby-container from pod 123456-7890
kubectl attach 123456-7890 -c ruby-container
# Switch to raw terminal mode, sends stdin to 'bash' in ruby-container from pod 123456-7890
# and sends stdout/stderr from 'bash' back to the client
kubectl attach 123456-7890 -c ruby-container -i -t
# Get output from the first pod of a ReplicaSet named nginx
kubectl attach rs/nginx
`))
)
const (
defaultPodAttachTimeout = 60 * time.Second
defaultPodLogsTimeout = 20 * time.Second
)
// AttachOptions declare the arguments accepted by the Exec command
type AttachOptions struct {
exec.StreamOptions
// whether to disable use of standard error when streaming output from tty
DisableStderr bool
CommandName string
SuggestedCmdUsage string
Pod *corev1.Pod
AttachFunc func(*AttachOptions, *corev1.Container, bool, remotecommand.TerminalSizeQueue) func() error
Resources []string
Builder func() *resource.Builder
AttachablePodFn polymorphichelpers.AttachableLogsForObjectFunc
restClientGetter genericclioptions.RESTClientGetter
Attach RemoteAttach
GetPodTimeout time.Duration
Config *restclient.Config
}
func NewAttachOptions(streams genericclioptions.IOStreams) *AttachOptions {
return &AttachOptions{
StreamOptions: exec.StreamOptions{
IOStreams: streams,
},
Attach: &DefaultRemoteAttach{},
AttachFunc: DefaultAttachFunc,
}
}
func NewCmdAttach(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := NewAttachOptions(streams)
cmd := &cobra.Command{
Use: "attach (POD | TYPE/NAME) -c CONTAINER",
DisableFlagsInUseLine: true,
Short: i18n.T("Attach to a running container"),
Long: "Attach to a process that is already running inside an existing container.",
Example: attachExample,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run())
},
}
cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodAttachTimeout)
cmd.Flags().StringVarP(&o.ContainerName, "container", "c", o.ContainerName, "Container name. If omitted, the first container in the pod will be chosen")
cmd.Flags().BoolVarP(&o.Stdin, "stdin", "i", o.Stdin, "Pass stdin to the container")
cmd.Flags().BoolVarP(&o.TTY, "tty", "t", o.TTY, "Stdin is a TTY")
return cmd
}
// 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 remotecommand.TerminalSizeQueue) error
}
func DefaultAttachFunc(o *AttachOptions, containerToAttach *corev1.Container, raw bool, sizeQueue remotecommand.TerminalSizeQueue) func() error {
return func() error {
restClient, err := restclient.RESTClientFor(o.Config)
if err != nil {
return err
}
req := restClient.Post().
Resource("pods").
Name(o.Pod.Name).
Namespace(o.Pod.Namespace).
SubResource("attach")
req.VersionedParams(&corev1.PodAttachOptions{
Container: containerToAttach.Name,
Stdin: o.Stdin,
Stdout: o.Out != nil,
Stderr: !o.DisableStderr,
TTY: raw,
}, scheme.ParameterCodec)
return o.Attach.Attach("POST", req.URL(), o.Config, o.In, o.Out, o.ErrOut, raw, sizeQueue)
}
}
// 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 remotecommand.TerminalSizeQueue) error {
exec, err := remotecommand.NewSPDYExecutor(config, method, url)
if err != nil {
return err
}
return exec.Stream(remotecommand.StreamOptions{
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
Tty: tty,
TerminalSizeQueue: terminalSizeQueue,
})
}
// Complete verifies command line arguments and loads data from the command environment
func (o *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil {
return err
}
o.AttachablePodFn = polymorphichelpers.AttachablePodForObjectFn
o.GetPodTimeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd)
if err != nil {
return cmdutil.UsageErrorf(cmd, err.Error())
}
o.Builder = f.NewBuilder
o.Resources = args
o.restClientGetter = f
fullCmdName := ""
cmdParent := cmd.Parent()
if cmdParent != nil {
fullCmdName = cmdParent.CommandPath()
}
if len(fullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "describe") {
o.SuggestedCmdUsage = fmt.Sprintf("Use '%s describe pod/%s -n %s' to see all of the containers in this pod.", fullCmdName, o.PodName, o.Namespace)
}
config, err := f.ToRESTConfig()
if err != nil {
return err
}
o.Config = config
if o.CommandName == "" {
o.CommandName = cmd.CommandPath()
}
return nil
}
// Validate checks that the provided attach options are specified.
func (o *AttachOptions) Validate() error {
if len(o.Resources) == 0 {
return fmt.Errorf("at least 1 argument is required for attach")
}
if len(o.Resources) > 2 {
return fmt.Errorf("expected POD, TYPE/NAME, or TYPE NAME, (at most 2 arguments) saw %d: %v", len(o.Resources), o.Resources)
}
if o.GetPodTimeout <= 0 {
return fmt.Errorf("--pod-running-timeout must be higher than zero")
}
return nil
}
// Run executes a validated remote execution against a pod.
func (o *AttachOptions) Run() error {
if o.Pod == nil {
b := o.Builder().
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
NamespaceParam(o.Namespace).DefaultNamespace()
switch len(o.Resources) {
case 1:
b.ResourceNames("pods", o.Resources[0])
case 2:
b.ResourceNames(o.Resources[0], o.Resources[1])
}
obj, err := b.Do().Object()
if err != nil {
return err
}
o.Pod, err = o.findAttachablePod(obj)
if err != nil {
return err
}
if o.Pod.Status.Phase == corev1.PodSucceeded || o.Pod.Status.Phase == corev1.PodFailed {
return fmt.Errorf("cannot attach a container in a completed pod; current phase is %s", o.Pod.Status.Phase)
}
// TODO: convert this to a clean "wait" behavior
}
// check for TTY
containerToAttach, err := o.containerToAttachTo(o.Pod)
if err != nil {
return fmt.Errorf("cannot attach to the container: %v", err)
}
if o.TTY && !containerToAttach.TTY {
o.TTY = false
if o.ErrOut != nil {
fmt.Fprintf(o.ErrOut, "Unable to use a TTY - container %s did not allocate one\n", containerToAttach.Name)
}
} else if !o.TTY && containerToAttach.TTY {
// the container was launched with a TTY, so we have to force a TTY here, otherwise you'll get
// an error "Unrecognized input header"
o.TTY = true
}
// ensure we can recover the terminal while attached
t := o.SetupTTY()
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
// screen being redrawn
sizePlusOne := *size
sizePlusOne.Width++
sizePlusOne.Height++
// this call spawns a goroutine to monitor/update the terminal size
sizeQueue = t.MonitorSize(&sizePlusOne, size)
}
o.DisableStderr = true
}
if !o.Quiet {
fmt.Fprintln(o.ErrOut, "If you don't see a command prompt, try pressing enter.")
}
if err := t.Safe(o.AttachFunc(o, containerToAttach, t.Raw, sizeQueue)); err != nil {
return err
}
if o.Stdin && t.Raw && o.Pod.Spec.RestartPolicy == corev1.RestartPolicyAlways {
fmt.Fprintf(o.Out, "Session ended, resume using '%s %s -c %s -i -t' command when the pod is running\n", o.CommandName, o.Pod.Name, containerToAttach.Name)
}
return nil
}
func (o *AttachOptions) findAttachablePod(obj runtime.Object) (*corev1.Pod, error) {
attachablePod, err := o.AttachablePodFn(o.restClientGetter, obj, o.GetPodTimeout)
if err != nil {
return nil, err
}
o.StreamOptions.PodName = attachablePod.Name
return attachablePod, nil
}
// containerToAttach returns a reference to the container to attach to, given
// by name or the first container if name is empty.
func (o *AttachOptions) containerToAttachTo(pod *corev1.Pod) (*corev1.Container, error) {
if len(o.ContainerName) > 0 {
for i := range pod.Spec.Containers {
if pod.Spec.Containers[i].Name == o.ContainerName {
return &pod.Spec.Containers[i], nil
}
}
for i := range pod.Spec.InitContainers {
if pod.Spec.InitContainers[i].Name == o.ContainerName {
return &pod.Spec.InitContainers[i], nil
}
}
return nil, fmt.Errorf("container not found (%s)", o.ContainerName)
}
if len(o.SuggestedCmdUsage) > 0 {
fmt.Fprintf(o.ErrOut, "Defaulting container name to %s.\n", pod.Spec.Containers[0].Name)
fmt.Fprintf(o.ErrOut, "%s\n", o.SuggestedCmdUsage)
}
klog.V(4).Infof("defaulting container name to %s", pod.Spec.Containers[0].Name)
return &pod.Spec.Containers[0], nil
}
// GetContainerName returns the name of the container to attach to, with a fallback.
func (o *AttachOptions) GetContainerName(pod *corev1.Pod) (string, error) {
c, err := o.containerToAttachTo(pod)
if err != nil {
return "", err
}
return c.Name, nil
}

View File

@ -0,0 +1,422 @@
/*
Copyright 2014 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 attach
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"testing"
"time"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/kubernetes/pkg/kubectl/cmd/exec"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
type fakeRemoteAttach struct {
method string
url *url.URL
err 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
}
func fakeAttachablePodFn(pod *corev1.Pod) polymorphichelpers.AttachableLogsForObjectFunc {
return func(getter genericclioptions.RESTClientGetter, obj runtime.Object, timeout time.Duration) (*corev1.Pod, error) {
return pod, nil
}
}
func TestPodAndContainerAttach(t *testing.T) {
tests := []struct {
name string
args []string
options *AttachOptions
expectError string
expectedPodName string
expectedContainerName string
obj *corev1.Pod
}{
{
name: "empty",
options: &AttachOptions{GetPodTimeout: 1},
expectError: "at least 1 argument is required",
},
{
name: "too many args",
options: &AttachOptions{GetPodTimeout: 2},
args: []string{"one", "two", "three"},
expectError: "at most 2 arguments",
},
{
name: "no container, no flags",
options: &AttachOptions{GetPodTimeout: defaultPodLogsTimeout},
args: []string{"foo"},
expectedPodName: "foo",
expectedContainerName: "bar",
obj: attachPod(),
},
{
name: "container in flag",
options: &AttachOptions{StreamOptions: exec.StreamOptions{ContainerName: "bar"}, GetPodTimeout: 10000000},
args: []string{"foo"},
expectedPodName: "foo",
expectedContainerName: "bar",
obj: attachPod(),
},
{
name: "init container in flag",
options: &AttachOptions{StreamOptions: exec.StreamOptions{ContainerName: "initfoo"}, GetPodTimeout: 30},
args: []string{"foo"},
expectedPodName: "foo",
expectedContainerName: "initfoo",
obj: attachPod(),
},
{
name: "non-existing container",
options: &AttachOptions{StreamOptions: exec.StreamOptions{ContainerName: "wrong"}, GetPodTimeout: 10},
args: []string{"foo"},
expectedPodName: "foo",
expectError: "container not found",
obj: attachPod(),
},
{
name: "no container, no flags, pods and name",
options: &AttachOptions{GetPodTimeout: 10000},
args: []string{"pods", "foo"},
expectedPodName: "foo",
expectedContainerName: "bar",
obj: attachPod(),
},
{
name: "invalid get pod timeout value",
options: &AttachOptions{GetPodTimeout: 0},
args: []string{"pod/foo"},
expectedPodName: "foo",
expectedContainerName: "bar",
obj: attachPod(),
expectError: "must be higher than zero",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
// setup opts to fetch our test pod
test.options.AttachablePodFn = fakeAttachablePodFn(test.obj)
test.options.Resources = test.args
if err := test.options.Validate(); err != nil {
if !strings.Contains(err.Error(), test.expectError) {
t.Errorf("unexpected error: expected %q, got %q", test.expectError, err)
}
return
}
pod, err := test.options.findAttachablePod(&corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "test"},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "foobar",
},
},
},
})
if err != nil {
if !strings.Contains(err.Error(), test.expectError) {
t.Errorf("unexpected error: expected %q, got %q", err, test.expectError)
}
return
}
if pod.Name != test.expectedPodName {
t.Errorf("unexpected pod name: expected %q, got %q", test.expectedContainerName, pod.Name)
}
container, err := test.options.containerToAttachTo(attachPod())
if err != nil {
if !strings.Contains(err.Error(), test.expectError) {
t.Errorf("unexpected error: expected %q, got %q", err, test.expectError)
}
return
}
if container.Name != test.expectedContainerName {
t.Errorf("unexpected container name: expected %q, got %q", test.expectedContainerName, container.Name)
}
if test.options.PodName != test.expectedPodName {
t.Errorf("%s: expected: %s, got: %s", test.name, test.expectedPodName, test.options.PodName)
}
if len(test.expectError) > 0 {
t.Fatalf("expected error %q, but saw none", test.expectError)
}
})
}
}
func TestAttach(t *testing.T) {
version := "v1"
tests := []struct {
name, version, podPath, fetchPodPath, attachPath, container string
pod *corev1.Pod
remoteAttachErr bool
exepctedErr string
}{
{
name: "pod attach",
version: version,
podPath: "/api/" + version + "/namespaces/test/pods/foo",
fetchPodPath: "/namespaces/test/pods/foo",
attachPath: "/api/" + version + "/namespaces/test/pods/foo/attach",
pod: attachPod(),
container: "bar",
},
{
name: "pod attach error",
version: version,
podPath: "/api/" + version + "/namespaces/test/pods/foo",
fetchPodPath: "/namespaces/test/pods/foo",
attachPath: "/api/" + version + "/namespaces/test/pods/foo/attach",
pod: attachPod(),
remoteAttachErr: true,
container: "bar",
exepctedErr: "attach error",
},
{
name: "container not found error",
version: version,
podPath: "/api/" + version + "/namespaces/test/pods/foo",
fetchPodPath: "/namespaces/test/pods/foo",
attachPath: "/api/" + version + "/namespaces/test/pods/foo/attach",
pod: attachPod(),
container: "foo",
exepctedErr: "cannot attach to the container: container not found (foo)",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == test.podPath && m == "GET":
body := cmdtesting.ObjBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: body}, nil
case p == test.fetchPodPath && m == "GET":
body := cmdtesting.ObjBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: body}, nil
default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", p, req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request")
}
}),
}
tf.ClientConfigVal = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: scheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
remoteAttach := &fakeRemoteAttach{}
if test.remoteAttachErr {
remoteAttach.err = fmt.Errorf("attach error")
}
options := &AttachOptions{
StreamOptions: exec.StreamOptions{
ContainerName: test.container,
IOStreams: genericclioptions.NewTestIOStreamsDiscard(),
},
Attach: remoteAttach,
GetPodTimeout: 1000,
}
options.restClientGetter = tf
options.Namespace = "test"
options.Resources = []string{"foo"}
options.Builder = tf.NewBuilder
options.AttachablePodFn = fakeAttachablePodFn(test.pod)
options.AttachFunc = func(opts *AttachOptions, containerToAttach *corev1.Container, raw bool, sizeQueue remotecommand.TerminalSizeQueue) func() error {
return func() error {
u, err := url.Parse(fmt.Sprintf("%s?container=%s", test.attachPath, containerToAttach.Name))
if err != nil {
return err
}
return options.Attach.Attach("POST", u, nil, nil, nil, nil, raw, sizeQueue)
}
}
err := options.Run()
if test.exepctedErr != "" && err.Error() != test.exepctedErr {
t.Errorf("%s: Unexpected exec error: %v", test.name, err)
return
}
if test.exepctedErr == "" && err != nil {
t.Errorf("%s: Unexpected error: %v", test.name, err)
return
}
if test.exepctedErr != "" {
return
}
if remoteAttach.url.Path != test.attachPath {
t.Errorf("%s: Did not get expected path for exec request: %q %q", test.name, test.attachPath, remoteAttach.url.Path)
return
}
if remoteAttach.method != "POST" {
t.Errorf("%s: Did not get method for attach request: %s", test.name, remoteAttach.method)
}
if remoteAttach.url.Query().Get("container") != "bar" {
t.Errorf("%s: Did not have query parameters: %s", test.name, remoteAttach.url.Query())
}
})
}
}
func TestAttachWarnings(t *testing.T) {
version := "v1"
tests := []struct {
name, container, version, podPath, fetchPodPath, expectedErr string
pod *corev1.Pod
stdin, tty bool
}{
{
name: "fallback tty if not supported",
version: version,
podPath: "/api/" + version + "/namespaces/test/pods/foo",
fetchPodPath: "/namespaces/test/pods/foo",
pod: attachPod(),
stdin: true,
tty: true,
expectedErr: "Unable to use a TTY - container bar did not allocate one",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
streams, _, _, bufErr := genericclioptions.NewTestIOStreams()
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == test.podPath && m == "GET":
body := cmdtesting.ObjBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: body}, nil
case p == test.fetchPodPath && m == "GET":
body := cmdtesting.ObjBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: cmdtesting.DefaultHeader(), Body: body}, nil
default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", p, req.Method, req.URL, req)
return nil, fmt.Errorf("unexpected request")
}
}),
}
tf.ClientConfigVal = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: scheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
options := &AttachOptions{
StreamOptions: exec.StreamOptions{
Stdin: test.stdin,
TTY: test.tty,
ContainerName: test.container,
IOStreams: streams,
},
Attach: &fakeRemoteAttach{},
GetPodTimeout: 1000,
}
options.restClientGetter = tf
options.Namespace = "test"
options.Resources = []string{"foo"}
options.Builder = tf.NewBuilder
options.AttachablePodFn = fakeAttachablePodFn(test.pod)
options.AttachFunc = func(opts *AttachOptions, containerToAttach *corev1.Container, raw bool, sizeQueue remotecommand.TerminalSizeQueue) func() error {
return func() error {
u, err := url.Parse("http://foo.bar")
if err != nil {
return err
}
return options.Attach.Attach("POST", u, nil, nil, nil, nil, raw, sizeQueue)
}
}
if err := options.Run(); err != nil {
t.Fatal(err)
}
if test.stdin && test.tty {
if !test.pod.Spec.Containers[0].TTY {
if !strings.Contains(bufErr.String(), test.expectedErr) {
t.Errorf("%s: Expected TTY fallback warning for attach request: %s", test.name, bufErr.String())
return
}
}
}
})
}
}
func attachPod() *corev1.Pod {
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyAlways,
DNSPolicy: corev1.DNSClusterFirst,
Containers: []corev1.Container{
{
Name: "bar",
},
},
InitContainers: []corev1.Container{
{
Name: "initfoo",
},
},
},
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
},
}
}