mirror of https://github.com/k3s-io/k3s
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.pull/6/head
parent
e929baf91a
commit
e9a465d635
|
@ -753,6 +753,7 @@ _kubectl_run()
|
|||
flags+=("--image=")
|
||||
flags+=("--labels=")
|
||||
two_word_flags+=("-l")
|
||||
flags+=("--leave-stdin-open")
|
||||
flags+=("--limits=")
|
||||
flags+=("--no-headers")
|
||||
flags+=("--output=")
|
||||
|
|
|
@ -50,6 +50,10 @@ Creates a replication controller to manage the created container(s).
|
|||
\fB\-l\fP, \fB\-\-labels\fP=""
|
||||
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
|
||||
\fB\-\-limits\fP=""
|
||||
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.
|
||||
--image="": The image for the container to run.
|
||||
-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'
|
||||
--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].
|
||||
|
|
|
@ -148,6 +148,7 @@ kubelet-timeout
|
|||
kube-master
|
||||
label-columns
|
||||
last-release-pr
|
||||
leave-stdin-open
|
||||
limit-bytes
|
||||
load-balancer-ip
|
||||
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().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("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().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'")
|
||||
|
@ -126,7 +127,7 @@ func Run(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cob
|
|||
return err
|
||||
}
|
||||
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")
|
||||
if len(generatorName) == 0 {
|
||||
|
|
|
@ -267,6 +267,7 @@ func (BasicPod) ParamNames() []GeneratorParam {
|
|||
{"port", false},
|
||||
{"hostport", false},
|
||||
{"stdin", false},
|
||||
{"leave-stdin-open", false},
|
||||
{"tty", false},
|
||||
{"restart", false},
|
||||
{"command", false},
|
||||
|
@ -333,6 +334,10 @@ func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object,
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
leaveStdinOpen, err := GetBool(params, "leave-stdin-open", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tty, err := GetBool(params, "tty", false)
|
||||
if err != nil {
|
||||
|
@ -360,6 +365,7 @@ func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object,
|
|||
Image: params["image"],
|
||||
ImagePullPolicy: api.PullIfNotPresent,
|
||||
Stdin: stdin,
|
||||
StdinOnce: !leaveStdinOpen && stdin,
|
||||
TTY: tty,
|
||||
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{}
|
||||
for _, test := range tests {
|
||||
|
|
|
@ -360,11 +360,49 @@ var _ = Describe("Kubectl client", func() {
|
|||
})
|
||||
|
||||
It("should support inline execution and attach", func() {
|
||||
By("executing a command with run and attach")
|
||||
runOutput := runKubectl(fmt.Sprintf("--namespace=%v", ns), "run", "run-test", "--image=busybox", "--restart=Never", "--attach=true", "echo", "running", "in", "container")
|
||||
expectedRunOutput := "running in container"
|
||||
Expect(runOutput).To(ContainSubstring(expectedRunOutput))
|
||||
// everything in the ns will be deleted at the end of the test
|
||||
nsFlag := fmt.Sprintf("--namespace=%v", ns)
|
||||
|
||||
By("executing a command with run and attach with stdin")
|
||||
runOutput := newKubectlCommand(nsFlag, "run", "run-test", "--image=busybox", "--restart=Never", "--attach=true", "--stdin", "--", "sh", "-c", "cat && echo 'stdin closed'").
|
||||
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() {
|
||||
|
|
|
@ -1061,6 +1061,11 @@ func runKubectl(args ...string) string {
|
|||
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) {
|
||||
stdout, err = cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue