Merge pull request #66519 from juanvallejo/jvallejo/switch-attch-externals

Automatic merge from submit-queue (batch tested with PRs 65730, 66615, 66684, 66519, 66510). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

update attach to use external objs

**Release note**:
```release-note
NONE
```

Updates attach command to use external versions.

cc @deads2k @soltysh
This commit is contained in:
Kubernetes Submit Queue 2018-08-01 15:52:16 -07:00 committed by GitHub
commit 9c58985a25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 714 additions and 554 deletions

View File

@ -59,7 +59,7 @@ go_library(
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/certificates:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/validation:go_default_library",
"//pkg/apis/core/v1:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/batch/internalversion:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
@ -196,7 +196,6 @@ go_test(
"//pkg/apis/batch:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/kubectl/cmd/create:go_default_library",
"//pkg/kubectl/cmd/testing:go_default_library",
"//pkg/kubectl/cmd/util:go_default_library",

View File

@ -17,7 +17,6 @@ limitations under the License.
package cmd
import (
"errors"
"fmt"
"io"
"net/url"
@ -26,17 +25,17 @@ import (
"github.com/golang/glog"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)
@ -62,14 +61,40 @@ const (
defaultPodLogsTimeout = 20 * time.Second
)
func NewCmdAttach(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
o := &AttachOptions{
// AttachOptions declare the arguments accepted by the Exec command
type AttachOptions struct {
StreamOptions
// whether to disable use of standard error when streaming output from tty
DisableStderr bool
CommandName string
SuggestedCmdUsage string
Pod *apiv1.Pod
AttachFunc func(*AttachOptions, *apiv1.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: StreamOptions{
IOStreams: streams,
},
Attach: &DefaultRemoteAttach{},
}
}
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,
@ -94,6 +119,29 @@ 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 *apiv1.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(&apiv1.PodAttachOptions{
Container: containerToAttach.Name,
Stdin: o.Stdin,
Stdout: o.Out != nil,
Stderr: !o.DisableStderr,
TTY: raw,
}, legacyscheme.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{}
@ -111,63 +159,24 @@ func (*DefaultRemoteAttach) Attach(method string, url *url.URL, config *restclie
})
}
// AttachOptions declare the arguments accepted by the Exec command
type AttachOptions struct {
StreamOptions
CommandName string
SuggestedCmdUsage string
Pod *api.Pod
Attach RemoteAttach
PodClient coreclient.PodsGetter
GetPodTimeout time.Duration
Config *restclient.Config
}
// Complete verifies command line arguments and loads data from the command environment
func (p *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn []string) error {
if len(argsIn) == 0 {
return cmdutil.UsageErrorf(cmd, "at least 1 argument is required for attach")
}
if len(argsIn) > 2 {
return cmdutil.UsageErrorf(cmd, "expected POD, TYPE/NAME, or TYPE NAME, (at most 2 arguments) saw %d: %v", len(argsIn), argsIn)
}
namespace, _, err := f.ToRawKubeConfigLoader().Namespace()
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
}
p.GetPodTimeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd)
o.AttachablePodFn = polymorphichelpers.AttachablePodForObjectFn
o.GetPodTimeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd)
if err != nil {
return cmdutil.UsageErrorf(cmd, err.Error())
}
builder := f.NewBuilder().
WithScheme(legacyscheme.Scheme).
NamespaceParam(namespace).DefaultNamespace()
switch len(argsIn) {
case 1:
builder.ResourceNames("pods", argsIn[0])
case 2:
builder.ResourceNames(argsIn[0], argsIn[1])
}
obj, err := builder.Do().Object()
if err != nil {
return err
}
attachablePod, err := polymorphichelpers.AttachablePodForObjectFn(f, obj, p.GetPodTimeout)
if err != nil {
return err
}
p.PodName = attachablePod.Name
p.Namespace = namespace
o.Builder = f.NewBuilder
o.Resources = args
o.restClientGetter = f
fullCmdName := ""
cmdParent := cmd.Parent()
@ -175,82 +184,87 @@ func (p *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn [
fullCmdName = cmdParent.CommandPath()
}
if len(fullCmdName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "describe") {
p.SuggestedCmdUsage = fmt.Sprintf("Use '%s describe pod/%s -n %s' to see all of the containers in this pod.", fullCmdName, p.PodName, p.Namespace)
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
}
p.Config = config
o.Config = config
clientset, err := f.ClientSet()
if err != nil {
return err
}
o.AttachFunc = defaultAttachFunc
p.PodClient = clientset.Core()
if p.CommandName == "" {
p.CommandName = cmd.CommandPath()
if o.CommandName == "" {
o.CommandName = cmd.CommandPath()
}
return nil
}
// Validate checks that the provided attach options are specified.
func (p *AttachOptions) Validate() error {
allErrs := []error{}
if len(p.PodName) == 0 {
allErrs = append(allErrs, errors.New("pod name must be specified"))
func (o *AttachOptions) Validate() error {
if len(o.Resources) == 0 {
return fmt.Errorf("at least 1 argument is required for attach")
}
if p.Out == nil || p.ErrOut == nil {
allErrs = append(allErrs, errors.New("both output and error output must be provided"))
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 p.Attach == nil || p.PodClient == nil || p.Config == nil {
allErrs = append(allErrs, errors.New("client, client config, and attach must be provided"))
if o.GetPodTimeout <= 0 {
return fmt.Errorf("--pod-running-timeout must be higher than zero")
}
return utilerrors.NewAggregate(allErrs)
return nil
}
// Run executes a validated remote execution against a pod.
func (p *AttachOptions) Run() error {
if p.Pod == nil {
pod, err := p.PodClient.Pods(p.Namespace).Get(p.PodName, metav1.GetOptions{})
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
}
if pod.Status.Phase == api.PodSucceeded || pod.Status.Phase == api.PodFailed {
return fmt.Errorf("cannot attach a container in a completed pod; current phase is %s", pod.Status.Phase)
o.Pod, err = o.findAttachablePod(obj)
if err != nil {
return err
}
p.Pod = pod
if o.Pod.Status.Phase == apiv1.PodSucceeded || o.Pod.Status.Phase == apiv1.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
}
pod := p.Pod
// check for TTY
containerToAttach, err := p.containerToAttachTo(pod)
containerToAttach, err := o.containerToAttachTo(o.Pod)
if err != nil {
return fmt.Errorf("cannot attach to the container: %v", err)
}
if p.TTY && !containerToAttach.TTY {
p.TTY = false
if p.ErrOut != nil {
fmt.Fprintf(p.ErrOut, "Unable to use a TTY - container %s did not allocate one\n", containerToAttach.Name)
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 !p.TTY && containerToAttach.TTY {
} 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"
p.TTY = true
o.TTY = true
}
// ensure we can recover the terminal while attached
t := p.setupTTY()
// save p.Err so we can print the command prompt message below
stderr := p.ErrOut
t := o.setupTTY()
var sizeQueue remotecommand.TerminalSizeQueue
if t.Raw {
@ -265,66 +279,52 @@ func (p *AttachOptions) Run() error {
sizeQueue = t.MonitorSize(&sizePlusOne, size)
}
// unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is
// true
p.ErrOut = nil
o.DisableStderr = true
}
fn := func() error {
restClient, err := restclient.RESTClientFor(p.Config)
if err != nil {
return err
if !o.Quiet {
fmt.Fprintln(o.ErrOut, "If you don't see a command prompt, try pressing enter.")
}
// TODO: consider abstracting into a client invocation or client helper
req := restClient.Post().
Resource("pods").
Name(pod.Name).
Namespace(pod.Namespace).
SubResource("attach")
req.VersionedParams(&api.PodAttachOptions{
Container: containerToAttach.Name,
Stdin: p.Stdin,
Stdout: p.Out != nil,
Stderr: p.ErrOut != nil,
TTY: t.Raw,
}, legacyscheme.ParameterCodec)
return p.Attach.Attach("POST", req.URL(), p.Config, p.In, p.Out, p.ErrOut, t.Raw, sizeQueue)
}
if !p.Quiet && stderr != nil {
fmt.Fprintln(stderr, "If you don't see a command prompt, try pressing enter.")
}
if err := t.Safe(fn); err != nil {
if err := t.Safe(o.AttachFunc(o, containerToAttach, t.Raw, sizeQueue)); err != nil {
return err
}
if p.Stdin && t.Raw && pod.Spec.RestartPolicy == api.RestartPolicyAlways {
fmt.Fprintf(p.Out, "Session ended, resume using '%s %s -c %s -i -t' command when the pod is running\n", p.CommandName, pod.Name, containerToAttach.Name)
if o.Stdin && t.Raw && o.Pod.Spec.RestartPolicy == apiv1.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) (*apiv1.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 (p *AttachOptions) containerToAttachTo(pod *api.Pod) (*api.Container, error) {
if len(p.ContainerName) > 0 {
func (o *AttachOptions) containerToAttachTo(pod *apiv1.Pod) (*apiv1.Container, error) {
if len(o.ContainerName) > 0 {
for i := range pod.Spec.Containers {
if pod.Spec.Containers[i].Name == p.ContainerName {
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 == p.ContainerName {
if pod.Spec.InitContainers[i].Name == o.ContainerName {
return &pod.Spec.InitContainers[i], nil
}
}
return nil, fmt.Errorf("container not found (%s)", p.ContainerName)
return nil, fmt.Errorf("container not found (%s)", o.ContainerName)
}
if len(p.SuggestedCmdUsage) > 0 {
fmt.Fprintf(p.ErrOut, "Defaulting container name to %s.\n", pod.Spec.Containers[0].Name)
fmt.Fprintf(p.ErrOut, "%s\n", p.SuggestedCmdUsage)
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)
}
glog.V(4).Infof("defaulting container name to %s", pod.Spec.Containers[0].Name)
@ -332,8 +332,8 @@ func (p *AttachOptions) containerToAttachTo(pod *api.Pod) (*api.Container, error
}
// GetContainerName returns the name of the container to attach to, with a fallback.
func (p *AttachOptions) GetContainerName(pod *api.Pod) (string, error) {
c, err := p.containerToAttachTo(pod)
func (o *AttachOptions) GetContainerName(pod *apiv1.Pod) (string, error) {
c, err := o.containerToAttachTo(pod)
if err != nil {
return "", err
}

View File

@ -25,8 +25,7 @@ import (
"testing"
"time"
"github.com/spf13/cobra"
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"
@ -34,10 +33,9 @@ import (
"k8s.io/client-go/rest/fake"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
@ -53,130 +51,136 @@ func (f *fakeRemoteAttach) Attach(method string, url *url.URL, config *restclien
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 {
args []string
p *AttachOptions
name string
expectError bool
expectedPod string
expectedContainer string
timeout time.Duration
obj runtime.Object
args []string
options *AttachOptions
expectError string
expectedPodName string
expectedContainerName string
obj *corev1.Pod
}{
{
p: &AttachOptions{},
expectError: true,
name: "empty",
timeout: 1,
options: &AttachOptions{GetPodTimeout: 1},
expectError: "at least 1 argument is required",
},
{
p: &AttachOptions{},
args: []string{"one", "two", "three"},
expectError: true,
name: "too many args",
timeout: 2,
options: &AttachOptions{GetPodTimeout: 2},
args: []string{"one", "two", "three"},
expectError: "at most 2 arguments",
},
{
p: &AttachOptions{},
args: []string{"foo"},
expectedPod: "foo",
name: "no container, no flags",
options: &AttachOptions{GetPodTimeout: defaultPodLogsTimeout},
args: []string{"foo"},
expectedPodName: "foo",
expectedContainerName: "bar",
obj: attachPod(),
timeout: defaultPodLogsTimeout,
},
{
p: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "bar"}},
args: []string{"foo"},
expectedPod: "foo",
expectedContainer: "bar",
name: "container in flag",
obj: attachPod(),
timeout: 10000000,
},
{
p: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "initfoo"}},
options: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "bar"}, GetPodTimeout: 10000000},
args: []string{"foo"},
expectedPod: "foo",
expectedContainer: "initfoo",
expectedPodName: "foo",
expectedContainerName: "bar",
obj: attachPod(),
},
{
name: "init container in flag",
options: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "initfoo"}, GetPodTimeout: 30},
args: []string{"foo"},
expectedPodName: "foo",
expectedContainerName: "initfoo",
obj: attachPod(),
timeout: 30,
},
{
p: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "bar"}},
args: []string{"foo", "-c", "wrong"},
expectError: true,
name: "non-existing container in flag",
name: "non-existing container",
options: &AttachOptions{StreamOptions: StreamOptions{ContainerName: "wrong"}, GetPodTimeout: 10},
args: []string{"foo"},
expectedPodName: "foo",
expectError: "container not found",
obj: attachPod(),
timeout: 10,
},
{
p: &AttachOptions{},
args: []string{"pods", "foo"},
expectedPod: "foo",
name: "no container, no flags, pods and name",
options: &AttachOptions{GetPodTimeout: 10000},
args: []string{"pods", "foo"},
expectedPodName: "foo",
expectedContainerName: "bar",
obj: attachPod(),
timeout: 10000,
},
{
p: &AttachOptions{},
args: []string{"pod/foo"},
expectedPod: "foo",
name: "no container, no flags, pod/name",
obj: attachPod(),
timeout: 1,
},
{
p: &AttachOptions{},
args: []string{"pod/foo"},
expectedPod: "foo",
name: "invalid get pod timeout value",
options: &AttachOptions{GetPodTimeout: 0},
args: []string{"pod/foo"},
expectedPodName: "foo",
expectedContainerName: "bar",
obj: attachPod(),
expectError: true,
timeout: 0,
expectError: "must be higher than zero",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
// setup opts to fetch our test pod
test.options.AttachablePodFn = fakeAttachablePodFn(test.obj)
test.options.Resources = test.args
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
GroupVersion: schema.GroupVersion{Group: "", Version: "v1"},
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
if test.obj != nil {
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, test.obj)}, nil
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 nil, nil
}),
}
tf.ClientConfigVal = defaultClientConfig()
cmd := &cobra.Command{}
options := test.p
cmdutil.AddPodRunningTimeoutFlag(cmd, test.timeout)
err := options.Complete(tf, cmd, test.args)
if test.expectError && err == nil {
t.Errorf("%s: unexpected non-error", test.name)
}
if !test.expectError && err != nil {
t.Errorf("%s: unexpected error: %v", test.name, err)
}
if err != nil {
return
}
if options.PodName != test.expectedPod {
t.Errorf("%s: expected: %s, got: %s", test.name, test.expectedPod, options.PodName)
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)
}
if options.ContainerName != test.expectedContainer {
t.Errorf("%s: expected: %s, got: %s", test.name, test.expectedContainer, options.ContainerName)
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)
}
})
}
@ -186,7 +190,7 @@ func TestAttach(t *testing.T) {
version := "v1"
tests := []struct {
name, version, podPath, fetchPodPath, attachPath, container string
pod *api.Pod
pod *corev1.Pod
remoteAttachErr bool
exepctedErr string
}{
@ -247,11 +251,12 @@ func TestAttach(t *testing.T) {
}),
}
tf.ClientConfigVal = &restclient.Config{APIPath: "/api", ContentConfig: restclient.ContentConfig{NegotiatedSerializer: legacyscheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
remoteAttach := &fakeRemoteAttach{}
if test.remoteAttachErr {
remoteAttach.err = fmt.Errorf("attach error")
}
params := &AttachOptions{
options := &AttachOptions{
StreamOptions: StreamOptions{
ContainerName: test.container,
IOStreams: genericclioptions.NewTestIOStreamsDiscard(),
@ -259,12 +264,24 @@ func TestAttach(t *testing.T) {
Attach: remoteAttach,
GetPodTimeout: 1000,
}
cmd := &cobra.Command{}
cmdutil.AddPodRunningTimeoutFlag(cmd, 1000)
if err := params.Complete(tf, cmd, []string{"foo"}); err != nil {
t.Fatal(err)
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
}
err := params.Run()
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
@ -294,7 +311,7 @@ func TestAttachWarnings(t *testing.T) {
version := "v1"
tests := []struct {
name, container, version, podPath, fetchPodPath, expectedErr string
pod *api.Pod
pod *corev1.Pod
stdin, tty bool
}{
{
@ -313,6 +330,8 @@ func TestAttachWarnings(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
streams, _, _, bufErr := genericclioptions.NewTestIOStreams()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
@ -328,30 +347,42 @@ func TestAttachWarnings(t *testing.T) {
body := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", test.name, req.Method, req.URL, req)
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: legacyscheme.Codecs, GroupVersion: &schema.GroupVersion{Version: test.version}}}
streams, _, _, bufErr := genericclioptions.NewTestIOStreams()
ex := &fakeRemoteAttach{}
params := &AttachOptions{
options := &AttachOptions{
StreamOptions: StreamOptions{
ContainerName: test.container,
IOStreams: streams,
Stdin: test.stdin,
TTY: test.tty,
ContainerName: test.container,
IOStreams: streams,
},
Attach: ex,
Attach: &fakeRemoteAttach{},
GetPodTimeout: 1000,
}
cmd := &cobra.Command{}
cmdutil.AddPodRunningTimeoutFlag(cmd, 1000)
if err := params.Complete(tf, cmd, []string{"foo"}); err != nil {
t.Fatal(err)
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
}
if err := params.Run(); err != nil {
return options.Attach.Attach("POST", u, nil, nil, nil, nil, raw, sizeQueue)
}
}
if err := options.Run(); err != nil {
t.Fatal(err)
}
@ -367,25 +398,25 @@ func TestAttachWarnings(t *testing.T) {
}
}
func attachPod() *api.Pod {
return &api.Pod{
func attachPod() *corev1.Pod {
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyAlways,
DNSPolicy: corev1.DNSClusterFirst,
Containers: []corev1.Container{
{
Name: "bar",
},
},
InitContainers: []api.Container{
InitContainers: []corev1.Container{
{
Name: "initfoo",
},
},
},
Status: api.PodStatus{
Phase: api.PodRunning,
Status: corev1.PodStatus{
Phase: corev1.PodRunning,
},
}
}

View File

@ -25,11 +25,10 @@ import (
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/rest"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
@ -80,6 +79,24 @@ type LogsOptions struct {
ResourceArg string
AllContainers bool
Options runtime.Object
Resources []string
ConsumeRequestFn func(*rest.Request, io.Writer) error
// PodLogOptions
SinceTime string
SinceSeconds time.Duration
Follow bool
Previous bool
Timestamps bool
LimitBytes int64
Tail int64
Container string
// whether or not a container name was given via --container
ContainerNameSpecified bool
Interactive bool
Selector string
Object runtime.Object
GetPodTimeout time.Duration
@ -93,6 +110,7 @@ func NewLogsOptions(streams genericclioptions.IOStreams, allContainers bool) *Lo
return &LogsOptions{
IOStreams: streams,
AllContainers: allContainers,
Tail: -1,
}
}
@ -119,40 +137,74 @@ func NewCmdLogs(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C
Aliases: []string{"log"},
}
cmd.Flags().BoolVar(&o.AllContainers, "all-containers", o.AllContainers, "Get all containers's logs in the pod(s).")
cmd.Flags().BoolP("follow", "f", false, "Specify if the logs should be streamed.")
cmd.Flags().Bool("timestamps", false, "Include timestamps on each line in the log output")
cmd.Flags().Int64("limit-bytes", 0, "Maximum bytes of logs to return. Defaults to no limit.")
cmd.Flags().BoolP("previous", "p", false, "If true, print the logs for the previous instance of the container in a pod if it exists.")
cmd.Flags().Int64("tail", -1, "Lines of recent log file to display. Defaults to -1 with no selector, showing all log lines otherwise 10, if a selector is provided.")
cmd.Flags().String("since-time", "", i18n.T("Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used."))
cmd.Flags().Duration("since", 0, "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used.")
cmd.Flags().StringP("container", "c", "", "Print the logs of this container")
cmd.Flags().Bool("interactive", false, "If true, prompt the user for input when required.")
cmd.Flags().BoolVarP(&o.Follow, "follow", "f", o.Follow, "Specify if the logs should be streamed.")
cmd.Flags().BoolVar(&o.Timestamps, "timestamps", o.Timestamps, "Include timestamps on each line in the log output")
cmd.Flags().Int64Var(&o.LimitBytes, "limit-bytes", o.LimitBytes, "Maximum bytes of logs to return. Defaults to no limit.")
cmd.Flags().BoolVarP(&o.Previous, "previous", "p", o.Previous, "If true, print the logs for the previous instance of the container in a pod if it exists.")
cmd.Flags().Int64Var(&o.Tail, "tail", o.Tail, "Lines of recent log file to display. Defaults to -1 with no selector, showing all log lines otherwise 10, if a selector is provided.")
cmd.Flags().StringVar(&o.SinceTime, "since-time", o.SinceTime, i18n.T("Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used."))
cmd.Flags().DurationVar(&o.SinceSeconds, "since", o.SinceSeconds, "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used.")
cmd.Flags().StringVarP(&o.Container, "container", "c", o.Container, "Print the logs of this container")
cmd.Flags().BoolVar(&o.Interactive, "interactive", o.Interactive, "If true, prompt the user for input when required.")
cmd.Flags().MarkDeprecated("interactive", "This flag is no longer respected and there is no replacement.")
cmdutil.AddPodRunningTimeoutFlag(cmd, defaultPodLogsTimeout)
cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on.")
cmd.Flags().StringVarP(&o.Selector, "selector", "l", o.Selector, "Selector (label query) to filter on.")
return cmd
}
func (o *LogsOptions) ToLogOptions() (*corev1.PodLogOptions, error) {
logOptions := &corev1.PodLogOptions{
Container: o.Container,
Follow: o.Follow,
Previous: o.Previous,
Timestamps: o.Timestamps,
}
if len(o.SinceTime) > 0 {
t, err := util.ParseRFC3339(o.SinceTime, metav1.Now)
if err != nil {
return nil, err
}
logOptions.SinceTime = &t
}
if o.LimitBytes != 0 {
logOptions.LimitBytes = &o.LimitBytes
}
if o.SinceSeconds != 0 {
// round up to the nearest second
sec := int64(o.SinceSeconds.Round(time.Second).Seconds())
logOptions.SinceSeconds = &sec
}
if len(o.Selector) > 0 && o.Tail != -1 {
logOptions.TailLines = &selectorTail
} else if o.Tail != -1 {
logOptions.TailLines = &o.Tail
}
return logOptions, nil
}
func (o *LogsOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
containerName := cmdutil.GetFlagString(cmd, "container")
selector := cmdutil.GetFlagString(cmd, "selector")
o.ContainerNameSpecified = cmd.Flag("container").Changed
o.Resources = args
switch len(args) {
case 0:
if len(selector) == 0 {
if len(o.Selector) == 0 {
return cmdutil.UsageErrorf(cmd, "%s", logsUsageStr)
}
case 1:
o.ResourceArg = args[0]
if len(selector) != 0 {
if len(o.Selector) != 0 {
return cmdutil.UsageErrorf(cmd, "only a selector (-l) or a POD name is allowed")
}
case 2:
if cmd.Flag("container").Changed {
return cmdutil.UsageErrorf(cmd, "only one of -c or an inline [CONTAINER] arg is allowed")
}
o.ResourceArg = args[0]
containerName = args[1]
o.Container = args[1]
default:
return cmdutil.UsageErrorf(cmd, "%s", logsUsageStr)
}
@ -162,48 +214,21 @@ func (o *LogsOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []str
return err
}
logOptions := &api.PodLogOptions{
Container: containerName,
Follow: cmdutil.GetFlagBool(cmd, "follow"),
Previous: cmdutil.GetFlagBool(cmd, "previous"),
Timestamps: cmdutil.GetFlagBool(cmd, "timestamps"),
}
if sinceTime := cmdutil.GetFlagString(cmd, "since-time"); len(sinceTime) > 0 {
t, err := util.ParseRFC3339(sinceTime, metav1.Now)
if err != nil {
return err
}
logOptions.SinceTime = &t
}
if limit := cmdutil.GetFlagInt64(cmd, "limit-bytes"); limit != 0 {
logOptions.LimitBytes = &limit
}
tail := cmdutil.GetFlagInt64(cmd, "tail")
if tail != -1 {
logOptions.TailLines = &tail
}
if sinceSeconds := cmdutil.GetFlagDuration(cmd, "since"); sinceSeconds != 0 {
// round up to the nearest second
sec := int64(sinceSeconds.Round(time.Second).Seconds())
logOptions.SinceSeconds = &sec
}
o.ConsumeRequestFn = consumeRequest
o.GetPodTimeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd)
if err != nil {
return err
}
o.Options = logOptions
o.Options, err = o.ToLogOptions()
if err != nil {
return err
}
o.RESTClientGetter = f
o.LogsForObject = polymorphichelpers.LogsForObjectFn
if len(selector) != 0 {
if logOptions.Follow {
return cmdutil.UsageErrorf(cmd, "only one of follow (-f) or selector (-l) is allowed")
}
if logOptions.TailLines == nil && tail != -1 {
logOptions.TailLines = &selectorTail
}
}
if o.Object == nil {
builder := f.NewBuilder().
WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...).
@ -212,14 +237,14 @@ func (o *LogsOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []str
if o.ResourceArg != "" {
builder.ResourceNames("pods", o.ResourceArg)
}
if selector != "" {
builder.ResourceTypes("pods").LabelSelectorParam(selector)
if o.Selector != "" {
builder.ResourceTypes("pods").LabelSelectorParam(o.Selector)
}
infos, err := builder.Do().Infos()
if err != nil {
return err
}
if selector == "" && len(infos) != 1 {
if o.Selector == "" && len(infos) != 1 {
return errors.New("expected a resource")
}
o.Object = infos[0].Object
@ -229,15 +254,36 @@ func (o *LogsOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []str
}
func (o LogsOptions) Validate() error {
logsOptions, ok := o.Options.(*api.PodLogOptions)
if o.Follow && len(o.Selector) > 0 {
return fmt.Errorf("only one of follow (-f) or selector (-l) is allowed")
}
if len(o.SinceTime) > 0 && o.SinceSeconds != 0 {
return fmt.Errorf("at most one of `sinceTime` or `sinceSeconds` may be specified")
}
logsOptions, ok := o.Options.(*corev1.PodLogOptions)
if !ok {
return errors.New("unexpected logs options object")
}
if o.AllContainers && len(logsOptions.Container) > 0 {
return fmt.Errorf("--all-containers=true should not be specified with container name %s", logsOptions.Container)
}
if errs := validation.ValidatePodLogOptions(logsOptions); len(errs) > 0 {
return errs.ToAggregate()
if o.ContainerNameSpecified && len(o.Resources) == 2 {
return fmt.Errorf("only one of -c or an inline [CONTAINER] arg is allowed")
}
if o.LimitBytes < 0 {
return fmt.Errorf("--limit-bytes must be greater than 0")
}
if logsOptions.SinceSeconds != nil && *logsOptions.SinceSeconds < int64(0) {
return fmt.Errorf("--since must be greater than 0")
}
if logsOptions.TailLines != nil && *logsOptions.TailLines < 0 {
return fmt.Errorf("TailLines must be greater than or equal to 0")
}
return nil
@ -251,7 +297,7 @@ func (o LogsOptions) RunLogs() error {
}
for _, request := range requests {
if err := consumeRequest(request, o.Out); err != nil {
if err := o.ConsumeRequestFn(request, o.Out); err != nil {
return err
}
}

View File

@ -17,29 +17,20 @@ limitations under the License.
package cmd
import (
"bytes"
"errors"
"io/ioutil"
"net/http"
"fmt"
"io"
"strings"
"testing"
"time"
"github.com/spf13/cobra"
"fmt"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/rest/fake"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
"k8s.io/kubernetes/pkg/kubectl/polymorphichelpers"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
func TestLog(t *testing.T) {
@ -49,9 +40,6 @@ func TestLog(t *testing.T) {
}{
{
name: "v1 - pod log",
version: "v1",
podPath: "/namespaces/test/pods/foo",
logPath: "/api/v1/namespaces/test/pods/foo/log",
pod: testPod(),
},
}
@ -61,41 +49,19 @@ func TestLog(t *testing.T) {
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := legacyscheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := legacyscheme.Codecs
tf.Client = &fake.RESTClient{
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 := objBody(codec, test.pod)
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
case p == test.logPath && m == "GET":
body := ioutil.NopCloser(bytes.NewBufferString(logContent))
return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: body}, nil
default:
t.Errorf("%s: unexpected request: %#v\n%#v", test.name, req.URL, req)
return nil, nil
}
}),
}
tf.ClientConfigVal = defaultClientConfig()
oldLogFn := polymorphichelpers.LogsForObjectFn
defer func() {
polymorphichelpers.LogsForObjectFn = oldLogFn
}()
clientset, err := tf.ClientSet()
if err != nil {
t.Fatal(err)
}
polymorphichelpers.LogsForObjectFn = logTestMock{client: clientset}.logsForObject
streams, _, buf, _ := genericclioptions.NewTestIOStreams()
cmd := NewCmdLogs(tf, streams)
cmd.Flags().Set("namespace", "test")
cmd.Run(cmd, []string{"foo"})
mock := &logTestMock{
logsContent: logContent,
}
opts := NewLogsOptions(streams, false)
opts.Namespace = "test"
opts.Object = test.pod
opts.Options = &corev1.PodLogOptions{}
opts.LogsForObject = mock.mockLogsForObject
opts.ConsumeRequestFn = mock.mockConsumeRequest
opts.RunLogs()
if buf.String() != logContent {
t.Errorf("%s: did not get expected log content. Got: %s", test.name, buf.String())
@ -119,66 +85,152 @@ func testPod() *api.Pod {
}
}
func TestValidateLogFlags(t *testing.T) {
func TestValidateLogOptions(t *testing.T) {
f := cmdtesting.NewTestFactory()
defer f.Cleanup()
f.WithNamespace("")
tests := []struct {
name string
flags map[string]string
args []string
opts func(genericclioptions.IOStreams) *LogsOptions
expected string
}{
{
name: "since & since-time",
flags: map[string]string{"since": "1h", "since-time": "2006-01-02T15:04:05Z"},
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
o := NewLogsOptions(streams, false)
o.SinceSeconds = time.Hour
o.SinceTime = "2006-01-02T15:04:05Z"
var err error
o.Options, err = o.ToLogOptions()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
return o
},
args: []string{"foo"},
expected: "at most one of `sinceTime` or `sinceSeconds` may be specified",
},
{
name: "negative since-time",
flags: map[string]string{"since": "-1s"},
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
o := NewLogsOptions(streams, false)
o.SinceSeconds = -1 * time.Second
var err error
o.Options, err = o.ToLogOptions()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
return o
},
args: []string{"foo"},
expected: "must be greater than 0",
},
{
name: "negative limit-bytes",
flags: map[string]string{"limit-bytes": "-100"},
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
o := NewLogsOptions(streams, false)
o.LimitBytes = -100
var err error
o.Options, err = o.ToLogOptions()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
return o
},
args: []string{"foo"},
expected: "must be greater than 0",
},
{
name: "negative tail",
flags: map[string]string{"tail": "-100"},
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
o := NewLogsOptions(streams, false)
o.Tail = -100
var err error
o.Options, err = o.ToLogOptions()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
return o
},
args: []string{"foo"},
expected: "must be greater than or equal to 0",
},
{
name: "container name combined with --all-containers",
flags: map[string]string{"all-containers": "true"},
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
o := NewLogsOptions(streams, true)
o.Container = "my-container"
var err error
o.Options, err = o.ToLogOptions()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
return o
},
args: []string{"my-pod", "my-container"},
expected: "--all-containers=true should not be specified with container",
},
{
name: "container name combined with second argument",
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
o := NewLogsOptions(streams, false)
o.Container = "my-container"
o.ContainerNameSpecified = true
var err error
o.Options, err = o.ToLogOptions()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
return o
},
args: []string{"my-pod", "my-container"},
expected: "only one of -c or an inline",
},
{
name: "follow and selector conflict",
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
o := NewLogsOptions(streams, false)
o.Selector = "foo"
o.Follow = true
var err error
o.Options, err = o.ToLogOptions()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
return o
},
expected: "only one of follow (-f) or selector (-l) is allowed",
},
}
for _, test := range tests {
streams := genericclioptions.NewTestIOStreamsDiscard()
cmd := NewCmdLogs(f, streams)
out := ""
for flag, value := range test.flags {
cmd.Flags().Set(flag, value)
}
// checkErr breaks tests in case of errors, plus we just
// need to check errors returned by the command validation
o := NewLogsOptions(streams, test.flags["all-containers"] == "true")
cmd.Run = func(cmd *cobra.Command, args []string) {
o.Complete(f, cmd, args)
out = o.Validate().Error()
}
cmd.Run(cmd, test.args)
if !strings.Contains(out, test.expected) {
t.Errorf("%s: expected to find:\n\t%s\nfound:\n\t%s\n", test.name, test.expected, out)
o := test.opts(streams)
o.Resources = test.args
err := o.Validate()
if err == nil {
t.Fatalf("expected error %q, got none", test.expected)
}
if !strings.Contains(err.Error(), test.expected) {
t.Errorf("%s: expected to find:\n\t%s\nfound:\n\t%s\n", test.name, test.expected, err.Error())
}
}
}
@ -190,49 +242,49 @@ func TestLogComplete(t *testing.T) {
tests := []struct {
name string
args []string
flags map[string]string
opts func(genericclioptions.IOStreams) *LogsOptions
expected string
}{
{
name: "No args case",
flags: map[string]string{"selector": ""},
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
return NewLogsOptions(streams, false)
},
expected: "'logs (POD | TYPE/NAME) [CONTAINER_NAME]'.\nPOD or TYPE/NAME is a required argument for the logs command",
},
{
name: "One args case",
args: []string{"foo"},
flags: map[string]string{"selector": "foo"},
expected: "only a selector (-l) or a POD name is allowed",
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
o := NewLogsOptions(streams, false)
o.Selector = "foo"
return o
},
{
name: "Two args case",
args: []string{"foo", "foo1"},
flags: map[string]string{"container": "foo1"},
expected: "only one of -c or an inline [CONTAINER] arg is allowed",
expected: "only a selector (-l) or a POD name is allowed",
},
{
name: "More than two args case",
args: []string{"foo", "foo1", "foo2"},
flags: map[string]string{"tail": "1"},
expected: "'logs (POD | TYPE/NAME) [CONTAINER_NAME]'.\nPOD or TYPE/NAME is a required argument for the logs command",
opts: func(streams genericclioptions.IOStreams) *LogsOptions {
o := NewLogsOptions(streams, false)
o.Tail = 1
return o
},
{
name: "follow and selecter conflict",
flags: map[string]string{"selector": "foo", "follow": "true"},
expected: "only one of follow (-f) or selector (-l) is allowed",
expected: "'logs (POD | TYPE/NAME) [CONTAINER_NAME]'.\nPOD or TYPE/NAME is a required argument for the logs command",
},
}
for _, test := range tests {
cmd := NewCmdLogs(f, genericclioptions.NewTestIOStreamsDiscard())
var err error
out := ""
for flag, value := range test.flags {
cmd.Flags().Set(flag, value)
}
// checkErr breaks tests in case of errors, plus we just
// need to check errors returned by the command validation
o := NewLogsOptions(genericclioptions.NewTestIOStreamsDiscard(), false)
err = o.Complete(f, cmd, test.args)
o := test.opts(genericclioptions.NewTestIOStreamsDiscard())
err := o.Complete(f, cmd, test.args)
if err == nil {
t.Fatalf("expected error %q, got none", test.expected)
}
out = err.Error()
if !strings.Contains(out, test.expected) {
t.Errorf("%s: expected to find:\n\t%s\nfound:\n\t%s\n", test.name, test.expected, out)
@ -241,17 +293,23 @@ func TestLogComplete(t *testing.T) {
}
type logTestMock struct {
client internalclientset.Interface
logsContent string
}
func (m logTestMock) logsForObject(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration, allContainers bool) ([]*restclient.Request, error) {
switch t := object.(type) {
func (l *logTestMock) mockConsumeRequest(req *restclient.Request, out io.Writer) error {
fmt.Fprintf(out, l.logsContent)
return nil
}
func (l *logTestMock) mockLogsForObject(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration, allContainers bool) ([]*restclient.Request, error) {
switch object.(type) {
case *api.Pod:
opts, ok := options.(*api.PodLogOptions)
_, ok := options.(*corev1.PodLogOptions)
if !ok {
return nil, errors.New("provided options object is not a PodLogOptions")
}
return []*restclient.Request{m.client.Core().Pods(t.Namespace).GetLogs(t.Name, opts)}, nil
return []*restclient.Request{{}}, nil
default:
return nil, fmt.Errorf("cannot get the logs from %T", object)
}

View File

@ -34,6 +34,7 @@ import (
"k8s.io/client-go/transport/spdy"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
apiv1 "k8s.io/kubernetes/pkg/apis/core/v1"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
@ -210,7 +211,13 @@ func (o *PortForwardOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, arg
// handle service port mapping to target port if needed
switch t := obj.(type) {
case *api.Service:
o.Ports, err = translateServicePortToTargetPort(args[1:], *t, *forwardablePod)
// TODO(juanvallejo): remove this once we convert this command to work with externals
internalPod := &api.Pod{}
if err := apiv1.Convert_v1_Pod_To_core_Pod(forwardablePod, internalPod, nil); err != nil {
return err
}
o.Ports, err = translateServicePortToTargetPort(args[1:], *t, *internalPod)
if err != nil {
return err
}

View File

@ -22,18 +22,20 @@ import (
"github.com/docker/distribution/reference"
"github.com/spf13/cobra"
"k8s.io/client-go/dynamic"
"github.com/golang/glog"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
coreclientv1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/kubernetes/pkg/api/legacyscheme"
api "k8s.io/kubernetes/pkg/apis/core"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
@ -377,27 +379,27 @@ func (o *RunOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
return err
}
opts.Config = config
opts.AttachFunc = defaultAttachFunc
clientset, err := f.ClientSet()
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return err
}
opts.PodClient = clientset.Core()
attachablePod, err := polymorphichelpers.AttachablePodForObjectFn(f, runObject.Object, opts.GetPodTimeout)
if err != nil {
return err
}
err = handleAttachPod(f, clientset.Core(), attachablePod.Namespace, attachablePod.Name, opts)
err = handleAttachPod(f, clientset.CoreV1(), attachablePod.Namespace, attachablePod.Name, opts)
if err != nil {
return err
}
var pod *api.Pod
var pod *corev1.Pod
leaveStdinOpen := o.LeaveStdinOpen
waitForExitCode := !leaveStdinOpen && restartPolicy == api.RestartPolicyNever
if waitForExitCode {
pod, err = waitForPod(clientset.Core(), attachablePod.Namespace, attachablePod.Name, kubectl.PodCompleted)
pod, err = waitForPod(clientset.CoreV1(), attachablePod.Namespace, attachablePod.Name, kubectl.PodCompleted)
if err != nil {
return err
}
@ -409,9 +411,9 @@ func (o *RunOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) e
}
switch pod.Status.Phase {
case api.PodSucceeded:
case corev1.PodSucceeded:
return nil
case api.PodFailed:
case corev1.PodFailed:
unknownRcErr := fmt.Errorf("pod %s/%s failed with unknown exit code", pod.Namespace, pod.Name)
if len(pod.Status.ContainerStatuses) == 0 || pod.Status.ContainerStatuses[0].State.Terminated == nil {
return unknownRcErr
@ -466,20 +468,20 @@ func (o *RunOptions) removeCreatedObjects(f cmdutil.Factory, createdObjects []*R
}
// waitForPod watches the given pod until the exitCondition is true
func waitForPod(podClient coreclient.PodsGetter, ns, name string, exitCondition watch.ConditionFunc) (*api.Pod, error) {
func waitForPod(podClient coreclientv1.PodsGetter, ns, name string, exitCondition watch.ConditionFunc) (*corev1.Pod, error) {
w, err := podClient.Pods(ns).Watch(metav1.SingleObject(metav1.ObjectMeta{Name: name}))
if err != nil {
return nil, err
}
intr := interrupt.New(nil, w.Stop)
var result *api.Pod
var result *corev1.Pod
err = intr.Run(func() error {
ev, err := watch.Until(0, w, func(ev watch.Event) (bool, error) {
return exitCondition(ev)
})
if ev != nil {
result = ev.Object.(*api.Pod)
result = ev.Object.(*corev1.Pod)
}
return err
})
@ -492,37 +494,39 @@ func waitForPod(podClient coreclient.PodsGetter, ns, name string, exitCondition
return result, err
}
func handleAttachPod(f cmdutil.Factory, podClient coreclient.PodsGetter, ns, name string, opts *AttachOptions) error {
func handleAttachPod(f cmdutil.Factory, podClient coreclientv1.PodsGetter, ns, name string, opts *AttachOptions) error {
pod, err := waitForPod(podClient, ns, name, kubectl.PodRunningAndReady)
if err != nil && err != kubectl.ErrPodCompleted {
return err
}
if pod.Status.Phase == api.PodSucceeded || pod.Status.Phase == api.PodFailed {
if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
return logOpts(f, pod, opts)
}
opts.PodClient = podClient
opts.Pod = pod
opts.PodName = name
opts.Namespace = ns
// TODO: opts.Run sets opts.Err to nil, we need to find a better way
stderr := opts.ErrOut
if opts.AttachFunc == nil {
opts.AttachFunc = defaultAttachFunc
}
if err := opts.Run(); err != nil {
fmt.Fprintf(stderr, "Error attaching, falling back to logs: %v\n", err)
fmt.Fprintf(opts.ErrOut, "Error attaching, falling back to logs: %v\n", err)
return logOpts(f, pod, opts)
}
return nil
}
// logOpts logs output from opts to the pods log.
func logOpts(restClientGetter genericclioptions.RESTClientGetter, pod *api.Pod, opts *AttachOptions) error {
func logOpts(restClientGetter genericclioptions.RESTClientGetter, pod *corev1.Pod, opts *AttachOptions) error {
ctrName, err := opts.GetContainerName(pod)
if err != nil {
return err
}
requests, err := polymorphichelpers.LogsForObjectFn(restClientGetter, pod, &api.PodLogOptions{Container: ctrName}, opts.GetPodTimeout, false)
requests, err := polymorphichelpers.LogsForObjectFn(restClientGetter, pod, &corev1.PodLogOptions{Container: ctrName}, opts.GetPodTimeout, false)
if err != nil {
return err
}

View File

@ -19,12 +19,14 @@ package kubectl
import (
"fmt"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/kubernetes/pkg/api/pod"
podv1 "k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/apis/apps"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/extensions"
@ -140,6 +142,13 @@ func PodRunning(event watch.Event) (bool, error) {
case api.PodFailed, api.PodSucceeded:
return false, ErrPodCompleted
}
case *corev1.Pod:
switch t.Status.Phase {
case corev1.PodRunning:
return true, nil
case corev1.PodFailed, corev1.PodSucceeded:
return false, ErrPodCompleted
}
}
return false, nil
}
@ -157,6 +166,11 @@ func PodCompleted(event watch.Event) (bool, error) {
case api.PodFailed, api.PodSucceeded:
return true, nil
}
case *corev1.Pod:
switch t.Status.Phase {
case corev1.PodFailed, corev1.PodSucceeded:
return true, nil
}
}
return false, nil
}
@ -177,6 +191,13 @@ func PodRunningAndReady(event watch.Event) (bool, error) {
case api.PodRunning:
return pod.IsPodReady(t), nil
}
case *corev1.Pod:
switch t.Status.Phase {
case corev1.PodFailed, corev1.PodSucceeded:
return false, ErrPodCompleted
case corev1.PodRunning:
return podv1.IsPodReady(t), nil
}
}
return false, nil
}

View File

@ -28,8 +28,6 @@ go_library(
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/v1:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/kubectl:go_default_library",
"//pkg/kubectl/genericclioptions:go_default_library",
@ -49,6 +47,7 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
],
)
@ -73,7 +72,6 @@ go_test(
"//pkg/apis/batch:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/extensions:go_default_library",
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
"//pkg/controller:go_default_library",
"//staging/src/k8s.io/api/apps/v1:go_default_library",
"//staging/src/k8s.io/api/apps/v1beta1:go_default_library",
@ -90,6 +88,7 @@ go_test(
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/watch:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
"//staging/src/k8s.io/client-go/testing:go_default_library",
],
)

View File

@ -24,30 +24,29 @@ import (
"k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
api "k8s.io/kubernetes/pkg/apis/core"
apiv1 "k8s.io/kubernetes/pkg/apis/core/v1"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
// attachablePodForObject returns the pod to which to attach given an object.
func attachablePodForObject(restClientGetter genericclioptions.RESTClientGetter, object runtime.Object, timeout time.Duration) (*api.Pod, error) {
func attachablePodForObject(restClientGetter genericclioptions.RESTClientGetter, object runtime.Object, timeout time.Duration) (*corev1.Pod, error) {
switch t := object.(type) {
case *api.Pod:
return t, nil
externalPod := &corev1.Pod{}
err := apiv1.Convert_core_Pod_To_v1_Pod(t, externalPod, nil)
return externalPod, err
case *corev1.Pod:
internalPod := &api.Pod{}
err := apiv1.Convert_v1_Pod_To_core_Pod(t, internalPod, nil)
return internalPod, err
return t, nil
}
clientConfig, err := restClientGetter.ToRESTConfig()
if err != nil {
return nil, err
}
clientset, err := internalclientset.NewForConfig(clientConfig)
clientset, err := corev1client.NewForConfig(clientConfig)
if err != nil {
return nil, err
}
@ -57,6 +56,6 @@ func attachablePodForObject(restClientGetter genericclioptions.RESTClientGetter,
return nil, fmt.Errorf("cannot attach to %T: %v", object, err)
}
sortBy := func(pods []*v1.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) }
pod, _, err := GetFirstPod(clientset.Core(), namespace, selector.String(), timeout, sortBy)
pod, _, err := GetFirstPod(clientset, namespace, selector.String(), timeout, sortBy)
return pod, err
}

View File

@ -32,17 +32,16 @@ import (
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
coreclient "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/batch"
api "k8s.io/kubernetes/pkg/apis/core"
apiv1 "k8s.io/kubernetes/pkg/apis/core/v1"
"k8s.io/kubernetes/pkg/apis/extensions"
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
)
// GetFirstPod returns a pod matching the namespace and label selector
// and the number of all pods that match the label selector.
func GetFirstPod(client coreclient.PodsGetter, namespace string, selector string, timeout time.Duration, sortBy func([]*v1.Pod) sort.Interface) (*api.Pod, int, error) {
func GetFirstPod(client coreclient.PodsGetter, namespace string, selector string, timeout time.Duration, sortBy func([]*v1.Pod) sort.Interface) (*v1.Pod, int, error) {
options := metav1.ListOptions{LabelSelector: selector}
podList, err := client.Pods(namespace).List(options)
@ -52,15 +51,11 @@ func GetFirstPod(client coreclient.PodsGetter, namespace string, selector string
pods := []*v1.Pod{}
for i := range podList.Items {
pod := podList.Items[i]
externalPod := &v1.Pod{}
apiv1.Convert_core_Pod_To_v1_Pod(&pod, externalPod, nil)
pods = append(pods, externalPod)
pods = append(pods, &pod)
}
if len(pods) > 0 {
sort.Sort(sortBy(pods))
internalPod := &api.Pod{}
apiv1.Convert_v1_Pod_To_core_Pod(pods[0], internalPod, nil)
return internalPod, len(podList.Items), nil
return pods[0], len(podList.Items), nil
}
// Watch until we observe a pod
@ -78,7 +73,7 @@ func GetFirstPod(client coreclient.PodsGetter, namespace string, selector string
if err != nil {
return nil, 0, err
}
pod, ok := event.Object.(*api.Pod)
pod, ok := event.Object.(*v1.Pod)
if !ok {
return nil, 0, fmt.Errorf("%#v is not a pod event", event)
}

View File

@ -22,15 +22,14 @@ import (
"testing"
"time"
"k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/watch"
fakeexternal "k8s.io/client-go/kubernetes/fake"
testcore "k8s.io/client-go/testing"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/kubernetes/pkg/controller"
)
@ -39,30 +38,30 @@ func TestGetFirstPod(t *testing.T) {
tests := []struct {
name string
podList *api.PodList
podList *corev1.PodList
watching []watch.Event
sortBy func([]*v1.Pod) sort.Interface
sortBy func([]*corev1.Pod) sort.Interface
expected *api.Pod
expected *corev1.Pod
expectedNum int
expectedErr bool
}{
{
name: "kubectl logs - two ready pods",
podList: newPodList(2, -1, -1, labelSet),
sortBy: func(pods []*v1.Pod) sort.Interface { return controller.ByLogging(pods) },
expected: &api.Pod{
sortBy: func(pods []*corev1.Pod) sort.Interface { return controller.ByLogging(pods) },
expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-1",
Namespace: metav1.NamespaceDefault,
CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC),
Labels: map[string]string{"test": "selector"},
},
Status: api.PodStatus{
Conditions: []api.PodCondition{
Status: corev1.PodStatus{
Conditions: []corev1.PodCondition{
{
Status: api.ConditionTrue,
Type: api.PodReady,
Status: corev1.ConditionTrue,
Type: corev1.PodReady,
},
},
},
@ -72,22 +71,22 @@ func TestGetFirstPod(t *testing.T) {
{
name: "kubectl logs - one unhealthy, one healthy",
podList: newPodList(2, -1, 1, labelSet),
sortBy: func(pods []*v1.Pod) sort.Interface { return controller.ByLogging(pods) },
expected: &api.Pod{
sortBy: func(pods []*corev1.Pod) sort.Interface { return controller.ByLogging(pods) },
expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-2",
Namespace: metav1.NamespaceDefault,
CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 1, 0, time.UTC),
Labels: map[string]string{"test": "selector"},
},
Status: api.PodStatus{
Conditions: []api.PodCondition{
Status: corev1.PodStatus{
Conditions: []corev1.PodCondition{
{
Status: api.ConditionTrue,
Type: api.PodReady,
Status: corev1.ConditionTrue,
Type: corev1.PodReady,
},
},
ContainerStatuses: []api.ContainerStatus{{RestartCount: 5}},
ContainerStatuses: []corev1.ContainerStatus{{RestartCount: 5}},
},
},
expectedNum: 2,
@ -95,19 +94,19 @@ func TestGetFirstPod(t *testing.T) {
{
name: "kubectl attach - two ready pods",
podList: newPodList(2, -1, -1, labelSet),
sortBy: func(pods []*v1.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) },
expected: &api.Pod{
sortBy: func(pods []*corev1.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) },
expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-1",
Namespace: metav1.NamespaceDefault,
CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC),
Labels: map[string]string{"test": "selector"},
},
Status: api.PodStatus{
Conditions: []api.PodCondition{
Status: corev1.PodStatus{
Conditions: []corev1.PodCondition{
{
Status: api.ConditionTrue,
Type: api.PodReady,
Status: corev1.ConditionTrue,
Type: corev1.PodReady,
},
},
},
@ -138,19 +137,19 @@ func TestGetFirstPod(t *testing.T) {
},
},
},
sortBy: func(pods []*v1.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) },
expected: &api.Pod{
sortBy: func(pods []*corev1.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) },
expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "pod-1",
Namespace: metav1.NamespaceDefault,
CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC),
Labels: map[string]string{"test": "selector"},
},
Status: api.PodStatus{
Conditions: []api.PodCondition{
Status: corev1.PodStatus{
Conditions: []corev1.PodCondition{
{
Status: api.ConditionTrue,
Type: api.PodReady,
Status: corev1.ConditionTrue,
Type: corev1.PodReady,
},
},
},
@ -161,7 +160,7 @@ func TestGetFirstPod(t *testing.T) {
for i := range tests {
test := tests[i]
fake := fake.NewSimpleClientset(test.podList)
fake := fakeexternal.NewSimpleClientset(test.podList)
if len(test.watching) > 0 {
watcher := watch.NewFake()
for _, event := range test.watching {
@ -196,21 +195,21 @@ func TestGetFirstPod(t *testing.T) {
}
}
func newPodList(count, isUnready, isUnhealthy int, labels map[string]string) *api.PodList {
pods := []api.Pod{}
func newPodList(count, isUnready, isUnhealthy int, labels map[string]string) *corev1.PodList {
pods := []corev1.Pod{}
for i := 0; i < count; i++ {
newPod := api.Pod{
newPod := corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("pod-%d", i+1),
Namespace: metav1.NamespaceDefault,
CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, i, 0, time.UTC),
Labels: labels,
},
Status: api.PodStatus{
Conditions: []api.PodCondition{
Status: corev1.PodStatus{
Conditions: []corev1.PodCondition{
{
Status: api.ConditionTrue,
Type: api.PodReady,
Status: corev1.ConditionTrue,
Type: corev1.PodReady,
},
},
},
@ -218,12 +217,12 @@ func newPodList(count, isUnready, isUnhealthy int, labels map[string]string) *ap
pods = append(pods, newPod)
}
if isUnready > -1 && isUnready < count {
pods[isUnready].Status.Conditions[0].Status = api.ConditionFalse
pods[isUnready].Status.Conditions[0].Status = corev1.ConditionFalse
}
if isUnhealthy > -1 && isUnhealthy < count {
pods[isUnhealthy].Status.ContainerStatuses = []api.ContainerStatus{{RestartCount: 5}}
pods[isUnhealthy].Status.ContainerStatuses = []corev1.ContainerStatus{{RestartCount: 5}}
}
return &api.PodList{
return &corev1.PodList{
Items: pods,
}
}

View File

@ -24,7 +24,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
@ -36,7 +35,7 @@ type LogsForObjectFunc func(restClientGetter genericclioptions.RESTClientGetter,
var LogsForObjectFn LogsForObjectFunc = logsForObject
// AttachableLogsForObjectFunc is a function type that can tell you how to get the pod for which to attach a given object
type AttachableLogsForObjectFunc func(restClientGetter genericclioptions.RESTClientGetter, object runtime.Object, timeout time.Duration) (*api.Pod, error)
type AttachableLogsForObjectFunc func(restClientGetter genericclioptions.RESTClientGetter, object runtime.Object, timeout time.Duration) (*v1.Pod, error)
// AttachablePodForObjectFn gives a way to easily override the function for unit testing if needed.
var AttachablePodForObjectFn AttachableLogsForObjectFunc = attachablePodForObject

View File

@ -26,9 +26,9 @@ import (
"k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest"
coreinternal "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/controller"
"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
)
@ -38,16 +38,18 @@ func logsForObject(restClientGetter genericclioptions.RESTClientGetter, object,
if err != nil {
return nil, err
}
clientset, err := internalclientset.NewForConfig(clientConfig)
clientset, err := corev1client.NewForConfig(clientConfig)
if err != nil {
return nil, err
}
return logsForObjectWithClient(clientset, object, options, timeout, allContainers)
}
// TODO: remove internal clientset once all callers use external versions
// this is split for easy test-ability
func logsForObjectWithClient(clientset internalclientset.Interface, object, options runtime.Object, timeout time.Duration, allContainers bool) ([]*rest.Request, error) {
opts, ok := options.(*coreinternal.PodLogOptions)
func logsForObjectWithClient(clientset corev1client.CoreV1Interface, object, options runtime.Object, timeout time.Duration, allContainers bool) ([]*rest.Request, error) {
opts, ok := options.(*corev1.PodLogOptions)
if !ok {
return nil, errors.New("provided options object is not a PodLogOptions")
}
@ -78,7 +80,7 @@ func logsForObjectWithClient(clientset internalclientset.Interface, object, opti
case *coreinternal.Pod:
// if allContainers is true, then we're going to locate all containers and then iterate through them. At that point, "allContainers" is false
if !allContainers {
return []*rest.Request{clientset.Core().Pods(t.Namespace).GetLogs(t.Name, opts)}, nil
return []*rest.Request{clientset.Pods(t.Namespace).GetLogs(t.Name, opts)}, nil
}
ret := []*rest.Request{}
@ -106,7 +108,7 @@ func logsForObjectWithClient(clientset internalclientset.Interface, object, opti
case *corev1.Pod:
// if allContainers is true, then we're going to locate all containers and then iterate through them. At that point, "allContainers" is false
if !allContainers {
return []*rest.Request{clientset.Core().Pods(t.Namespace).GetLogs(t.Name, opts)}, nil
return []*rest.Request{clientset.Pods(t.Namespace).GetLogs(t.Name, opts)}, nil
}
ret := []*rest.Request{}
@ -136,8 +138,9 @@ func logsForObjectWithClient(clientset internalclientset.Interface, object, opti
if err != nil {
return nil, fmt.Errorf("cannot get the logs from %T: %v", object, err)
}
sortBy := func(pods []*v1.Pod) sort.Interface { return controller.ByLogging(pods) }
pod, numPods, err := GetFirstPod(clientset.Core(), namespace, selector.String(), timeout, sortBy)
pod, numPods, err := GetFirstPod(clientset, namespace, selector.String(), timeout, sortBy)
if err != nil {
return nil, err
}

View File

@ -21,34 +21,34 @@ import (
"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/apimachinery/pkg/util/diff"
fakeexternal "k8s.io/client-go/kubernetes/fake"
testclient "k8s.io/client-go/testing"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/batch"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
)
var (
podsResource = schema.GroupVersionResource{Resource: "pods"}
podsKind = schema.GroupVersionKind{Kind: "Pod"}
podsResource = schema.GroupVersionResource{Version: "v1", Resource: "pods"}
podsKind = schema.GroupVersionKind{Version: "v1", Kind: "Pod"}
)
func TestLogsForObject(t *testing.T) {
tests := []struct {
name string
obj runtime.Object
opts *api.PodLogOptions
opts *corev1.PodLogOptions
pods []runtime.Object
actions []testclient.Action
}{
{
name: "pod logs",
obj: &api.Pod{
obj: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
},
pods: []runtime.Object{testPod()},
@ -58,9 +58,9 @@ func TestLogsForObject(t *testing.T) {
},
{
name: "replication controller logs",
obj: &api.ReplicationController{
obj: &corev1.ReplicationController{
ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: "test"},
Spec: api.ReplicationControllerSpec{
Spec: corev1.ReplicationControllerSpec{
Selector: map[string]string{"foo": "bar"},
},
},
@ -129,8 +129,8 @@ func TestLogsForObject(t *testing.T) {
}
for _, test := range tests {
fakeClientset := fake.NewSimpleClientset(test.pods...)
_, err := logsForObjectWithClient(fakeClientset, test.obj, test.opts, 20*time.Second, false)
fakeClientset := fakeexternal.NewSimpleClientset(test.pods...)
_, err := logsForObjectWithClient(fakeClientset.CoreV1(), test.obj, test.opts, 20*time.Second, false)
if err != nil {
t.Errorf("%s: unexpected error: %v", test.name, err)
continue
@ -151,21 +151,21 @@ func TestLogsForObject(t *testing.T) {
}
func testPod() runtime.Object {
return &api.Pod{
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "test",
Labels: map[string]string{"foo": "bar"},
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{Name: "c1"}},
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyAlways,
DNSPolicy: corev1.DNSClusterFirst,
Containers: []corev1.Container{{Name: "c1"}},
},
}
}
func getLogsAction(namespace string, opts *api.PodLogOptions) testclient.Action {
func getLogsAction(namespace string, opts *corev1.PodLogOptions) testclient.Action {
action := testclient.GenericActionImpl{}
action.Verb = "get"
action.Namespace = namespace

View File

@ -564,7 +564,7 @@ var _ = SIGDescribe("Kubectl client", func() {
ExecOrDie()
Expect(runOutput).ToNot(ContainSubstring("stdin closed"))
g := func(pods []*v1.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) }
runTestPod, _, err := polymorphichelpers.GetFirstPod(f.InternalClientset.Core(), ns, "run=run-test-3", 1*time.Minute, g)
runTestPod, _, err := polymorphichelpers.GetFirstPod(f.ClientSet.CoreV1(), ns, "run=run-test-3", 1*time.Minute, g)
if err != nil {
os.Exit(1)
}