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
.PP
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
@ -40,7 +40,7 @@ Creates a replication controller to manage the created container(s).
.PP
\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
\fB\-\-hostport\fP=\-1
@ -94,7 +94,7 @@ Creates a replication controller to manage the created container(s).
.PP
\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
\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.
$ 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.
$ kubectl run \-i \-\-tty nginx \-\-image=nginx \-\-restart=Never
# Start a single instance of busybox and keep it in the foreground, don't restart it if it exits.
$ 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.
$ 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>
# 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
.RE

View File

@ -39,7 +39,7 @@ Run a particular image on the cluster.
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...]
@ -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.
$ 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.
$ kubectl run -i --tty nginx --image=nginx --restart=Never
# Start a single instance of busybox and keep it in the foreground, don't restart it if it exits.
$ 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.
$ 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>
# 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
@ -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.
--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
--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.
--image="": The image for the container to run.
-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.
-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'
--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.
--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.

View File

@ -611,7 +611,7 @@ runTests() {
# Pre-Condition: no RC is running
kube::test::get_object_assert rc "{{range.items}}{{$id_field}}:{{end}}" ''
# 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
[[ "$(kubectl get rc nginx -o yaml "${kube_flags[@]}" | grep kubectl.kubernetes.io/last-applied-configuration)" ]]
## 5. kubectl expose --save-config should generate configuration annotation
@ -647,6 +647,24 @@ runTests() {
# Clean up
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 #
##############

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.ValidatePodTemplateSpecForRC(&spec.Template, spec.Selector, spec.Replicas, "template")...)
allErrs = append(allErrs, ValidateDeploymentStrategy(&spec.Strategy, "strategy")...)
allErrs = append(allErrs, apivalidation.ValidateLabelName(spec.UniqueLabelKey, "uniqueLabel")...)
// empty string is a valid UniqueLabelKey
if len(spec.UniqueLabelKey) > 0 {
allErrs = append(allErrs, apivalidation.ValidateLabelName(spec.UniqueLabelKey, "uniqueLabel")...)
}
return allErrs
}

View File

@ -34,7 +34,7 @@ import (
const (
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.
$ 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.
$ 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.
$ kubectl run -i --tty nginx --image=nginx --restart=Never
# Start a single instance of busybox and keep it in the foreground, don't restart it if it exits.
$ 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.
$ kubectl run nginx --image=nginx -- <arg1> <arg2> ... <argN>
# Start the nginx container using a different command and custom arguments
$ kubectl run nginx --image=nginx --command -- <cmd> <arg1> ... <argN>`
# Start the nginx container using a different command and custom arguments.
$ 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 {
@ -84,7 +87,8 @@ func NewCmdRun(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer) *c
}
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.MarkFlagRequired("image")
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("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, 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().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'")
@ -142,10 +146,11 @@ func Run(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cob
generatorName := cmdutil.GetFlagString(cmd, "generator")
if len(generatorName) == 0 {
// TODO: Change the default to "deployment/v1beta1" when deployment reaches beta (#15313)
if restartPolicy == api.RestartPolicyAlways {
generatorName = "run/v1"
} else {
generatorName = "run-pod/v1"
generatorName = "job/v1beta1"
}
}
generator, found := f.Generator(generatorName)

View File

@ -36,6 +36,7 @@ import (
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/registered"
"k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/apis/extensions"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
"k8s.io/kubernetes/pkg/fields"
@ -116,6 +117,8 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
"service/v1": kubectl.ServiceGeneratorV1{},
"service/v2": kubectl.ServiceGeneratorV2{},
"horizontalpodautoscaler/v1beta1": kubectl.HorizontalPodAutoscalerV1Beta1{},
"deployment/v1beta1": kubectl.DeploymentV1Beta1{},
"job/v1beta1": kubectl.JobV1Beta1{},
}
clientConfig := optionalClientConfig
@ -312,18 +315,11 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
}
switch t := object.(type) {
case *api.ReplicationController:
var pods *api.PodList
for pods == nil || len(pods.Items) == 0 {
var err error
if pods, err = client.Pods(t.Namespace).List(labels.SelectorFromSet(t.Spec.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
return GetFirstPod(client, t.Namespace, t.Spec.Selector)
case *extensions.Deployment:
return GetFirstPod(client, t.Namespace, t.Spec.Selector)
case *extensions.Job:
return GetFirstPod(client, t.Namespace, t.Spec.Selector.MatchLabels)
case *api.Pod:
return t, nil
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.
func (f *Factory) BindFlags(flags *pflag.FlagSet) {
// 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/resource"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/runtime"
"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{}
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) {
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")
args, err := getArgs(genericParams)
if err != nil {
return nil, err
}
// 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)
}
envs, err := getEnvs(genericParams)
if err != nil {
return nil, err
}
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
params, err := getParams(genericParams)
if err != nil {
return nil, err
}
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.")
}
name, err := getName(params)
if err != nil {
return nil, err
}
// 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 {
return nil, err
}
} else {
labels = map[string]string{
"run": name,
}
labels, err := getLabels(params, true, name)
if err != nil {
return nil, err
}
count, err := strconv.Atoi(params["replicas"])
if err != nil {
return nil, err
@ -184,20 +407,13 @@ func (BasicReplicationController) Generate(genericParams map[string]interface{})
if err != nil {
return nil, err
}
if len(args) > 0 {
command, err := GetBool(params, "command", false)
if err != nil {
return nil, err
}
if command {
podSpec.Containers[0].Command = args
} else {
podSpec.Containers[0].Args = args
}
if err = updatePodContainers(params, args, envs, podSpec); err != nil {
return nil, err
}
if len(envs) > 0 {
podSpec.Containers[0].Env = envs
if err := updatePodPorts(params, podSpec); err != nil {
return nil, err
}
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
}
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) {
port := -1
hostPort := -1
@ -279,57 +511,31 @@ func (BasicPod) ParamNames() []GeneratorParam {
}
func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object, 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")
}
// 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)
}
args, err := getArgs(genericParams)
if err != nil {
return nil, err
}
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
envs, err := getEnvs(genericParams)
if err != nil {
return nil, err
}
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.")
}
params, err := getParams(genericParams)
if err != nil {
return nil, err
}
// 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 {
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)
if err != nil {
return nil, err
@ -374,20 +580,8 @@ func (BasicPod) Generate(genericParams map[string]interface{}) (runtime.Object,
RestartPolicy: restartPolicy,
},
}
if len(args) > 0 {
command, err := GetBool(params, "command", false)
if err != nil {
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 = updatePodContainers(params, args, envs, &pod.Spec); err != nil {
return nil, err
}
if err := updatePodPorts(params, &pod.Spec); err != nil {

View File

@ -22,6 +22,7 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/apis/extensions"
)
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"
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/kubectl"
"k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/util/wait"
@ -367,7 +368,7 @@ var _ = Describe("Kubectl client", func() {
execOrDie()
Expect(runOutput).To(ContainSubstring("abcd1234"))
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")
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()
Expect(runOutput).ToNot(ContainSubstring("abcd1234"))
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")
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").
execOrDie()
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")
runTestPod, err := util.GetFirstPod(c, ns, map[string]string{"run": "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
// 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")
err = wait.PollImmediate(time.Second, time.Minute, func() (bool, error) {
if !checkPodsRunningReady(c, ns, []string{runTestPod.Name}, 1*time.Second) {
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"))
return strings.Contains(logOutput, "abcd1234"), nil
})
@ -401,7 +406,7 @@ var _ = Describe("Kubectl client", func() {
}
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() {
@ -804,54 +809,54 @@ var _ = Describe("Kubectl client", func() {
})
Describe("Kubectl run pod", func() {
Describe("Kubectl run job", func() {
var nsFlag string
var podName string
var jobName string
BeforeEach(func() {
nsFlag = fmt.Sprintf("--namespace=%v", ns)
podName = "e2e-test-nginx-pod"
jobName = "e2e-test-nginx-job"
})
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"
By("running the image " + image)
runKubectlOrDie("run", podName, "--restart=OnFailure", "--image="+image, nsFlag)
By("verifying the pod " + podName + " was created")
pod, err := c.Pods(ns).Get(podName)
runKubectlOrDie("run", jobName, "--restart=OnFailure", "--image="+image, nsFlag)
By("verifying the job " + jobName + " was created")
job, err := c.Extensions().Jobs(ns).Get(jobName)
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 {
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 {
Failf("Failed creating a pod with correct restart policy for --restart=OnFailure")
if job.Spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure {
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"
By("running the image " + image)
runKubectlOrDie("run", podName, "--restart=Never", "--image="+image, nsFlag)
By("verifying the pod " + podName + " was created")
pod, err := c.Pods(ns).Get(podName)
runKubectlOrDie("run", jobName, "--restart=Never", "--image="+image, nsFlag)
By("verifying the job " + jobName + " was created")
job, err := c.Extensions().Jobs(ns).Get(jobName)
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 {
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 {
Failf("Failed creating a pod with correct restart policy for --restart=OnFailure")
if job.Spec.Template.Spec.RestartPolicy != api.RestartPolicyNever {
Failf("Failed creating a job with correct restart policy for --restart=OnFailure")
}
})