mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Make kubectl run attach behave like docker run
Have stdin closed by default, can be left open with --leave-stdin-open. Add e2e tests for the behavior.
This commit is contained in:
		@@ -753,6 +753,7 @@ _kubectl_run()
 | 
				
			|||||||
    flags+=("--image=")
 | 
					    flags+=("--image=")
 | 
				
			||||||
    flags+=("--labels=")
 | 
					    flags+=("--labels=")
 | 
				
			||||||
    two_word_flags+=("-l")
 | 
					    two_word_flags+=("-l")
 | 
				
			||||||
 | 
					    flags+=("--leave-stdin-open")
 | 
				
			||||||
    flags+=("--limits=")
 | 
					    flags+=("--limits=")
 | 
				
			||||||
    flags+=("--no-headers")
 | 
					    flags+=("--no-headers")
 | 
				
			||||||
    flags+=("--output=")
 | 
					    flags+=("--output=")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,6 +50,10 @@ Creates a replication controller to manage the created container(s).
 | 
				
			|||||||
\fB\-l\fP, \fB\-\-labels\fP=""
 | 
					\fB\-l\fP, \fB\-\-labels\fP=""
 | 
				
			||||||
    Labels to apply to the pod(s).
 | 
					    Labels to apply to the pod(s).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PP
 | 
				
			||||||
 | 
					\fB\-\-leave\-stdin\-open\fP=false
 | 
				
			||||||
 | 
					    If the pod is started in interactive mode or with stdin, leave stdin open after the first attach completes. By default, stdin will be closed after the first attach completes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PP
 | 
					.PP
 | 
				
			||||||
\fB\-\-limits\fP=""
 | 
					\fB\-\-limits\fP=""
 | 
				
			||||||
    The resource requirement limits for this container.  For example, 'cpu=200m,memory=512Mi'
 | 
					    The resource requirement limits for this container.  For example, 'cpu=200m,memory=512Mi'
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -87,6 +87,7 @@ $ kubectl run nginx --image=nginx --command -- <cmd> <arg1> ... <argN>
 | 
				
			|||||||
      --hostport=-1: The host port mapping for the container port. To demonstrate a single-machine container.
 | 
					      --hostport=-1: The host port mapping for the container port. To demonstrate a single-machine container.
 | 
				
			||||||
      --image="": The image for the container to run.
 | 
					      --image="": The image for the container to run.
 | 
				
			||||||
  -l, --labels="": Labels to apply to the pod(s).
 | 
					  -l, --labels="": Labels to apply to the pod(s).
 | 
				
			||||||
 | 
					      --leave-stdin-open[=false]: If the pod is started in interactive mode or with stdin, leave stdin open after the first attach completes. By default, stdin will be closed after the first attach completes.
 | 
				
			||||||
      --limits="": The resource requirement limits for this container.  For example, 'cpu=200m,memory=512Mi'
 | 
					      --limits="": The resource requirement limits for this container.  For example, 'cpu=200m,memory=512Mi'
 | 
				
			||||||
      --no-headers[=false]: When using the default output, don't print headers.
 | 
					      --no-headers[=false]: When using the default output, don't print headers.
 | 
				
			||||||
  -o, --output="": Output format. One of: json|yaml|wide|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://releases.k8s.io/HEAD/docs/user-guide/jsonpath.md].
 | 
					  -o, --output="": Output format. One of: json|yaml|wide|name|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://releases.k8s.io/HEAD/docs/user-guide/jsonpath.md].
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -148,6 +148,7 @@ kubelet-timeout
 | 
				
			|||||||
kube-master
 | 
					kube-master
 | 
				
			||||||
label-columns
 | 
					label-columns
 | 
				
			||||||
last-release-pr
 | 
					last-release-pr
 | 
				
			||||||
 | 
					leave-stdin-open
 | 
				
			||||||
limit-bytes
 | 
					limit-bytes
 | 
				
			||||||
load-balancer-ip
 | 
					load-balancer-ip
 | 
				
			||||||
log-flush-frequency
 | 
					log-flush-frequency
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -90,6 +90,7 @@ func NewCmdRun(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *c
 | 
				
			|||||||
	cmd.Flags().BoolP("stdin", "i", false, "Keep stdin open on the container(s) in the pod, even if nothing is attached.")
 | 
						cmd.Flags().BoolP("stdin", "i", false, "Keep stdin open on the container(s) in the pod, even if nothing is attached.")
 | 
				
			||||||
	cmd.Flags().Bool("tty", false, "Allocated a TTY for each container in the pod.  Because -t is currently shorthand for --template, -t is not supported for --tty. This shorthand is deprecated and we expect to adopt -t for --tty soon.")
 | 
						cmd.Flags().Bool("tty", false, "Allocated a TTY for each container in the pod.  Because -t is currently shorthand for --template, -t is not supported for --tty. This shorthand is deprecated and we expect to adopt -t for --tty soon.")
 | 
				
			||||||
	cmd.Flags().Bool("attach", false, "If true, wait for the Pod to start running, and then attach to the Pod as if 'kubectl attach ...' were called.  Default false, unless '-i/--interactive' is set, in which case the default is true.")
 | 
						cmd.Flags().Bool("attach", false, "If true, wait for the Pod to start running, and then attach to the Pod as if 'kubectl attach ...' were called.  Default false, unless '-i/--interactive' is set, in which case the default is true.")
 | 
				
			||||||
 | 
						cmd.Flags().Bool("leave-stdin-open", false, "If the pod is started in interactive mode or with stdin, leave stdin open after the first attach completes. By default, stdin will be closed after the first attach completes.")
 | 
				
			||||||
	cmd.Flags().String("restart", "Always", "The restart policy for this Pod.  Legal values [Always, OnFailure, Never].  If set to 'Always' a replication controller is created for this pod, if set to OnFailure or Never, only the Pod is created and --replicas must be 1.  Default 'Always'")
 | 
						cmd.Flags().String("restart", "Always", "The restart policy for this Pod.  Legal values [Always, OnFailure, Never].  If set to 'Always' a replication controller is created for this pod, if set to OnFailure or Never, only the Pod is created and --replicas must be 1.  Default 'Always'")
 | 
				
			||||||
	cmd.Flags().Bool("command", false, "If true and extra arguments are present, use them as the 'command' field in the container, rather than the 'args' field which is the default.")
 | 
						cmd.Flags().Bool("command", false, "If true and extra arguments are present, use them as the 'command' field in the container, rather than the 'args' field which is the default.")
 | 
				
			||||||
	cmd.Flags().String("requests", "", "The resource requirement requests for this container.  For example, 'cpu=100m,memory=256Mi'")
 | 
						cmd.Flags().String("requests", "", "The resource requirement requests for this container.  For example, 'cpu=100m,memory=256Mi'")
 | 
				
			||||||
@@ -126,7 +127,7 @@ func Run(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cob
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if restartPolicy != api.RestartPolicyAlways && replicas != 1 {
 | 
						if restartPolicy != api.RestartPolicyAlways && replicas != 1 {
 | 
				
			||||||
		return cmdutil.UsageError(cmd, fmt.Sprintf("--restart=%s requires that --repliacs=1, found %d", restartPolicy, replicas))
 | 
							return cmdutil.UsageError(cmd, fmt.Sprintf("--restart=%s requires that --replicas=1, found %d", restartPolicy, replicas))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	generatorName := cmdutil.GetFlagString(cmd, "generator")
 | 
						generatorName := cmdutil.GetFlagString(cmd, "generator")
 | 
				
			||||||
	if len(generatorName) == 0 {
 | 
						if len(generatorName) == 0 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -267,6 +267,7 @@ func (BasicPod) ParamNames() []GeneratorParam {
 | 
				
			|||||||
		{"port", false},
 | 
							{"port", false},
 | 
				
			||||||
		{"hostport", false},
 | 
							{"hostport", false},
 | 
				
			||||||
		{"stdin", false},
 | 
							{"stdin", false},
 | 
				
			||||||
 | 
							{"leave-stdin-open", false},
 | 
				
			||||||
		{"tty", false},
 | 
							{"tty", false},
 | 
				
			||||||
		{"restart", false},
 | 
							{"restart", false},
 | 
				
			||||||
		{"command", false},
 | 
							{"command", false},
 | 
				
			||||||
@@ -333,6 +334,10 @@ func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object,
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						leaveStdinOpen, err := GetBool(params, "leave-stdin-open", false)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tty, err := GetBool(params, "tty", false)
 | 
						tty, err := GetBool(params, "tty", false)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -360,6 +365,7 @@ func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object,
 | 
				
			|||||||
					Image:           params["image"],
 | 
										Image:           params["image"],
 | 
				
			||||||
					ImagePullPolicy: api.PullIfNotPresent,
 | 
										ImagePullPolicy: api.PullIfNotPresent,
 | 
				
			||||||
					Stdin:           stdin,
 | 
										Stdin:           stdin,
 | 
				
			||||||
 | 
										StdinOnce:       !leaveStdinOpen && stdin,
 | 
				
			||||||
					TTY:             tty,
 | 
										TTY:             tty,
 | 
				
			||||||
					Resources:       resourceRequirements,
 | 
										Resources:       resourceRequirements,
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -553,6 +553,63 @@ func TestGeneratePod(t *testing.T) {
 | 
				
			|||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								params: map[string]interface{}{
 | 
				
			||||||
 | 
									"name":     "foo",
 | 
				
			||||||
 | 
									"image":    "someimage",
 | 
				
			||||||
 | 
									"replicas": "1",
 | 
				
			||||||
 | 
									"labels":   "foo=bar,baz=blah",
 | 
				
			||||||
 | 
									"stdin":    "true",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: &api.Pod{
 | 
				
			||||||
 | 
									ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
 | 
										Name:   "foo",
 | 
				
			||||||
 | 
										Labels: map[string]string{"foo": "bar", "baz": "blah"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: api.PodSpec{
 | 
				
			||||||
 | 
										Containers: []api.Container{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:            "foo",
 | 
				
			||||||
 | 
												Image:           "someimage",
 | 
				
			||||||
 | 
												ImagePullPolicy: api.PullIfNotPresent,
 | 
				
			||||||
 | 
												Stdin:           true,
 | 
				
			||||||
 | 
												StdinOnce:       true,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										DNSPolicy:     api.DNSClusterFirst,
 | 
				
			||||||
 | 
										RestartPolicy: api.RestartPolicyAlways,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								params: map[string]interface{}{
 | 
				
			||||||
 | 
									"name":             "foo",
 | 
				
			||||||
 | 
									"image":            "someimage",
 | 
				
			||||||
 | 
									"replicas":         "1",
 | 
				
			||||||
 | 
									"labels":           "foo=bar,baz=blah",
 | 
				
			||||||
 | 
									"stdin":            "true",
 | 
				
			||||||
 | 
									"leave-stdin-open": "true",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								expected: &api.Pod{
 | 
				
			||||||
 | 
									ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
 | 
										Name:   "foo",
 | 
				
			||||||
 | 
										Labels: map[string]string{"foo": "bar", "baz": "blah"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									Spec: api.PodSpec{
 | 
				
			||||||
 | 
										Containers: []api.Container{
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												Name:            "foo",
 | 
				
			||||||
 | 
												Image:           "someimage",
 | 
				
			||||||
 | 
												ImagePullPolicy: api.PullIfNotPresent,
 | 
				
			||||||
 | 
												Stdin:           true,
 | 
				
			||||||
 | 
												StdinOnce:       false,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										DNSPolicy:     api.DNSClusterFirst,
 | 
				
			||||||
 | 
										RestartPolicy: api.RestartPolicyAlways,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	generator := BasicPod{}
 | 
						generator := BasicPod{}
 | 
				
			||||||
	for _, test := range tests {
 | 
						for _, test := range tests {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -360,11 +360,49 @@ var _ = Describe("Kubectl client", func() {
 | 
				
			|||||||
		})
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		It("should support inline execution and attach", func() {
 | 
							It("should support inline execution and attach", func() {
 | 
				
			||||||
			By("executing a command with run and attach")
 | 
								nsFlag := fmt.Sprintf("--namespace=%v", ns)
 | 
				
			||||||
			runOutput := runKubectl(fmt.Sprintf("--namespace=%v", ns), "run", "run-test", "--image=busybox", "--restart=Never", "--attach=true", "echo", "running", "in", "container")
 | 
					
 | 
				
			||||||
			expectedRunOutput := "running in container"
 | 
								By("executing a command with run and attach with stdin")
 | 
				
			||||||
			Expect(runOutput).To(ContainSubstring(expectedRunOutput))
 | 
								runOutput := newKubectlCommand(nsFlag, "run", "run-test", "--image=busybox", "--restart=Never", "--attach=true", "--stdin", "--", "sh", "-c", "cat && echo 'stdin closed'").
 | 
				
			||||||
			// everything in the ns will be deleted at the end of the test
 | 
									withStdinData("abcd1234").
 | 
				
			||||||
 | 
									exec()
 | 
				
			||||||
 | 
								Expect(runOutput).To(ContainSubstring("abcd1234"))
 | 
				
			||||||
 | 
								Expect(runOutput).To(ContainSubstring("stdin closed"))
 | 
				
			||||||
 | 
								Expect(c.Pods(ns).Delete("run-test", api.NewDeleteOptions(0))).To(BeNil())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								By("executing a command with run and attach without stdin")
 | 
				
			||||||
 | 
								runOutput = newKubectlCommand(fmt.Sprintf("--namespace=%v", ns), "run", "run-test-2", "--image=busybox", "--restart=Never", "--attach=true", "--leave-stdin-open=true", "--", "sh", "-c", "cat && echo 'stdin closed'").
 | 
				
			||||||
 | 
									withStdinData("abcd1234").
 | 
				
			||||||
 | 
									exec()
 | 
				
			||||||
 | 
								Expect(runOutput).ToNot(ContainSubstring("abcd1234"))
 | 
				
			||||||
 | 
								Expect(runOutput).To(ContainSubstring("stdin closed"))
 | 
				
			||||||
 | 
								Expect(c.Pods(ns).Delete("run-test-2", api.NewDeleteOptions(0))).To(BeNil())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								By("executing a command with run and attach with stdin with open stdin should remain running")
 | 
				
			||||||
 | 
								runOutput = newKubectlCommand(nsFlag, "run", "run-test-3", "--image=busybox", "--restart=Never", "--attach=true", "--leave-stdin-open=true", "--stdin", "--", "sh", "-c", "cat && echo 'stdin closed'").
 | 
				
			||||||
 | 
									withStdinData("abcd1234\n").
 | 
				
			||||||
 | 
									exec()
 | 
				
			||||||
 | 
								Expect(runOutput).ToNot(ContainSubstring("stdin closed"))
 | 
				
			||||||
 | 
								if !checkPodsRunningReady(c, ns, []string{"run-test-3"}, time.Minute) {
 | 
				
			||||||
 | 
									Failf("Pod %q should still be running", "run-test-3")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// NOTE: we cannot guarantee our output showed up in the container logs before stdin was closed, so we have
 | 
				
			||||||
 | 
								// to loop test.
 | 
				
			||||||
 | 
								err := wait.PollImmediate(time.Second, time.Minute, func() (bool, error) {
 | 
				
			||||||
 | 
									if !checkPodsRunningReady(c, ns, []string{"run-test-3"}, 1*time.Second) {
 | 
				
			||||||
 | 
										Failf("Pod %q should still be running", "run-test-3")
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									logOutput := runKubectl(nsFlag, "logs", "run-test-3")
 | 
				
			||||||
 | 
									Expect(logOutput).ToNot(ContainSubstring("stdin closed"))
 | 
				
			||||||
 | 
									return strings.Contains(logOutput, "abcd1234"), nil
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									os.Exit(1)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								Expect(err).To(BeNil())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Expect(c.Pods(ns).Delete("run-test-3", api.NewDeleteOptions(0))).To(BeNil())
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		It("should support port-forward", func() {
 | 
							It("should support port-forward", func() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1061,6 +1061,11 @@ func runKubectl(args ...string) string {
 | 
				
			|||||||
	return newKubectlCommand(args...).exec()
 | 
						return newKubectlCommand(args...).exec()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// runKubectlInput is a convenience wrapper over kubectlBuilder that takes input to stdin
 | 
				
			||||||
 | 
					func runKubectlInput(data string, args ...string) string {
 | 
				
			||||||
 | 
						return newKubectlCommand(args...).withStdinData(data).exec()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func startCmdAndStreamOutput(cmd *exec.Cmd) (stdout, stderr io.ReadCloser, err error) {
 | 
					func startCmdAndStreamOutput(cmd *exec.Cmd) (stdout, stderr io.ReadCloser, err error) {
 | 
				
			||||||
	stdout, err = cmd.StdoutPipe()
 | 
						stdout, err = cmd.StdoutPipe()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user