kubectl run to produce deployment and job

This commit is contained in:
Janet Kuo 2015-11-12 17:07:21 -08:00
parent f3753c02ed
commit c0c02c95c4
9 changed files with 618 additions and 189 deletions

View File

@ -14,7 +14,7 @@ kubectl run \- Run a particular image on the cluster.
.SH DESCRIPTION .SH DESCRIPTION
.PP .PP
Create and run a particular image, possibly replicated. Create and run a particular image, possibly replicated.
Creates a replication controller to manage the created container(s). Creates a replication controller or job to manage the created container(s).
.SH OPTIONS .SH OPTIONS
@ -40,7 +40,7 @@ Creates a replication controller to manage the created container(s).
.PP .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 'job/v1beta1'.
.PP .PP
\fB\-\-hostport\fP=\-1 \fB\-\-hostport\fP=\-1
@ -94,7 +94,7 @@ Creates a replication controller to manage the created container(s).
.PP .PP
\fB\-\-restart\fP="Always" \fB\-\-restart\fP="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' 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, a job is created for this pod and \-\-replicas must be 1. Default 'Always'
.PP .PP
\fB\-\-save\-config\fP=false \fB\-\-save\-config\fP=false
@ -247,15 +247,18 @@ $ kubectl run nginx \-\-image=nginx \-\-dry\-run
# Start a single instance of nginx, but overload the spec of the replication controller with a partial set of values parsed from JSON. # Start a single instance of nginx, but overload the spec of the replication controller with a partial set of values parsed from JSON.
$ kubectl run nginx \-\-image=nginx \-\-overrides='{ "apiVersion": "v1", "spec": { ... } }' $ kubectl run nginx \-\-image=nginx \-\-overrides='{ "apiVersion": "v1", "spec": { ... } }'
# Start a single instance of nginx and keep it in the foreground, don't restart it if it exits. # Start a single instance of busybox and keep it in the foreground, don't restart it if it exits.
$ kubectl run \-i \-\-tty nginx \-\-image=nginx \-\-restart=Never $ kubectl run \-i \-\-tty busybox \-\-image=busybox \-\-restart=Never
# Start the nginx container using the default command, but use custom arguments (arg1 .. argN) for that command. # Start the nginx container using the default command, but use custom arguments (arg1 .. argN) for that command.
$ kubectl run nginx \-\-image=nginx \-\- <arg1> <arg2> ... <argN> $ kubectl run nginx \-\-image=nginx \-\- <arg1> <arg2> ... <argN>
# Start the nginx container using a different command and custom arguments # Start the nginx container using a different command and custom arguments.
$ kubectl run nginx \-\-image=nginx \-\-command \-\- <cmd> <arg1> ... <argN> $ kubectl run nginx \-\-image=nginx \-\-command \-\- <cmd> <arg1> ... <argN>
# Start the perl container to compute π to 2000 places and print it out.
$ kubectl run pi \-\-image=perl \-\-restart=OnFailure \-\- perl \-Mbignum=bpi \-wle 'print bpi(2000)'
.fi .fi
.RE .RE

View File

@ -39,7 +39,7 @@ Run a particular image on the cluster.
Create and run a particular image, possibly replicated. Create and run a particular image, possibly replicated.
Creates a replication controller to manage the created container(s). Creates a replication controller or job to manage the created container(s).
``` ```
kubectl run NAME --image=image [--env="key=value"] [--port=port] [--replicas=replicas] [--dry-run=bool] [--overrides=inline-json] [--command] -- [COMMAND] [args...] kubectl run NAME --image=image [--env="key=value"] [--port=port] [--replicas=replicas] [--dry-run=bool] [--overrides=inline-json] [--command] -- [COMMAND] [args...]
@ -66,14 +66,17 @@ $ kubectl run nginx --image=nginx --dry-run
# Start a single instance of nginx, but overload the spec of the replication controller with a partial set of values parsed from JSON. # Start a single instance of nginx, but overload the spec of the replication controller with a partial set of values parsed from JSON.
$ kubectl run nginx --image=nginx --overrides='{ "apiVersion": "v1", "spec": { ... } }' $ kubectl run nginx --image=nginx --overrides='{ "apiVersion": "v1", "spec": { ... } }'
# Start a single instance of nginx and keep it in the foreground, don't restart it if it exits. # Start a single instance of busybox and keep it in the foreground, don't restart it if it exits.
$ kubectl run -i --tty nginx --image=nginx --restart=Never $ kubectl run -i --tty busybox --image=busybox --restart=Never
# Start the nginx container using the default command, but use custom arguments (arg1 .. argN) for that command. # Start the nginx container using the default command, but use custom arguments (arg1 .. argN) for that command.
$ kubectl run nginx --image=nginx -- <arg1> <arg2> ... <argN> $ kubectl run nginx --image=nginx -- <arg1> <arg2> ... <argN>
# Start the nginx container using a different command and custom arguments # Start the nginx container using a different command and custom arguments.
$ kubectl run nginx --image=nginx --command -- <cmd> <arg1> ... <argN> $ kubectl run nginx --image=nginx --command -- <cmd> <arg1> ... <argN>
# Start the perl container to compute π to 2000 places and print it out.
$ kubectl run pi --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(2000)'
``` ```
### Options ### Options
@ -84,7 +87,7 @@ $ kubectl run nginx --image=nginx --command -- <cmd> <arg1> ... <argN>
--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 --env=[]: Environment variables to set in the container
--expose[=false]: If true, a public, external service is created for the container(s) which are run --expose[=false]: If true, a public, external service is created for the container(s) which are run
--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 'job/v1beta1'.
--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).
@ -97,7 +100,7 @@ $ kubectl run nginx --image=nginx --command -- <cmd> <arg1> ... <argN>
--port=-1: The port that this container exposes. If --expose is true, this is also the port used by the service that is created. --port=-1: The port that this container exposes. If --expose is true, this is also the port used by the service that is created.
-r, --replicas=1: Number of replicas to create for this container. Default is 1. -r, --replicas=1: Number of replicas to create for this container. Default is 1.
--requests="": The resource requirement requests for this container. For example, 'cpu=100m,memory=256Mi' --requests="": The resource requirement requests for this container. For example, 'cpu=100m,memory=256Mi'
--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' --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, a job is created for this pod and --replicas must be 1. Default 'Always'
--save-config[=false]: If true, the configuration of current object will be saved in its annotation. This is useful when you want to perform kubectl apply on this object in the future. --save-config[=false]: If true, the configuration of current object will be saved in its annotation. This is useful when you want to perform kubectl apply on this object in the future.
--service-generator="service/v2": The name of the generator to use for creating a service. Only used if --expose is true --service-generator="service/v2": The name of the generator to use for creating a service. Only used if --expose is true
--service-overrides="": An inline JSON override for the generated service object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field. Only used if --expose is true. --service-overrides="": An inline JSON override for the generated service object. If this is non-empty, it is used to override the generated object. Requires that the object supply a valid apiVersion field. Only used if --expose is true.

View File

@ -611,7 +611,7 @@ runTests() {
# Pre-Condition: no RC is running # Pre-Condition: no RC is running
kube::test::get_object_assert rc "{{range.items}}{{$id_field}}:{{end}}" '' kube::test::get_object_assert rc "{{range.items}}{{$id_field}}:{{end}}" ''
# Command: create the rc "nginx" with image nginx # Command: create the rc "nginx" with image nginx
kubectl run nginx --image=nginx --save-config "${kube_flags[@]}" kubectl run nginx --image=nginx --save-config --generator=run/v1 "${kube_flags[@]}"
# Post-Condition: rc "nginx" has configuration annotation # Post-Condition: rc "nginx" has configuration annotation
[[ "$(kubectl get rc nginx -o yaml "${kube_flags[@]}" | grep kubectl.kubernetes.io/last-applied-configuration)" ]] [[ "$(kubectl get rc nginx -o yaml "${kube_flags[@]}" | grep kubectl.kubernetes.io/last-applied-configuration)" ]]
## 5. kubectl expose --save-config should generate configuration annotation ## 5. kubectl expose --save-config should generate configuration annotation
@ -647,6 +647,24 @@ runTests() {
# Clean up # Clean up
kubectl delete pods test-pod "${kube_flags[@]}" kubectl delete pods test-pod "${kube_flags[@]}"
## kubectl run should create deployments or jobs
# Pre-Condition: no Job is running
kube::test::get_object_assert jobs "{{range.items}}{{$id_field}}:{{end}}" ''
# Command
kubectl run pi --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(20)'
# Post-Condition: Job "pi" is created
kube::test::get_object_assert jobs "{{range.items}}{{$id_field}}:{{end}}" 'pi:'
# Clean up
kubectl delete jobs pi
# Pre-Condition: no Deployment is running
kube::test::get_object_assert deployment "{{range.items}}{{$id_field}}:{{end}}" ''
# Command
kubectl run nginx --image=nginx --generator=deployment/v1beta1
# Post-Condition: Deployment "nginx" is created
kube::test::get_object_assert deployment "{{range.items}}{{$id_field}}:{{end}}" 'nginx:'
# Clean up
kubectl delete deployment nginx
############## ##############
# Namespaces # # Namespaces #
############## ##############

View File

@ -297,7 +297,10 @@ func ValidateDeploymentSpec(spec *extensions.DeploymentSpec) validation.ErrorLis
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(spec.Replicas), "replicas")...) allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(spec.Replicas), "replicas")...)
allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpecForRC(&spec.Template, spec.Selector, spec.Replicas, "template")...) allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpecForRC(&spec.Template, spec.Selector, spec.Replicas, "template")...)
allErrs = append(allErrs, ValidateDeploymentStrategy(&spec.Strategy, "strategy")...) allErrs = append(allErrs, ValidateDeploymentStrategy(&spec.Strategy, "strategy")...)
// empty string is a valid UniqueLabelKey
if len(spec.UniqueLabelKey) > 0 {
allErrs = append(allErrs, apivalidation.ValidateLabelName(spec.UniqueLabelKey, "uniqueLabel")...) allErrs = append(allErrs, apivalidation.ValidateLabelName(spec.UniqueLabelKey, "uniqueLabel")...)
}
return allErrs return allErrs
} }

View File

@ -34,7 +34,7 @@ import (
const ( const (
run_long = `Create and run a particular image, possibly replicated. run_long = `Create and run a particular image, possibly replicated.
Creates a replication controller to manage the created container(s).` Creates a replication controller or job 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
@ -53,14 +53,17 @@ $ kubectl run nginx --image=nginx --dry-run
# Start a single instance of nginx, but overload the spec of the replication controller with a partial set of values parsed from JSON. # Start a single instance of nginx, but overload the spec of the replication controller with a partial set of values parsed from JSON.
$ kubectl run nginx --image=nginx --overrides='{ "apiVersion": "v1", "spec": { ... } }' $ kubectl run nginx --image=nginx --overrides='{ "apiVersion": "v1", "spec": { ... } }'
# Start a single instance of nginx and keep it in the foreground, don't restart it if it exits. # Start a single instance of busybox and keep it in the foreground, don't restart it if it exits.
$ kubectl run -i --tty nginx --image=nginx --restart=Never $ kubectl run -i --tty busybox --image=busybox --restart=Never
# Start the nginx container using the default command, but use custom arguments (arg1 .. argN) for that command. # Start the nginx container using the default command, but use custom arguments (arg1 .. argN) for that command.
$ kubectl run nginx --image=nginx -- <arg1> <arg2> ... <argN> $ kubectl run nginx --image=nginx -- <arg1> <arg2> ... <argN>
# Start the nginx container using a different command and custom arguments # Start the nginx container using a different command and custom arguments.
$ kubectl run nginx --image=nginx --command -- <cmd> <arg1> ... <argN>` $ kubectl run nginx --image=nginx --command -- <cmd> <arg1> ... <argN>
# Start the perl container to compute π to 2000 places and print it out.
$ kubectl run pi --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(2000)'`
) )
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 {
@ -84,7 +87,8 @@ func NewCmdRun(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *c
} }
func addRunFlags(cmd *cobra.Command) { func addRunFlags(cmd *cobra.Command) {
cmd.Flags().String("generator", "", "The name of the API generator to use. Default is 'run/v1' if --restart=Always, otherwise the default is 'run-pod/v1'.") // TODO: Change the default to "deployment/v1beta1" (which is a valid generator) when deployment reaches beta (#15313)
cmd.Flags().String("generator", "", "The name of the API generator to use. Default is 'run/v1' if --restart=Always, otherwise the default is 'job/v1beta1'.")
cmd.Flags().String("image", "", "The image for the container to run.") cmd.Flags().String("image", "", "The image for the container to run.")
cmd.MarkFlagRequired("image") cmd.MarkFlagRequired("image")
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.")
@ -98,7 +102,7 @@ func addRunFlags(cmd *cobra.Command) {
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().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, a job is created for this pod 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'")
cmd.Flags().String("limits", "", "The resource requirement limits for this container. For example, 'cpu=200m,memory=512Mi'") cmd.Flags().String("limits", "", "The resource requirement limits for this container. For example, 'cpu=200m,memory=512Mi'")
@ -142,10 +146,11 @@ func Run(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cob
generatorName := cmdutil.GetFlagString(cmd, "generator") generatorName := cmdutil.GetFlagString(cmd, "generator")
if len(generatorName) == 0 { if len(generatorName) == 0 {
// TODO: Change the default to "deployment/v1beta1" when deployment reaches beta (#15313)
if restartPolicy == api.RestartPolicyAlways { if restartPolicy == api.RestartPolicyAlways {
generatorName = "run/v1" generatorName = "run/v1"
} else { } else {
generatorName = "run-pod/v1" generatorName = "job/v1beta1"
} }
} }
generator, found := f.Generator(generatorName) generator, found := f.Generator(generatorName)

View File

@ -36,6 +36,7 @@ import (
"k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/registered" "k8s.io/kubernetes/pkg/api/registered"
"k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/apis/extensions"
client "k8s.io/kubernetes/pkg/client/unversioned" client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/fields"
@ -116,6 +117,8 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
"service/v1": kubectl.ServiceGeneratorV1{}, "service/v1": kubectl.ServiceGeneratorV1{},
"service/v2": kubectl.ServiceGeneratorV2{}, "service/v2": kubectl.ServiceGeneratorV2{},
"horizontalpodautoscaler/v1beta1": kubectl.HorizontalPodAutoscalerV1Beta1{}, "horizontalpodautoscaler/v1beta1": kubectl.HorizontalPodAutoscalerV1Beta1{},
"deployment/v1beta1": kubectl.DeploymentV1Beta1{},
"job/v1beta1": kubectl.JobV1Beta1{},
} }
clientConfig := optionalClientConfig clientConfig := optionalClientConfig
@ -312,18 +315,11 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
} }
switch t := object.(type) { switch t := object.(type) {
case *api.ReplicationController: case *api.ReplicationController:
var pods *api.PodList return GetFirstPod(client, t.Namespace, t.Spec.Selector)
for pods == nil || len(pods.Items) == 0 { case *extensions.Deployment:
var err error return GetFirstPod(client, t.Namespace, t.Spec.Selector)
if pods, err = client.Pods(t.Namespace).List(labels.SelectorFromSet(t.Spec.Selector), fields.Everything()); err != nil { case *extensions.Job:
return nil, err return GetFirstPod(client, t.Namespace, t.Spec.Selector.MatchLabels)
}
if len(pods.Items) == 0 {
time.Sleep(2 * time.Second)
}
}
pod := &pods.Items[0]
return pod, nil
case *api.Pod: case *api.Pod:
return t, nil return t, nil
default: default:
@ -337,6 +333,22 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
} }
} }
// GetFirstPod returns the first pod of an object from its namespace and selector
func GetFirstPod(client *client.Client, namespace string, selector map[string]string) (*api.Pod, error) {
var pods *api.PodList
for pods == nil || len(pods.Items) == 0 {
var err error
if pods, err = client.Pods(namespace).List(labels.SelectorFromSet(selector), fields.Everything()); err != nil {
return nil, err
}
if len(pods.Items) == 0 {
time.Sleep(2 * time.Second)
}
}
pod := &pods.Items[0]
return pod, nil
}
// BindFlags adds any flags that are common to all kubectl sub commands. // BindFlags adds any flags that are common to all kubectl sub commands.
func (f *Factory) BindFlags(flags *pflag.FlagSet) { func (f *Factory) BindFlags(flags *pflag.FlagSet) {
// any flags defined by external projects (not part of pflags) // any flags defined by external projects (not part of pflags)

View File

@ -23,10 +23,264 @@ import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/util/validation" "k8s.io/kubernetes/pkg/util/validation"
) )
type DeploymentV1Beta1 struct{}
func (DeploymentV1Beta1) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"labels", false},
{"default-name", false},
{"name", true},
{"replicas", true},
{"image", true},
{"port", false},
{"hostport", false},
{"stdin", false},
{"tty", false},
{"command", false},
{"args", false},
{"env", false},
{"requests", false},
{"limits", false},
}
}
func (DeploymentV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
args, err := getArgs(genericParams)
if err != nil {
return nil, err
}
envs, err := getEnvs(genericParams)
if err != nil {
return nil, err
}
params, err := getParams(genericParams)
if err != nil {
return nil, err
}
name, err := getName(params)
if err != nil {
return nil, err
}
labels, err := getLabels(params, true, name)
if err != nil {
return nil, err
}
count, err := strconv.Atoi(params["replicas"])
if err != nil {
return nil, err
}
podSpec, err := makePodSpec(params, name)
if err != nil {
return nil, err
}
if err = updatePodContainers(params, args, envs, podSpec); err != nil {
return nil, err
}
if err := updatePodPorts(params, podSpec); err != nil {
return nil, err
}
// TODO: use versioned types for generators so that we don't need to
// set default values manually (see issue #17384)
deployment := extensions.Deployment{
ObjectMeta: api.ObjectMeta{
Name: name,
Labels: labels,
},
Spec: extensions.DeploymentSpec{
Replicas: count,
Selector: labels,
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: labels,
},
Spec: *podSpec,
},
UniqueLabelKey: "deployment.kubernetes.io/podTemplateHash",
},
}
return &deployment, nil
}
func getLabels(params map[string]string, defaultRunLabel bool, name string) (map[string]string, error) {
labelString, found := params["labels"]
var labels map[string]string
var err error
if found && len(labelString) > 0 {
labels, err = ParseLabels(labelString)
if err != nil {
return nil, err
}
} else if defaultRunLabel {
labels = map[string]string{
"run": name,
}
}
return labels, nil
}
func getName(params map[string]string) (string, error) {
name, found := params["name"]
if !found || len(name) == 0 {
name, found = params["default-name"]
if !found || len(name) == 0 {
return "", fmt.Errorf("'name' is a required parameter.")
}
}
return name, nil
}
func getParams(genericParams map[string]interface{}) (map[string]string, error) {
params := map[string]string{}
for key, value := range genericParams {
strVal, isString := value.(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
}
params[key] = strVal
}
return params, nil
}
func getArgs(genericParams map[string]interface{}) ([]string, error) {
args := []string{}
val, found := genericParams["args"]
if found {
var isArray bool
args, isArray = val.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found: %v", val)
}
delete(genericParams, "args")
}
return args, nil
}
func getEnvs(genericParams map[string]interface{}) ([]api.EnvVar, error) {
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)
}
}
return envs, nil
}
type JobV1Beta1 struct{}
func (JobV1Beta1) ParamNames() []GeneratorParam {
return []GeneratorParam{
{"labels", false},
{"default-name", false},
{"name", true},
{"image", true},
{"port", false},
{"hostport", false},
{"stdin", false},
{"leave-stdin-open", false},
{"tty", false},
{"command", false},
{"args", false},
{"env", false},
{"requests", false},
{"limits", false},
{"restart", false},
}
}
func (JobV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
args, err := getArgs(genericParams)
if err != nil {
return nil, err
}
envs, err := getEnvs(genericParams)
if err != nil {
return nil, err
}
params, err := getParams(genericParams)
if err != nil {
return nil, err
}
name, err := getName(params)
if err != nil {
return nil, err
}
labels, err := getLabels(params, true, name)
if err != nil {
return nil, err
}
podSpec, err := makePodSpec(params, name)
if err != nil {
return nil, err
}
if err = updatePodContainers(params, args, envs, podSpec); err != nil {
return nil, err
}
leaveStdinOpen, err := GetBool(params, "leave-stdin-open", false)
if err != nil {
return nil, err
}
podSpec.Containers[0].StdinOnce = !leaveStdinOpen && podSpec.Containers[0].Stdin
if err := updatePodPorts(params, podSpec); err != nil {
return nil, err
}
restartPolicy := api.RestartPolicy(params["restart"])
if len(restartPolicy) == 0 {
restartPolicy = api.RestartPolicyAlways
}
podSpec.RestartPolicy = restartPolicy
job := extensions.Job{
ObjectMeta: api.ObjectMeta{
Name: name,
Labels: labels,
},
Spec: extensions.JobSpec{
Selector: &extensions.PodSelector{
MatchLabels: labels,
},
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: labels,
},
Spec: *podSpec,
},
},
}
return &job, nil
}
type BasicReplicationController struct{} type BasicReplicationController struct{}
func (BasicReplicationController) ParamNames() []GeneratorParam { func (BasicReplicationController) ParamNames() []GeneratorParam {
@ -119,62 +373,31 @@ func makePodSpec(params map[string]string, name string) (*api.PodSpec, error) {
} }
func (BasicReplicationController) Generate(genericParams map[string]interface{}) (runtime.Object, error) { func (BasicReplicationController) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
args := []string{} args, err := getArgs(genericParams)
val, found := genericParams["args"]
if found {
var isArray bool
args, isArray = val.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found: %v", val)
}
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 { if err != nil {
return nil, err return nil, err
} }
delete(genericParams, "env")
} else {
return nil, fmt.Errorf("expected []string, found: %v", envStrings)
}
}
params := map[string]string{} envs, err := getEnvs(genericParams)
for key, value := range genericParams {
strVal, isString := value.(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
}
params[key] = strVal
}
name, found := params["name"]
if !found || len(name) == 0 {
name, found = params["default-name"]
if !found || len(name) == 0 {
return nil, fmt.Errorf("'name' is a required parameter.")
}
}
// TODO: extract this flag to a central location.
labelString, found := params["labels"]
var labels map[string]string
var err error
if found && len(labelString) > 0 {
labels, err = ParseLabels(labelString)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else {
labels = map[string]string{ params, err := getParams(genericParams)
"run": name, if err != nil {
return nil, err
} }
name, err := getName(params)
if err != nil {
return nil, err
} }
labels, err := getLabels(params, true, name)
if err != nil {
return nil, err
}
count, err := strconv.Atoi(params["replicas"]) count, err := strconv.Atoi(params["replicas"])
if err != nil { if err != nil {
return nil, err return nil, err
@ -184,20 +407,13 @@ func (BasicReplicationController) Generate(genericParams map[string]interface{})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(args) > 0 {
command, err := GetBool(params, "command", false) if err = updatePodContainers(params, args, envs, podSpec); err != nil {
if err != nil {
return nil, err return nil, err
} }
if command {
podSpec.Containers[0].Command = args
} else {
podSpec.Containers[0].Args = args
}
}
if len(envs) > 0 { if err := updatePodPorts(params, podSpec); err != nil {
podSpec.Containers[0].Env = envs return nil, err
} }
controller := api.ReplicationController{ controller := api.ReplicationController{
@ -216,12 +432,28 @@ func (BasicReplicationController) Generate(genericParams map[string]interface{})
}, },
}, },
} }
if err := updatePodPorts(params, &controller.Spec.Template.Spec); err != nil {
return nil, err
}
return &controller, nil return &controller, nil
} }
func updatePodContainers(params map[string]string, args []string, envs []api.EnvVar, podSpec *api.PodSpec) error {
if len(args) > 0 {
command, err := GetBool(params, "command", false)
if err != nil {
return err
}
if command {
podSpec.Containers[0].Command = args
} else {
podSpec.Containers[0].Args = args
}
}
if len(envs) > 0 {
podSpec.Containers[0].Env = envs
}
return nil
}
func updatePodPorts(params map[string]string, podSpec *api.PodSpec) (err error) { func updatePodPorts(params map[string]string, podSpec *api.PodSpec) (err error) {
port := -1 port := -1
hostPort := -1 hostPort := -1
@ -279,57 +511,31 @@ func (BasicPod) ParamNames() []GeneratorParam {
} }
func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object, error) { func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
args := []string{} args, err := getArgs(genericParams)
val, found := genericParams["args"]
if found {
var isArray bool
args, isArray = val.([]string)
if !isArray {
return nil, fmt.Errorf("expected []string, found: %v", val)
}
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 { if err != nil {
return nil, err return nil, err
} }
delete(genericParams, "env")
} else {
return nil, fmt.Errorf("expected []string, found: %v", envStrings)
}
}
params := map[string]string{} envs, err := getEnvs(genericParams)
for key, value := range genericParams {
strVal, isString := value.(string)
if !isString {
return nil, fmt.Errorf("expected string, saw %v for '%s'", value, key)
}
params[key] = strVal
}
name, found := params["name"]
if !found || len(name) == 0 {
name, found = params["default-name"]
if !found || len(name) == 0 {
return nil, fmt.Errorf("'name' is a required parameter.")
}
}
// TODO: extract this flag to a central location.
labelString, found := params["labels"]
var labels map[string]string
var err error
if found && len(labelString) > 0 {
labels, err = ParseLabels(labelString)
if err != nil { if err != nil {
return nil, err return nil, err
} }
params, err := getParams(genericParams)
if err != nil {
return nil, err
} }
name, err := getName(params)
if err != nil {
return nil, err
}
labels, err := getLabels(params, false, name)
if err != nil {
return nil, err
}
stdin, err := GetBool(params, "stdin", false) stdin, err := GetBool(params, "stdin", false)
if err != nil { if err != nil {
return nil, err return nil, err
@ -374,21 +580,9 @@ func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object,
RestartPolicy: restartPolicy, RestartPolicy: restartPolicy,
}, },
} }
if len(args) > 0 { if err = updatePodContainers(params, args, envs, &pod.Spec); err != nil {
command, err := GetBool(params, "command", false)
if err != nil {
return nil, err return nil, err
} }
if command {
pod.Spec.Containers[0].Command = args
} else {
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

View File

@ -22,6 +22,7 @@ import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/apis/extensions"
) )
func TestGenerate(t *testing.T) { func TestGenerate(t *testing.T) {
@ -625,3 +626,188 @@ func TestGeneratePod(t *testing.T) {
} }
} }
} }
func TestGenerateDeployment(t *testing.T) {
tests := []struct {
params map[string]interface{}
expected *extensions.Deployment
expectErr bool
}{
{
params: map[string]interface{}{
"labels": "foo=bar,baz=blah",
"name": "foo",
"replicas": "3",
"image": "someimage",
"port": "80",
"hostport": "80",
"stdin": "true",
"command": "true",
"args": []string{"bar", "baz", "blah"},
"env": []string{"a=b", "c=d"},
"requests": "cpu=100m,memory=100Mi",
"limits": "cpu=400m,memory=200Mi",
},
expected: &extensions.Deployment{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{"foo": "bar", "baz": "blah"},
},
Spec: extensions.DeploymentSpec{
Replicas: 3,
Selector: map[string]string{"foo": "bar", "baz": "blah"},
UniqueLabelKey: "deployment.kubernetes.io/podTemplateHash",
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"foo": "bar", "baz": "blah"},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "foo",
Image: "someimage",
Stdin: true,
Ports: []api.ContainerPort{
{
ContainerPort: 80,
HostPort: 80,
},
},
Command: []string{"bar", "baz", "blah"},
Env: []api.EnvVar{
{
Name: "a",
Value: "b",
},
{
Name: "c",
Value: "d",
},
},
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceCPU: resource.MustParse("100m"),
api.ResourceMemory: resource.MustParse("100Mi"),
},
Limits: api.ResourceList{
api.ResourceCPU: resource.MustParse("400m"),
api.ResourceMemory: resource.MustParse("200Mi"),
},
},
},
},
},
},
},
},
},
}
generator := DeploymentV1Beta1{}
for _, test := range tests {
obj, err := generator.Generate(test.params)
if !test.expectErr && err != nil {
t.Errorf("unexpected error: %v", err)
}
if test.expectErr && err != nil {
continue
}
if !reflect.DeepEqual(obj.(*extensions.Deployment), test.expected) {
t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*extensions.Deployment))
}
}
}
func TestGenerateJob(t *testing.T) {
tests := []struct {
params map[string]interface{}
expected *extensions.Job
expectErr bool
}{
{
params: map[string]interface{}{
"labels": "foo=bar,baz=blah",
"name": "foo",
"image": "someimage",
"port": "80",
"hostport": "80",
"stdin": "true",
"leave-stdin-open": "true",
"command": "true",
"args": []string{"bar", "baz", "blah"},
"env": []string{"a=b", "c=d"},
"requests": "cpu=100m,memory=100Mi",
"limits": "cpu=400m,memory=200Mi",
"restart": "OnFailure",
},
expected: &extensions.Job{
ObjectMeta: api.ObjectMeta{
Name: "foo",
Labels: map[string]string{"foo": "bar", "baz": "blah"},
},
Spec: extensions.JobSpec{
Selector: &extensions.PodSelector{
MatchLabels: map[string]string{"foo": "bar", "baz": "blah"},
},
Template: api.PodTemplateSpec{
ObjectMeta: api.ObjectMeta{
Labels: map[string]string{"foo": "bar", "baz": "blah"},
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyOnFailure,
Containers: []api.Container{
{
Name: "foo",
Image: "someimage",
Stdin: true,
StdinOnce: false,
Ports: []api.ContainerPort{
{
ContainerPort: 80,
HostPort: 80,
},
},
Command: []string{"bar", "baz", "blah"},
Env: []api.EnvVar{
{
Name: "a",
Value: "b",
},
{
Name: "c",
Value: "d",
},
},
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceCPU: resource.MustParse("100m"),
api.ResourceMemory: resource.MustParse("100Mi"),
},
Limits: api.ResourceList{
api.ResourceCPU: resource.MustParse("400m"),
api.ResourceMemory: resource.MustParse("200Mi"),
},
},
},
},
},
},
},
},
},
}
generator := JobV1Beta1{}
for _, test := range tests {
obj, err := generator.Generate(test.params)
if !test.expectErr && err != nil {
t.Errorf("unexpected error: %v", err)
}
if test.expectErr && err != nil {
continue
}
if !reflect.DeepEqual(obj.(*extensions.Job), test.expected) {
t.Errorf("\nexpected:\n%#v\nsaw:\n%#v", test.expected, obj.(*extensions.Job))
}
}
}

View File

@ -44,6 +44,7 @@ import (
client "k8s.io/kubernetes/pkg/client/unversioned" client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/util/wait" "k8s.io/kubernetes/pkg/util/wait"
@ -367,7 +368,7 @@ var _ = Describe("Kubectl client", func() {
execOrDie() execOrDie()
Expect(runOutput).To(ContainSubstring("abcd1234")) Expect(runOutput).To(ContainSubstring("abcd1234"))
Expect(runOutput).To(ContainSubstring("stdin closed")) Expect(runOutput).To(ContainSubstring("stdin closed"))
Expect(c.Pods(ns).Delete("run-test", api.NewDeleteOptions(0))).To(BeNil()) Expect(c.Extensions().Jobs(ns).Delete("run-test", api.NewDeleteOptions(0))).To(BeNil())
By("executing a command with run and attach without stdin") 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'"). 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'").
@ -375,24 +376,28 @@ var _ = Describe("Kubectl client", func() {
execOrDie() execOrDie()
Expect(runOutput).ToNot(ContainSubstring("abcd1234")) Expect(runOutput).ToNot(ContainSubstring("abcd1234"))
Expect(runOutput).To(ContainSubstring("stdin closed")) Expect(runOutput).To(ContainSubstring("stdin closed"))
Expect(c.Pods(ns).Delete("run-test-2", api.NewDeleteOptions(0))).To(BeNil()) Expect(c.Extensions().Jobs(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") 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'"). 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"). withStdinData("abcd1234\n").
execOrDie() execOrDie()
Expect(runOutput).ToNot(ContainSubstring("stdin closed")) Expect(runOutput).ToNot(ContainSubstring("stdin closed"))
if !checkPodsRunningReady(c, ns, []string{"run-test-3"}, time.Minute) { runTestPod, err := util.GetFirstPod(c, ns, map[string]string{"run": "run-test-3"})
Failf("Pod %q should still be running", "run-test-3") if err != nil {
os.Exit(1)
}
if !checkPodsRunningReady(c, ns, []string{runTestPod.Name}, time.Minute) {
Failf("Pod %q of Job %q should still be running", runTestPod.Name, "run-test-3")
} }
// NOTE: we cannot guarantee our output showed up in the container logs before stdin was closed, so we have // NOTE: we cannot guarantee our output showed up in the container logs before stdin was closed, so we have
// to loop test. // to loop test.
err := wait.PollImmediate(time.Second, time.Minute, func() (bool, error) { err = wait.PollImmediate(time.Second, time.Minute, func() (bool, error) {
if !checkPodsRunningReady(c, ns, []string{"run-test-3"}, 1*time.Second) { if !checkPodsRunningReady(c, ns, []string{runTestPod.Name}, 1*time.Second) {
Failf("Pod %q should still be running", "run-test-3") Failf("Pod %q of Job %q should still be running", runTestPod.Name, "run-test-3")
} }
logOutput := runKubectlOrDie(nsFlag, "logs", "run-test-3") logOutput := runKubectlOrDie(nsFlag, "logs", runTestPod.Name)
Expect(logOutput).ToNot(ContainSubstring("stdin closed")) Expect(logOutput).ToNot(ContainSubstring("stdin closed"))
return strings.Contains(logOutput, "abcd1234"), nil return strings.Contains(logOutput, "abcd1234"), nil
}) })
@ -401,7 +406,7 @@ var _ = Describe("Kubectl client", func() {
} }
Expect(err).To(BeNil()) Expect(err).To(BeNil())
Expect(c.Pods(ns).Delete("run-test-3", api.NewDeleteOptions(0))).To(BeNil()) Expect(c.Extensions().Jobs(ns).Delete("run-test-3", api.NewDeleteOptions(0))).To(BeNil())
}) })
It("should support port-forward", func() { It("should support port-forward", func() {
@ -804,54 +809,54 @@ var _ = Describe("Kubectl client", func() {
}) })
Describe("Kubectl run pod", func() { Describe("Kubectl run job", func() {
var nsFlag string var nsFlag string
var podName string var jobName string
BeforeEach(func() { BeforeEach(func() {
nsFlag = fmt.Sprintf("--namespace=%v", ns) nsFlag = fmt.Sprintf("--namespace=%v", ns)
podName = "e2e-test-nginx-pod" jobName = "e2e-test-nginx-job"
}) })
AfterEach(func() { AfterEach(func() {
runKubectlOrDie("stop", "pods", podName, nsFlag) runKubectlOrDie("stop", "jobs", jobName, nsFlag)
}) })
It("should create a pod from an image when restart is OnFailure [Conformance]", func() { It("should create a job from an image when restart is OnFailure [Conformance]", func() {
image := "nginx" image := "nginx"
By("running the image " + image) By("running the image " + image)
runKubectlOrDie("run", podName, "--restart=OnFailure", "--image="+image, nsFlag) runKubectlOrDie("run", jobName, "--restart=OnFailure", "--image="+image, nsFlag)
By("verifying the pod " + podName + " was created") By("verifying the job " + jobName + " was created")
pod, err := c.Pods(ns).Get(podName) job, err := c.Extensions().Jobs(ns).Get(jobName)
if err != nil { if err != nil {
Failf("Failed getting pod %s: %v", podName, err) Failf("Failed getting job %s: %v", jobName, err)
} }
containers := pod.Spec.Containers containers := job.Spec.Template.Spec.Containers
if containers == nil || len(containers) != 1 || containers[0].Image != image { if containers == nil || len(containers) != 1 || containers[0].Image != image {
Failf("Failed creating pod %s for 1 pod with expected image %s", podName, image) Failf("Failed creating job %s for 1 pod with expected image %s", jobName, image)
} }
if pod.Spec.RestartPolicy != api.RestartPolicyOnFailure { if job.Spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure {
Failf("Failed creating a pod with correct restart policy for --restart=OnFailure") Failf("Failed creating a job with correct restart policy for --restart=OnFailure")
} }
}) })
It("should create a pod from an image when restart is Never [Conformance]", func() { It("should create a job from an image when restart is Never [Conformance]", func() {
image := "nginx" image := "nginx"
By("running the image " + image) By("running the image " + image)
runKubectlOrDie("run", podName, "--restart=Never", "--image="+image, nsFlag) runKubectlOrDie("run", jobName, "--restart=Never", "--image="+image, nsFlag)
By("verifying the pod " + podName + " was created") By("verifying the job " + jobName + " was created")
pod, err := c.Pods(ns).Get(podName) job, err := c.Extensions().Jobs(ns).Get(jobName)
if err != nil { if err != nil {
Failf("Failed getting pod %s: %v", podName, err) Failf("Failed getting job %s: %v", jobName, err)
} }
containers := pod.Spec.Containers containers := job.Spec.Template.Spec.Containers
if containers == nil || len(containers) != 1 || containers[0].Image != image { if containers == nil || len(containers) != 1 || containers[0].Image != image {
Failf("Failed creating pod %s for 1 pod with expected image %s", podName, image) Failf("Failed creating job %s for 1 pod with expected image %s", jobName, image)
} }
if pod.Spec.RestartPolicy != api.RestartPolicyNever { if job.Spec.Template.Spec.RestartPolicy != api.RestartPolicyNever {
Failf("Failed creating a pod with correct restart policy for --restart=OnFailure") Failf("Failed creating a job with correct restart policy for --restart=OnFailure")
} }
}) })