Commit a6566031 authored by Chao Xu's avatar Chao Xu

Merge pull request #13422 from feihujiang/supportSettingEnvInKubectlRun

Support setting env vars in kubectl run
parents 2f93978d 84e94e39
...@@ -681,6 +681,7 @@ _kubectl_run() ...@@ -681,6 +681,7 @@ _kubectl_run()
flags+=("--attach") flags+=("--attach")
flags+=("--command") flags+=("--command")
flags+=("--dry-run") flags+=("--dry-run")
flags+=("--env=")
flags+=("--generator=") flags+=("--generator=")
flags+=("--hostport=") flags+=("--hostport=")
flags+=("--image=") flags+=("--image=")
......
...@@ -31,6 +31,10 @@ Creates a replication controller to manage the created container(s). ...@@ -31,6 +31,10 @@ Creates a replication controller to manage the created container(s).
If true, only print the object that would be sent, without sending it. If true, only print the object that would be sent, without sending it.
.PP .PP
\fB\-\-env\fP=[]
Environment variables to set in the container
.PP
\fB\-\-generator\fP="" \fB\-\-generator\fP=""
The name of the API generator to use. Default is 'run/v1' if \-\-restart=Always, otherwise the default is 'run\-pod/v1'. The name of the API generator to use. Default is 'run/v1' if \-\-restart=Always, otherwise the default is 'run\-pod/v1'.
...@@ -200,6 +204,12 @@ Creates a replication controller to manage the created container(s). ...@@ -200,6 +204,12 @@ Creates a replication controller to manage the created container(s).
# Start a single instance of nginx. # Start a single instance of nginx.
$ kubectl run nginx \-\-image=nginx $ kubectl run nginx \-\-image=nginx
# Start a single instance of hazelcast and let the container expose port 5701 .
$ kubectl run hazelcast \-\-image=hazelcast \-\-port=5701
# Start a single instance of hazelcast and set environment variables "DNS\_DOMAIN=cluster" and "POD\_NAMESPACE=default" in the container.
$ kubectl run hazelcast \-\-image=hazelcast \-\-env="DNS\_DOMAIN=local" \-\-env="POD\_NAMESPACE=default"
# Start a replicated instance of nginx. # Start a replicated instance of nginx.
$ kubectl run nginx \-\-image=nginx \-\-replicas=5 $ kubectl run nginx \-\-image=nginx \-\-replicas=5
......
...@@ -58,7 +58,7 @@ How do I run an nginx container and expose it to the world? Checkout [kubectl ru ...@@ -58,7 +58,7 @@ How do I run an nginx container and expose it to the world? Checkout [kubectl ru
With docker: With docker:
```console ```console
$ docker run -d --restart=always --name nginx-app -p 80:80 nginx $ docker run -d --restart=always -e DOMAIN=cluster --name nginx-app -p 80:80 nginx
a9ec34d9878748d2f33dc20cb25c714ff21da8d40558b45bfaec9955859075d0 a9ec34d9878748d2f33dc20cb25c714ff21da8d40558b45bfaec9955859075d0
$ docker ps $ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
...@@ -69,7 +69,7 @@ With kubectl: ...@@ -69,7 +69,7 @@ With kubectl:
```console ```console
# start the pod running nginx # start the pod running nginx
$ kubectl run --image=nginx nginx-app $ kubectl run --image=nginx nginx-app --port=80 --env="DOMAIN=local"
CONTROLLER CONTAINER(S) IMAGE(S) SELECTOR REPLICAS CONTROLLER CONTAINER(S) IMAGE(S) SELECTOR REPLICAS
nginx-app nginx-app nginx run=nginx-app 1 nginx-app nginx-app nginx run=nginx-app 1
# expose a port through with a service # expose a port through with a service
......
...@@ -42,7 +42,7 @@ Create and run a particular image, possibly replicated. ...@@ -42,7 +42,7 @@ Create and run a particular image, possibly replicated.
Creates a replication controller to manage the created container(s). Creates a replication controller to manage the created container(s).
``` ```
kubectl run NAME --image=image [--port=port] [--replicas=replicas] [--dry-run=bool] [--overrides=inline-json] kubectl run NAME --image=image [--env="key=value"] [--port=port] [--replicas=replicas] [--dry-run=bool] [--overrides=inline-json]
``` ```
### Examples ### Examples
...@@ -51,6 +51,12 @@ kubectl run NAME --image=image [--port=port] [--replicas=replicas] [--dry-run=bo ...@@ -51,6 +51,12 @@ kubectl run NAME --image=image [--port=port] [--replicas=replicas] [--dry-run=bo
# Start a single instance of nginx. # Start a single instance of nginx.
$ kubectl run nginx --image=nginx $ kubectl run nginx --image=nginx
# Start a single instance of hazelcast and let the container expose port 5701 .
$ kubectl run hazelcast --image=hazelcast --port=5701
# Start a single instance of hazelcast and set environment variables "DNS_DOMAIN=cluster" and "POD_NAMESPACE=default" in the container.
$ kubectl run hazelcast --image=hazelcast --env="DNS_DOMAIN=local" --env="POD_NAMESPACE=default"
# Start a replicated instance of nginx. # Start a replicated instance of nginx.
$ kubectl run nginx --image=nginx --replicas=5 $ kubectl run nginx --image=nginx --replicas=5
...@@ -76,6 +82,7 @@ $ kubectl run nginx --image=nginx --command -- <cmd> <arg1> ... <argN> ...@@ -76,6 +82,7 @@ $ kubectl run nginx --image=nginx --command -- <cmd> <arg1> ... <argN>
--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. --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.
--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. --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.
--dry-run[=false]: If true, only print the object that would be sent, without sending it. --dry-run[=false]: If true, only print the object that would be sent, without sending it.
--env=[]: Environment variables to set in the container
--generator="": The name of the API generator to use. Default is 'run/v1' if --restart=Always, otherwise the default is 'run-pod/v1'. --generator="": The name of the API generator to use. Default is 'run/v1' if --restart=Always, otherwise the default is 'run-pod/v1'.
--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.
...@@ -126,7 +133,7 @@ $ kubectl run nginx --image=nginx --command -- <cmd> <arg1> ... <argN> ...@@ -126,7 +133,7 @@ $ kubectl run nginx --image=nginx --command -- <cmd> <arg1> ... <argN>
* [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager
###### Auto generated by spf13/cobra at 2015-08-29 13:01:26.772003236 +0000 UTC ###### Auto generated by spf13/cobra at 2015-09-07 06:40:12.142439604 +0000 UTC
<!-- BEGIN MUNGE: GENERATED_ANALYTICS --> <!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_run.md?pixel)]() [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_run.md?pixel)]()
......
...@@ -38,6 +38,12 @@ Creates a replication controller to manage the created container(s).` ...@@ -38,6 +38,12 @@ Creates a replication controller to manage the created container(s).`
run_example = `# Start a single instance of nginx. run_example = `# Start a single instance of nginx.
$ kubectl run nginx --image=nginx $ kubectl run nginx --image=nginx
# Start a single instance of hazelcast and let the container expose port 5701 .
$ kubectl run hazelcast --image=hazelcast --port=5701
# Start a single instance of hazelcast and set environment variables "DNS_DOMAIN=cluster" and "POD_NAMESPACE=default" in the container.
$ kubectl run hazelcast --image=hazelcast --env="DNS_DOMAIN=local" --env="POD_NAMESPACE=default"
# Start a replicated instance of nginx. # Start a replicated instance of nginx.
$ kubectl run nginx --image=nginx --replicas=5 $ kubectl run nginx --image=nginx --replicas=5
...@@ -59,7 +65,7 @@ $ kubectl run nginx --image=nginx --command -- <cmd> <arg1> ... <argN>` ...@@ -59,7 +65,7 @@ $ kubectl run nginx --image=nginx --command -- <cmd> <arg1> ... <argN>`
func NewCmdRun(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command { func NewCmdRun(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "run NAME --image=image [--port=port] [--replicas=replicas] [--dry-run=bool] [--overrides=inline-json]", Use: "run NAME --image=image [--env=\"key=value\"] [--port=port] [--replicas=replicas] [--dry-run=bool] [--overrides=inline-json]",
// run-container is deprecated // run-container is deprecated
Aliases: []string{"run-container"}, Aliases: []string{"run-container"},
Short: "Run a particular image on the cluster.", Short: "Run a particular image on the cluster.",
...@@ -77,6 +83,7 @@ func NewCmdRun(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *c ...@@ -77,6 +83,7 @@ func NewCmdRun(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *c
cmd.Flags().IntP("replicas", "r", 1, "Number of replicas to create for this container. Default is 1.") cmd.Flags().IntP("replicas", "r", 1, "Number of replicas to create for this container. Default is 1.")
cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it.") cmd.Flags().Bool("dry-run", false, "If true, only print the object that would be sent, without sending it.")
cmd.Flags().String("overrides", "", "An inline JSON override for the generated object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field.") cmd.Flags().String("overrides", "", "An inline JSON override for the generated object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field.")
cmd.Flags().StringSlice("env", []string{}, "Environment variables to set in the container")
cmd.Flags().Int("port", -1, "The port that this container exposes.") cmd.Flags().Int("port", -1, "The port that this container exposes.")
cmd.Flags().Int("hostport", -1, "The host port mapping for the container port. To demonstrate a single-machine container.") cmd.Flags().Int("hostport", -1, "The host port mapping for the container port. To demonstrate a single-machine container.")
cmd.Flags().StringP("labels", "l", "", "Labels to apply to the pod(s).") cmd.Flags().StringP("labels", "l", "", "Labels to apply to the pod(s).")
...@@ -137,6 +144,9 @@ func Run(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cob ...@@ -137,6 +144,9 @@ func Run(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cob
if len(args) > 1 { if len(args) > 1 {
params["args"] = args[1:] params["args"] = args[1:]
} }
params["env"] = cmdutil.GetFlagStringSlice(cmd, "env")
err = kubectl.ValidateParams(names, params) err = kubectl.ValidateParams(names, params)
if err != nil { if err != nil {
return err return err
......
...@@ -17,10 +17,12 @@ limitations under the License. ...@@ -17,10 +17,12 @@ limitations under the License.
package cmd package cmd
import ( import (
"reflect"
"testing" "testing"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
) )
func TestGetRestartPolicy(t *testing.T) { func TestGetRestartPolicy(t *testing.T) {
...@@ -78,3 +80,20 @@ func TestGetRestartPolicy(t *testing.T) { ...@@ -78,3 +80,20 @@ func TestGetRestartPolicy(t *testing.T) {
} }
} }
} }
func TestGetEnv(t *testing.T) {
test := struct {
input []string
expected []string
}{
input: []string{"a=b", "c=d"},
expected: []string{"a=b", "c=d"},
}
cmd := &cobra.Command{}
cmd.Flags().StringSlice("env", test.input, "")
envStrings := cmdutil.GetFlagStringSlice(cmd, "env")
if len(envStrings) != 2 || !reflect.DeepEqual(envStrings, test.expected) {
t.Errorf("expected: %s, saw: %s", test.expected, envStrings)
}
}
...@@ -19,9 +19,11 @@ package kubectl ...@@ -19,9 +19,11 @@ package kubectl
import ( import (
"fmt" "fmt"
"strconv" "strconv"
"strings"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util"
) )
type BasicReplicationController struct{} type BasicReplicationController struct{}
...@@ -39,6 +41,7 @@ func (BasicReplicationController) ParamNames() []GeneratorParam { ...@@ -39,6 +41,7 @@ func (BasicReplicationController) ParamNames() []GeneratorParam {
{"tty", false}, {"tty", false},
{"command", false}, {"command", false},
{"args", false}, {"args", false},
{"env", false},
} }
} }
...@@ -77,6 +80,23 @@ func (BasicReplicationController) Generate(genericParams map[string]interface{}) ...@@ -77,6 +80,23 @@ func (BasicReplicationController) Generate(genericParams map[string]interface{})
} }
delete(genericParams, "args") delete(genericParams, "args")
} }
// TODO: abstract this logic so that multiple generators can handle env in the same way. Same for parse envs.
var envs []api.EnvVar
envStrings, found := genericParams["env"]
if found {
if envStringArray, isArray := envStrings.([]string); isArray {
var err error
envs, err = parseEnvs(envStringArray)
if err != nil {
return nil, err
}
delete(genericParams, "env")
} else {
return nil, fmt.Errorf("expected []string, found: %v", envStrings)
}
}
params := map[string]string{} params := map[string]string{}
for key, value := range genericParams { for key, value := range genericParams {
strVal, isString := value.(string) strVal, isString := value.(string)
...@@ -127,6 +147,10 @@ func (BasicReplicationController) Generate(genericParams map[string]interface{}) ...@@ -127,6 +147,10 @@ func (BasicReplicationController) Generate(genericParams map[string]interface{})
} }
} }
if len(envs) > 0 {
podSpec.Containers[0].Env = envs
}
controller := api.ReplicationController{ controller := api.ReplicationController{
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: name, Name: name,
...@@ -198,6 +222,7 @@ func (BasicPod) ParamNames() []GeneratorParam { ...@@ -198,6 +222,7 @@ func (BasicPod) ParamNames() []GeneratorParam {
{"restart", false}, {"restart", false},
{"command", false}, {"command", false},
{"args", false}, {"args", false},
{"env", false},
} }
} }
...@@ -212,6 +237,22 @@ func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object, ...@@ -212,6 +237,22 @@ func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object,
} }
delete(genericParams, "args") delete(genericParams, "args")
} }
// TODO: abstract this logic so that multiple generators can handle env in the same way. Same for parse envs.
var envs []api.EnvVar
envStrings, found := genericParams["env"]
if found {
if envStringArray, isArray := envStrings.([]string); isArray {
var err error
envs, err = parseEnvs(envStringArray)
if err != nil {
return nil, err
}
delete(genericParams, "env")
} else {
return nil, fmt.Errorf("expected []string, found: %v", envStrings)
}
}
params := map[string]string{} params := map[string]string{}
for key, value := range genericParams { for key, value := range genericParams {
strVal, isString := value.(string) strVal, isString := value.(string)
...@@ -281,8 +322,26 @@ func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object, ...@@ -281,8 +322,26 @@ func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object,
pod.Spec.Containers[0].Args = args pod.Spec.Containers[0].Args = args
} }
} }
if len(envs) > 0 {
pod.Spec.Containers[0].Env = envs
}
if err := updatePodPorts(params, &pod.Spec); err != nil { if err := updatePodPorts(params, &pod.Spec); err != nil {
return nil, err return nil, err
} }
return &pod, nil return &pod, nil
} }
func parseEnvs(envArray []string) ([]api.EnvVar, error) {
envs := []api.EnvVar{}
for _, env := range envArray {
parts := strings.Split(env, "=")
if len(parts) != 2 || !util.IsCIdentifier(parts[0]) || len(parts[1]) == 0 {
return nil, fmt.Errorf("invalid env: %v", env)
}
envVar := api.EnvVar{Name: parts[0], Value: parts[1]}
envs = append(envs, envVar)
}
return envs, nil
}
...@@ -60,6 +60,50 @@ func TestGenerate(t *testing.T) { ...@@ -60,6 +60,50 @@ func TestGenerate(t *testing.T) {
}, },
}, },
}, },
{
params: map[string]interface{}{
"name": "foo",
"image": "someimage",
"replicas": "1",
"port": "-1",
"env": []string{"a=b", "c=d"},
},
expected: &api.ReplicationController{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{"run": "foo"},
},
Spec: api.ReplicationControllerSpec{
Replicas: 1,
Selector: map[string]string{"run": "foo"},
Template: &api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"run": "foo"},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "foo",
Image: "someimage",
Env: []api.EnvVar{
{
Name: "a",
Value: "b",
},
{
Name: "c",
Value: "d",
},
},
},
},
},
},
},
},
},
{ {
params: map[string]interface{}{ params: map[string]interface{}{
"name": "foo", "name": "foo",
...@@ -291,6 +335,49 @@ func TestGeneratePod(t *testing.T) { ...@@ -291,6 +335,49 @@ func TestGeneratePod(t *testing.T) {
params: map[string]interface{}{ params: map[string]interface{}{
"name": "foo", "name": "foo",
"image": "someimage", "image": "someimage",
"env": []string{"a", "c"},
},
expected: nil,
expectErr: true,
},
{
params: map[string]interface{}{
"name": "foo",
"image": "someimage",
"env": []string{"a=b", "c=d"},
},
expected: &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "foo",
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "foo",
Image: "someimage",
ImagePullPolicy: api.PullIfNotPresent,
Env: []api.EnvVar{
{
Name: "a",
Value: "b",
},
{
Name: "c",
Value: "d",
},
},
},
},
DNSPolicy: api.DNSClusterFirst,
RestartPolicy: api.RestartPolicyAlways,
},
},
},
{
params: map[string]interface{}{
"name": "foo",
"image": "someimage",
"port": "80", "port": "80",
}, },
expected: &api.Pod{ expected: &api.Pod{
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment