package kubernetes import ( "fmt" executionv1alpha1 "git.ddupan.top/panxiao81/ayatori/api/execution/v1alpha1" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" ) const ( ControllerName = "execution.ayatori.ddupan.top/kubernetes" ReferenceType = "Job" JobUIDLabel = "execution.ayatori.ddupan.top/job-uid" ) var ayatoriJobGVK = schema.GroupVersionKind{ Group: executionv1alpha1.GroupVersion.Group, Version: executionv1alpha1.GroupVersion.Version, Kind: "Job", } // BuildJob translates the stable execution API into the Kubernetes adapter's // backend object. It intentionally does not accept or expose a PodSpec. func BuildJob( job *executionv1alpha1.Job, parameters *executionv1alpha1.KubernetesExecutionParameters, resources executionv1alpha1.ExecutionResourceRequirements, ) *batchv1.Job { backoffLimit := int32(0) controller := true blockOwnerDeletion := true //nolint:modernize // ObjectMeta is promoted through embedded TypeMeta; embedlit produces invalid Go here. return &batchv1.Job{ ObjectMeta: metav1.ObjectMeta{ Name: job.Name, Namespace: job.Namespace, Labels: map[string]string{ JobUIDLabel: string(job.UID), }, OwnerReferences: []metav1.OwnerReference{{ APIVersion: ayatoriJobGVK.GroupVersion().String(), Kind: ayatoriJobGVK.Kind, Name: job.Name, UID: job.UID, Controller: &controller, BlockOwnerDeletion: &blockOwnerDeletion, }}, }, Spec: batchv1.JobSpec{ BackoffLimit: &backoffLimit, Template: corev1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{JobUIDLabel: string(job.UID)}}, Spec: corev1.PodSpec{ RestartPolicy: corev1.RestartPolicyNever, ServiceAccountName: parameters.Spec.ServiceAccountName, RuntimeClassName: optionalString(parameters.Spec.RuntimeClassName), NodeSelector: parameters.Spec.Scheduling.NodeSelector, Tolerations: parameters.Spec.Scheduling.Tolerations, SecurityContext: parameters.Spec.PodSecurityContext, ImagePullSecrets: job.Spec.Task.ImagePullSecrets, Containers: []corev1.Container{{ Name: "task", Image: job.Spec.Task.Image, ImagePullPolicy: parameters.Spec.ImagePullPolicy, Command: job.Spec.Task.Command, Args: job.Spec.Task.Args, WorkingDir: job.Spec.Task.WorkingDir, Env: environment(job.Spec.Task.Env), Resources: resourceRequirements(resources), }}, }, }, }, } } func ValidateOwnership(owner *executionv1alpha1.Job, backend *batchv1.Job) error { if backend.Labels[JobUIDLabel] != string(owner.UID) { return fmt.Errorf("backend Job %s/%s is not owned by Ayatori Job UID %s", backend.Namespace, backend.Name, owner.UID) } return nil } func environment(values []executionv1alpha1.EnvVar) []corev1.EnvVar { result := make([]corev1.EnvVar, 0, len(values)) for _, value := range values { env := corev1.EnvVar{Name: value.Name} if value.Value != nil { env.Value = *value.Value } if value.ValueFrom != nil { env.ValueFrom = &corev1.EnvVarSource{ SecretKeyRef: value.ValueFrom.SecretKeyRef, ConfigMapKeyRef: value.ValueFrom.ConfigMapKeyRef, } } result = append(result, env) } return result } func resourceRequirements(resources executionv1alpha1.ExecutionResourceRequirements) corev1.ResourceRequirements { return corev1.ResourceRequirements{ Requests: resourceList(resources.Requests), Limits: resourceList(resources.Limits), } } func resourceList(values executionv1alpha1.ResourceValues) corev1.ResourceList { result := corev1.ResourceList{} if values.CPU != nil { result[corev1.ResourceCPU] = values.CPU.DeepCopy() } if values.Memory != nil { result[corev1.ResourceMemory] = values.Memory.DeepCopy() } if len(result) == 0 { return nil } return result } func optionalString(value string) *string { if value == "" { return nil } return &value }