Skip to content

Commit

Permalink
Merge pull request #23722 from umohnani8/jobs
Browse files Browse the repository at this point in the history
Add support for Job to kube generate & play
  • Loading branch information
openshift-merge-bot[bot] authored Sep 6, 2024
2 parents b645f70 + bdf96e7 commit dde0310
Show file tree
Hide file tree
Showing 13 changed files with 1,101 additions and 17 deletions.
2 changes: 1 addition & 1 deletion cmd/podman/kube/play.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ var (
playOptions = playKubeOptionsWrapper{}
playDescription = `Reads in a structured file of Kubernetes YAML.
Creates pods or volumes based on the Kubernetes kind described in the YAML. Supported kinds are Pods, Deployments, DaemonSets and PersistentVolumeClaims.`
Creates pods or volumes based on the Kubernetes kind described in the YAML. Supported kinds are Pods, Deployments, DaemonSets, Jobs, and PersistentVolumeClaims.`

playCmd = &cobra.Command{
Use: "play [options] KUBEFILE|-",
Expand Down
34 changes: 25 additions & 9 deletions docs/kubernetes_support.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,12 +176,28 @@ Note: **N/A** means that the option cannot be supported in a single-node Podman

## DaemonSet Fields

| Field | Support |
|-----------------------------------------|-------------------------------------------------------|
| selector ||
| template ||
| minReadySeconds | no |
| strategy\.type | no |
| strategy\.rollingUpdate\.maxSurge | no |
| strategy\.rollingUpdate\.maxUnavailable | no |
| revisionHistoryLimit | no |
| Field | Support |
|-----------------------------------------|---------|
| selector ||
| template ||
| minReadySeconds | no |
| strategy\.type | no |
| strategy\.rollingUpdate\.maxSurge | no |
| strategy\.rollingUpdate\.maxUnavailable | no |
| revisionHistoryLimit | no |

## Job Fields

| Field | Support |
|-------------------------|----------------------------------|
| activeDeadlineSeconds | no |
| selector | no (automatically set by k8s) |
| template ||
| backoffLimit | no |
| completionMode | no |
| completions | no (set to 1 with kube generate) |
| manualSelector | no |
| parallelism | no (set to 1 with kube generate) |
| podFailurePolicy | no |
| suspend | no |
| ttlSecondsAfterFinished | no |
8 changes: 5 additions & 3 deletions docs/source/markdown/podman-kube-generate.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ Note that the generated Kubernetes YAML file can be used to re-run the deploymen

Note that if the pod being generated was created with the **--infra-name** flag set, then the generated kube yaml will have the **io.podman.annotations.infra.name** set where the value is the name of the infra container set by the user.

Also note that both Deployment and DaemonSet can only have `restartPolicy` set to `Always`.
Note that both Deployment and DaemonSet can only have `restartPolicy` set to `Always`.

Note that Job can only have `restartPolicy` set to `OnFailure` or `Never`. By default, podman sets it to `Never` when generating a kube yaml using `kube generate`.

## OPTIONS

Expand All @@ -52,9 +54,9 @@ Note: this can only be set with the option `--type=deployment`.

Generate a Kubernetes service object in addition to the Pods. Used to generate a Service specification for the corresponding Pod output. In particular, if the object has portmap bindings, the service specification includes a NodePort declaration to expose the service. A random port is assigned by Podman in the specification.

#### **--type**, **-t**=*pod* | *deployment* | *daemonset*
#### **--type**, **-t**=*pod* | *deployment* | *daemonset* | *job*

The Kubernetes kind to generate in the YAML file. Currently, the only supported Kubernetes specifications are `Pod`, `Deployment` and `DaemonSet`. By default, the `Pod` specification is generated.
The Kubernetes kind to generate in the YAML file. Currently, the only supported Kubernetes specifications are `Pod`, `Deployment`, `Job`, and `DaemonSet`. By default, the `Pod` specification is generated.

## EXAMPLES

Expand Down
1 change: 1 addition & 0 deletions docs/source/markdown/podman-kube-play.1.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Currently, the supported Kubernetes kinds are:
- ConfigMap
- Secret
- DaemonSet
- Job

`Kubernetes Pods or Deployments`

Expand Down
2 changes: 2 additions & 0 deletions libpod/define/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,6 @@ const (
K8sKindDeployment = "deployment"
// A DaemonSet kube yaml spec
K8sKindDaemonSet = "daemonset"
// a Job kube yaml spec
K8sKindJob = "job"
)
70 changes: 70 additions & 0 deletions libpod/kube.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,61 @@ func GenerateForKubeDeployment(ctx context.Context, pod *YAMLPod, options entiti
return &dep, nil
}

// GenerateForKubeJob returns a YAMLDeployment from a YAMLPod that is then used to create a kubernetes Job
// kind YAML.
func GenerateForKubeJob(ctx context.Context, pod *YAMLPod, options entities.GenerateKubeOptions) (*YAMLJob, error) {
// Restart policy for Job cannot be set to Always
if options.Type == define.K8sKindJob && pod.Spec.RestartPolicy == v1.RestartPolicyAlways {
return nil, fmt.Errorf("k8s Jobs can not have restartPolicy set to Always; only Never and OnFailure policies allowed")
}

// Create label map that will be added to podSpec and Job metadata
// The matching label lets the job know which pods to manage
appKey := "app"
matchLabels := map[string]string{appKey: pod.Name}
// Add the key:value (app:pod-name) to the podSpec labels
if pod.Labels == nil {
pod.Labels = matchLabels
} else {
pod.Labels[appKey] = pod.Name
}

jobSpec := YAMLJobSpec{
Template: &YAMLPodTemplateSpec{
PodTemplateSpec: v1.PodTemplateSpec{
ObjectMeta: pod.ObjectMeta,
},
Spec: pod.Spec,
},
}

// Set the completions and parallelism to 1 by default for the Job
completions, parallelism := int32(1), int32(1)
jobSpec.Completions = &completions
jobSpec.Parallelism = &parallelism
// Set the restart policy to never as k8s requires a job to have a restart policy
// of onFailure or never set in the kube yaml
jobSpec.Template.Spec.RestartPolicy = v1.RestartPolicyNever

// Create the Deployment object
job := YAMLJob{
Job: v1.Job{
ObjectMeta: v12.ObjectMeta{
Name: pod.Name + "-job",
CreationTimestamp: pod.CreationTimestamp,
Labels: pod.Labels,
},
TypeMeta: v12.TypeMeta{
Kind: "Job",
APIVersion: "batch/v1",
},
},
Spec: &jobSpec,
}

return &job, nil
}

// GenerateForKube generates a v1.PersistentVolumeClaim from a libpod volume.
func (v *Volume) GenerateForKube() *v1.PersistentVolumeClaim {
annotations := make(map[string]string)
Expand Down Expand Up @@ -328,6 +383,15 @@ type YAMLDaemonSetSpec struct {
Strategy *v1.DaemonSetUpdateStrategy `json:"strategy,omitempty"`
}

// YAMLJobSpec represents the same k8s API core JobSpec with a small
// change and that is having Template as a pointer to YAMLPodTemplateSpec
// because Go doesn't omit empty struct and we want to omit Strategy and any fields in the Pod YAML
// if it's empty.
type YAMLJobSpec struct {
v1.JobSpec
Template *YAMLPodTemplateSpec `json:"template,omitempty"`
}

// YAMLDaemonSet represents the same k8s API core DaemonSet with a small change
// and that is having Spec as a pointer to YAMLDaemonSetSpec and Status as a pointer to
// k8s API core DaemonSetStatus.
Expand All @@ -350,6 +414,12 @@ type YAMLDeployment struct {
Status *v1.DeploymentStatus `json:"status,omitempty"`
}

type YAMLJob struct {
v1.Job
Spec *YAMLJobSpec `json:"spec,omitempty"`
Status *v1.JobStatus `json:"status,omitempty"`
}

// YAMLService represents the same k8s API core Service struct with a small
// change and that is having Status as a pointer to k8s API core ServiceStatus.
// Because Go doesn't omit empty struct and we want to omit Status in YAML
Expand Down
24 changes: 22 additions & 2 deletions pkg/domain/infra/abi/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,14 +244,24 @@ func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string,
return nil, err
}
typeContent = append(typeContent, b)
case define.K8sKindJob:
job, err := libpod.GenerateForKubeJob(ctx, libpod.ConvertV1PodToYAMLPod(po), options)
if err != nil {
return nil, err
}
b, err := generateKubeYAML(job)
if err != nil {
return nil, err
}
typeContent = append(typeContent, b)
case define.K8sKindPod:
b, err := generateKubeYAML(libpod.ConvertV1PodToYAMLPod(po))
if err != nil {
return nil, err
}
typeContent = append(typeContent, b)
default:
return nil, fmt.Errorf("invalid generation type - only pods, deployments and daemonsets are currently supported: %+v", options.Type)
return nil, fmt.Errorf("invalid generation type - only pods, deployments, jobs, and daemonsets are currently supported: %+v", options.Type)
}

if options.Service {
Expand Down Expand Up @@ -311,14 +321,24 @@ func getKubePods(ctx context.Context, pods []*libpod.Pod, options entities.Gener
return nil, nil, err
}
out = append(out, b)
case define.K8sKindJob:
job, err := libpod.GenerateForKubeJob(ctx, libpod.ConvertV1PodToYAMLPod(po), options)
if err != nil {
return nil, nil, err
}
b, err := generateKubeYAML(job)
if err != nil {
return nil, nil, err
}
out = append(out, b)
case define.K8sKindPod:
b, err := generateKubeYAML(libpod.ConvertV1PodToYAMLPod(po))
if err != nil {
return nil, nil, err
}
out = append(out, b)
default:
return nil, nil, fmt.Errorf("invalid generation type - only pods, deployments and daemonsets are currently supported")
return nil, nil, fmt.Errorf("invalid generation type - only pods, deployments, jobs, and daemonsets are currently supported")
}

if options.Service {
Expand Down
50 changes: 49 additions & 1 deletion pkg/domain/infra/abi/play.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,22 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options
}
notifyProxies = append(notifyProxies, proxies...)

report.Pods = append(report.Pods, r.Pods...)
validKinds++
ranContainers = true
case "Job":
var jobYAML v1.Job

if err := yaml.Unmarshal(document, &jobYAML); err != nil {
return nil, fmt.Errorf("unable to read YAML as Kube Job: %w", err)
}

r, proxies, err := ic.playKubeJob(ctx, &jobYAML, options, &ipIndex, configMaps, serviceContainer)
if err != nil {
return nil, err
}
notifyProxies = append(notifyProxies, proxies...)

report.Pods = append(report.Pods, r.Pods...)
validKinds++
ranContainers = true
Expand Down Expand Up @@ -549,6 +565,29 @@ func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAM
return &report, proxies, nil
}

func (ic *ContainerEngine) playKubeJob(ctx context.Context, jobYAML *v1.Job, options entities.PlayKubeOptions, ipIndex *int, configMaps []v1.ConfigMap, serviceContainer *libpod.Container) (*entities.PlayKubeReport, []*notifyproxy.NotifyProxy, error) {
var (
jobName string
podSpec v1.PodTemplateSpec
report entities.PlayKubeReport
)

jobName = jobYAML.ObjectMeta.Name
if jobName == "" {
return nil, nil, errors.New("job does not have a name")
}
podSpec = jobYAML.Spec.Template

podName := fmt.Sprintf("%s-pod", jobName)
podReport, proxies, err := ic.playKubePod(ctx, podName, &podSpec, options, ipIndex, jobYAML.Annotations, configMaps, serviceContainer)
if err != nil {
return nil, nil, fmt.Errorf("encountered while bringing up pod %s: %w", podName, err)
}
report.Pods = podReport.Pods

return &report, proxies, nil
}

func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podYAML *v1.PodTemplateSpec, options entities.PlayKubeOptions, ipIndex *int, annotations map[string]string, configMaps []v1.ConfigMap, serviceContainer *libpod.Container) (*entities.PlayKubeReport, []*notifyproxy.NotifyProxy, error) {
var (
writer io.Writer
Expand Down Expand Up @@ -1502,7 +1541,7 @@ func sortKubeKinds(documentList [][]byte) ([][]byte, error) {
}

switch kind {
case "Pod", "Deployment", "DaemonSet":
case "Pod", "Deployment", "DaemonSet", "Job":
sortedDocumentList = append(sortedDocumentList, document)
default:
sortedDocumentList = append([][]byte{document}, sortedDocumentList...)
Expand Down Expand Up @@ -1633,6 +1672,15 @@ func (ic *ContainerEngine) PlayKubeDown(ctx context.Context, body io.Reader, opt
}
podName := fmt.Sprintf("%s-pod", deploymentName)
podNames = append(podNames, podName)
case "Job":
var jobYAML v1.Job

if err := yaml.Unmarshal(document, &jobYAML); err != nil {
return nil, fmt.Errorf("unable to read YAML as Kube Job: %w", err)
}
jobName := jobYAML.ObjectMeta.Name
podName := fmt.Sprintf("%s-pod", jobName)
podNames = append(podNames, podName)
case "PersistentVolumeClaim":
var pvcYAML v1.PersistentVolumeClaim
if err := yaml.Unmarshal(document, &pvcYAML); err != nil {
Expand Down
Loading

0 comments on commit dde0310

Please sign in to comment.