Added job/v1 generator to kubectl run

This commit is contained in:
Maciej Szulik 2016-03-10 00:06:49 +01:00
parent aca37830b1
commit de83631768
6 changed files with 270 additions and 10 deletions

View File

@ -40,7 +40,7 @@ Creates a deployment or job to manage the created container(s).
.PP .PP
\fB\-\-generator\fP="" \fB\-\-generator\fP=""
The name of the API generator to use. Default is 'deployment/v1beta1' if \-\-restart=Always, otherwise the default is 'job/v1beta1'. The name of the API generator to use. Default is 'deployment/v1beta1' if \-\-restart=Always, otherwise the default is 'job/v1'.
.PP .PP
\fB\-\-hostport\fP=\-1 \fB\-\-hostport\fP=\-1

View File

@ -88,7 +88,7 @@ kubectl run pi --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print
--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 'deployment/v1beta1' if --restart=Always, otherwise the default is 'job/v1beta1'. --generator="": The name of the API generator to use. Default is 'deployment/v1beta1' if --restart=Always, otherwise the default is 'job/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.
-l, --labels="": Labels to apply to the pod(s). -l, --labels="": Labels to apply to the pod(s).

View File

@ -755,6 +755,12 @@ __EOF__
kube::test::get_object_assert jobs "{{range.items}}{{$id_field}}:{{end}}" 'pi:' kube::test::get_object_assert jobs "{{range.items}}{{$id_field}}:{{end}}" 'pi:'
# Clean up # Clean up
kubectl delete jobs pi "${kube_flags[@]}" kubectl delete jobs pi "${kube_flags[@]}"
# Command
kubectl run pi --generator=job/v1 --image=perl --restart=OnFailure -- perl -Mbignum=bpi -wle 'print bpi(20)' "${kube_flags[@]}"
# Post-Condition: Job "pi" is created
kube::test::get_object_assert jobs "{{range.items}}{{$id_field}}:{{end}}" 'pi:'
# Clean up
kubectl delete jobs pi "${kube_flags[@]}"
# Post-condition: no pods exist. # Post-condition: no pods exist.
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" '' kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" ''
# Pre-Condition: no Deployment exists # Pre-Condition: no Deployment exists

View File

@ -88,7 +88,7 @@ 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 'deployment/v1beta1' if --restart=Always, otherwise the default is 'job/v1beta1'.") cmd.Flags().String("generator", "", "The name of the API generator to use. Default is 'deployment/v1beta1' if --restart=Always, otherwise the default is 'job/v1'.")
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.")
@ -149,7 +149,7 @@ func Run(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cob
if restartPolicy == api.RestartPolicyAlways { if restartPolicy == api.RestartPolicyAlways {
generatorName = "deployment/v1beta1" generatorName = "deployment/v1beta1"
} else { } else {
generatorName = "job/v1beta1" generatorName = "job/v1"
} }
} }
generators := f.Generators("run") generators := f.Generators("run")

View File

@ -143,6 +143,7 @@ const (
HorizontalPodAutoscalerV1Beta1GeneratorName = "horizontalpodautoscaler/v1beta1" HorizontalPodAutoscalerV1Beta1GeneratorName = "horizontalpodautoscaler/v1beta1"
DeploymentV1Beta1GeneratorName = "deployment/v1beta1" DeploymentV1Beta1GeneratorName = "deployment/v1beta1"
JobV1Beta1GeneratorName = "job/v1beta1" JobV1Beta1GeneratorName = "job/v1beta1"
JobV1GeneratorName = "job/v1"
NamespaceV1GeneratorName = "namespace/v1" NamespaceV1GeneratorName = "namespace/v1"
SecretV1GeneratorName = "secret/v1" SecretV1GeneratorName = "secret/v1"
SecretForDockerRegistryV1GeneratorName = "secret-for-docker-registry/v1" SecretForDockerRegistryV1GeneratorName = "secret-for-docker-registry/v1"
@ -161,6 +162,7 @@ func DefaultGenerators(cmdName string) map[string]kubectl.Generator {
RunPodV1GeneratorName: kubectl.BasicPod{}, RunPodV1GeneratorName: kubectl.BasicPod{},
DeploymentV1Beta1GeneratorName: kubectl.DeploymentV1Beta1{}, DeploymentV1Beta1GeneratorName: kubectl.DeploymentV1Beta1{},
JobV1Beta1GeneratorName: kubectl.JobV1Beta1{}, JobV1Beta1GeneratorName: kubectl.JobV1Beta1{},
JobV1GeneratorName: kubectl.JobV1{},
} }
generators["autoscale"] = map[string]kubectl.Generator{ generators["autoscale"] = map[string]kubectl.Generator{
HorizontalPodAutoscalerV1Beta1GeneratorName: kubectl.HorizontalPodAutoscalerV1Beta1{}, HorizontalPodAutoscalerV1Beta1GeneratorName: kubectl.HorizontalPodAutoscalerV1Beta1{},

View File

@ -24,6 +24,8 @@ 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/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/v1"
batchv1 "k8s.io/kubernetes/pkg/apis/batch/v1"
"k8s.io/kubernetes/pkg/apis/extensions" "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"
@ -187,6 +189,24 @@ func getEnvs(genericParams map[string]interface{}) ([]api.EnvVar, error) {
return envs, nil return envs, nil
} }
func getV1Envs(genericParams map[string]interface{}) ([]v1.EnvVar, error) {
var envs []v1.EnvVar
envStrings, found := genericParams["env"]
if found {
if envStringArray, isArray := envStrings.([]string); isArray {
var err error
envs, err = parseV1Envs(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{} type JobV1Beta1 struct{}
func (JobV1Beta1) ParamNames() []GeneratorParam { func (JobV1Beta1) ParamNames() []GeneratorParam {
@ -256,7 +276,7 @@ func (JobV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object
restartPolicy := api.RestartPolicy(params["restart"]) restartPolicy := api.RestartPolicy(params["restart"])
if len(restartPolicy) == 0 { if len(restartPolicy) == 0 {
restartPolicy = api.RestartPolicyAlways restartPolicy = api.RestartPolicyNever
} }
podSpec.RestartPolicy = restartPolicy podSpec.RestartPolicy = restartPolicy
@ -282,6 +302,97 @@ func (JobV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object
return &job, nil return &job, nil
} }
type JobV1 struct{}
func (JobV1) 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 (JobV1) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
args, err := getArgs(genericParams)
if err != nil {
return nil, err
}
envs, err := getV1Envs(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 := makeV1PodSpec(params, name)
if err != nil {
return nil, err
}
if err = updateV1PodContainers(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 := updateV1PodPorts(params, podSpec); err != nil {
return nil, err
}
restartPolicy := v1.RestartPolicy(params["restart"])
if len(restartPolicy) == 0 {
restartPolicy = v1.RestartPolicyNever
}
podSpec.RestartPolicy = restartPolicy
job := batchv1.Job{
ObjectMeta: v1.ObjectMeta{
Name: name,
Labels: labels,
},
Spec: batchv1.JobSpec{
Template: v1.PodTemplateSpec{
ObjectMeta: v1.ObjectMeta{
Labels: labels,
},
Spec: *podSpec,
},
},
}
return &job, nil
}
type BasicReplicationController struct{} type BasicReplicationController struct{}
func (BasicReplicationController) ParamNames() []GeneratorParam { func (BasicReplicationController) ParamNames() []GeneratorParam {
@ -327,6 +438,30 @@ func populateResourceList(spec string) (api.ResourceList, error) {
return result, nil return result, nil
} }
// populateResourceList takes strings of form <resourceName1>=<value1>,<resourceName1>=<value2>
func populateV1ResourceList(spec string) (v1.ResourceList, error) {
// empty input gets a nil response to preserve generator test expected behaviors
if spec == "" {
return nil, nil
}
result := v1.ResourceList{}
resourceStatements := strings.Split(spec, ",")
for _, resourceStatement := range resourceStatements {
parts := strings.Split(resourceStatement, "=")
if len(parts) != 2 {
return nil, fmt.Errorf("Invalid argument syntax %v, expected <resource>=<value>", resourceStatement)
}
resourceName := v1.ResourceName(parts[0])
resourceQuantity, err := resource.ParseQuantity(parts[1])
if err != nil {
return nil, err
}
result[resourceName] = *resourceQuantity
}
return result, nil
}
// HandleResourceRequirements parses the limits and requests parameters if specified // HandleResourceRequirements parses the limits and requests parameters if specified
func HandleResourceRequirements(params map[string]string) (api.ResourceRequirements, error) { func HandleResourceRequirements(params map[string]string) (api.ResourceRequirements, error) {
result := api.ResourceRequirements{} result := api.ResourceRequirements{}
@ -343,6 +478,22 @@ func HandleResourceRequirements(params map[string]string) (api.ResourceRequireme
return result, nil return result, nil
} }
// HandleResourceRequirements parses the limits and requests parameters if specified
func handleV1ResourceRequirements(params map[string]string) (v1.ResourceRequirements, error) {
result := v1.ResourceRequirements{}
limits, err := populateV1ResourceList(params["limits"])
if err != nil {
return result, err
}
result.Limits = limits
requests, err := populateV1ResourceList(params["requests"])
if err != nil {
return result, err
}
result.Requests = requests
return result, nil
}
func makePodSpec(params map[string]string, name string) (*api.PodSpec, error) { func makePodSpec(params map[string]string, name string) (*api.PodSpec, error) {
stdin, err := GetBool(params, "stdin", false) stdin, err := GetBool(params, "stdin", false)
if err != nil { if err != nil {
@ -373,6 +524,36 @@ func makePodSpec(params map[string]string, name string) (*api.PodSpec, error) {
return &spec, nil return &spec, nil
} }
func makeV1PodSpec(params map[string]string, name string) (*v1.PodSpec, error) {
stdin, err := GetBool(params, "stdin", false)
if err != nil {
return nil, err
}
tty, err := GetBool(params, "tty", false)
if err != nil {
return nil, err
}
resourceRequirements, err := handleV1ResourceRequirements(params)
if err != nil {
return nil, err
}
spec := v1.PodSpec{
Containers: []v1.Container{
{
Name: name,
Image: params["image"],
Stdin: stdin,
TTY: tty,
Resources: resourceRequirements,
},
},
}
return &spec, nil
}
func (BasicReplicationController) Generate(genericParams map[string]interface{}) (runtime.Object, error) { func (BasicReplicationController) Generate(genericParams map[string]interface{}) (runtime.Object, error) {
args, err := getArgs(genericParams) args, err := getArgs(genericParams)
if err != nil { if err != nil {
@ -455,6 +636,25 @@ func updatePodContainers(params map[string]string, args []string, envs []api.Env
return nil return nil
} }
func updateV1PodContainers(params map[string]string, args []string, envs []v1.EnvVar, podSpec *v1.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
@ -489,6 +689,40 @@ func updatePodPorts(params map[string]string, podSpec *api.PodSpec) (err error)
return nil return nil
} }
func updateV1PodPorts(params map[string]string, podSpec *v1.PodSpec) (err error) {
port := -1
hostPort := -1
if len(params["port"]) > 0 {
port, err = strconv.Atoi(params["port"])
if err != nil {
return err
}
}
if len(params["hostport"]) > 0 {
hostPort, err = strconv.Atoi(params["hostport"])
if err != nil {
return err
}
if hostPort > 0 && port < 0 {
return fmt.Errorf("--hostport requires --port to be specified")
}
}
// Don't include the port if it was not specified.
if port > 0 {
podSpec.Containers[0].Ports = []v1.ContainerPort{
{
ContainerPort: int32(port),
},
}
if hostPort > 0 {
podSpec.Containers[0].Ports[0].HostPort = int32(hostPort)
}
}
return nil
}
type BasicPod struct{} type BasicPod struct{}
func (BasicPod) ParamNames() []GeneratorParam { func (BasicPod) ParamNames() []GeneratorParam {
@ -609,6 +843,24 @@ func parseEnvs(envArray []string) ([]api.EnvVar, error) {
return envs, nil return envs, nil
} }
func parseV1Envs(envArray []string) ([]v1.EnvVar, error) {
envs := []v1.EnvVar{}
for _, env := range envArray {
pos := strings.Index(env, "=")
if pos == -1 {
return nil, fmt.Errorf("invalid env: %v", env)
}
name := env[:pos]
value := env[pos+1:]
if len(name) == 0 || !validation.IsCIdentifier(name) || len(value) == 0 {
return nil, fmt.Errorf("invalid env: %v", env)
}
envVar := v1.EnvVar{Name: name, Value: value}
envs = append(envs, envVar)
}
return envs, nil
}
func newBool(val bool) *bool { func newBool(val bool) *bool {
p := new(bool) p := new(bool)
*p = val *p = val