Added job/v1 generator to kubectl run

pull/6/head
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
\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
\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.
--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 '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.
--image="": The image for the container to run.
-l, --labels="": Labels to apply to the pod(s).

View File

@ -485,11 +485,11 @@ runTests() {
kube::test::get_object_assert pods "{{range.items}}{{$image_field}}:{{end}}" 'nginx:'
# Post-condition: valid-pod has the record annotation
kube::test::get_object_assert pods "{{range.items}}{{$annotations_field}}:{{end}}" "${change_cause_annotation}"
# prove that patch can use different types
# prove that patch can use different types
kubectl patch "${kube_flags[@]}" pod valid-pod --type="json" -p='[{"op": "replace", "path": "/spec/containers/0/image", "value":"nginx2"}]'
# Post-condition: valid-pod POD has image nginx
kube::test::get_object_assert pods "{{range.items}}{{$image_field}}:{{end}}" 'nginx2:'
# prove that patch can use different types
# prove that patch can use different types
kubectl patch "${kube_flags[@]}" pod valid-pod --type="json" -p='[{"op": "replace", "path": "/spec/containers/0/image", "value":"nginx"}]'
# Post-condition: valid-pod POD has image nginx
kube::test::get_object_assert pods "{{range.items}}{{$image_field}}:{{end}}" 'nginx:'
@ -545,7 +545,7 @@ runTests() {
kube::test::get_object_assert 'pod valid-pod' "{{(index .spec.containers 0).name}}" 'replaced-k8s-serve-hostname'
#cleaning
rm /tmp/tmp-valid-pod.json
## replace of a cluster scoped resource can succeed
# Pre-condition: a node exists
kubectl create -f - "${kube_flags[@]}" << __EOF__
@ -755,6 +755,12 @@ __EOF__
kube::test::get_object_assert jobs "{{range.items}}{{$id_field}}:{{end}}" 'pi:'
# Clean up
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.
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" ''
# Pre-Condition: no Deployment exists
@ -853,7 +859,7 @@ __EOF__
# Command
[[ "$(kubectl create secret generic test-secret --namespace=test-secrets --from-literal=key1=value1 --output=go-template --template=\"{{.metadata.name}}:\" | grep 'test-secret:')" ]]
## Clean-up
kubectl delete secret test-secret --namespace=test-secrets
kubectl delete secret test-secret --namespace=test-secrets
# Clean up
kubectl delete namespace test-secrets
@ -884,7 +890,7 @@ __EOF__
# Clean-up
kubectl delete configmap test-configmap --namespace=test-configmaps
kubectl delete namespace test-configmaps
####################
# Service Accounts #
####################

View File

@ -88,7 +88,7 @@ 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 '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.MarkFlagRequired("image")
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 {
generatorName = "deployment/v1beta1"
} else {
generatorName = "job/v1beta1"
generatorName = "job/v1"
}
}
generators := f.Generators("run")

View File

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

View File

@ -24,6 +24,8 @@ import (
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/resource"
"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/runtime"
"k8s.io/kubernetes/pkg/util/validation"
@ -187,6 +189,24 @@ func getEnvs(genericParams map[string]interface{}) ([]api.EnvVar, error) {
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{}
func (JobV1Beta1) ParamNames() []GeneratorParam {
@ -256,7 +276,7 @@ func (JobV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object
restartPolicy := api.RestartPolicy(params["restart"])
if len(restartPolicy) == 0 {
restartPolicy = api.RestartPolicyAlways
restartPolicy = api.RestartPolicyNever
}
podSpec.RestartPolicy = restartPolicy
@ -282,6 +302,97 @@ func (JobV1Beta1) Generate(genericParams map[string]interface{}) (runtime.Object
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{}
func (BasicReplicationController) ParamNames() []GeneratorParam {
@ -327,6 +438,30 @@ func populateResourceList(spec string) (api.ResourceList, error) {
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
func HandleResourceRequirements(params map[string]string) (api.ResourceRequirements, error) {
result := api.ResourceRequirements{}
@ -343,6 +478,22 @@ func HandleResourceRequirements(params map[string]string) (api.ResourceRequireme
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) {
stdin, err := GetBool(params, "stdin", false)
if err != nil {
@ -373,6 +524,36 @@ func makePodSpec(params map[string]string, name string) (*api.PodSpec, error) {
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) {
args, err := getArgs(genericParams)
if err != nil {
@ -455,6 +636,25 @@ func updatePodContainers(params map[string]string, args []string, envs []api.Env
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) {
port := -1
hostPort := -1
@ -489,6 +689,40 @@ func updatePodPorts(params map[string]string, podSpec *api.PodSpec) (err error)
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{}
func (BasicPod) ParamNames() []GeneratorParam {
@ -609,6 +843,24 @@ func parseEnvs(envArray []string) ([]api.EnvVar, error) {
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 {
p := new(bool)
*p = val