Repository: helm/helm (go)

Issue:
ReadyCheck.jobReady can SIGSEGV if `job.spec.completions` is not set
`job.spec.completions` is allowed to be null:

>      Specifies the desired number of successfully finished pods the job should
>     be run with. Setting to nil means that the success of any pod signals the
>     success of all pods, and allows parallelism to have any positive value.
>     Setting to 1 means that parallelism is limited to 1 and the success of that
>     pod signals the success of the job. More info:
>     https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/


But `ReadyCheck.jobReady` assumes that the value is _not_ nil: 

https://github.com/helm/helm/blob/29da1b94e9e5e7ac800e5a43b0f4dafbe2e89d70/pkg/kube/ready.go#L230

If it _is_ null, then throws an error like:

```
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x1a6d5a5]
```


Pull request:
Fix SIGSEGV when job.Spec.Completions is nil
Signed-off-by: lsowen <logan@s1network.com>

<!--  Thanks for sending a pull request!  Here are some tips for you:
1. Make sure to read the Contributing Guide before submitting your PR: https://github.com/helm/helm/blob/main/CONTRIBUTING.md
2. If this PR closes another issue, add 'closes #<issue number>' somewhere in the PR summary. GitHub will automatically close that issue when this PR gets merged. Alternatively, adding 'refs #<issue number>' will not close the issue, but help provide the reviewer more context.-->

**What this PR does / why we need it**:

Fixes #10042 

**Special notes for your reviewer**:

**If applicable**:
- [ ] this PR contains documentation
- [x] this PR contains unit tests
- [x] this PR has been tested for backwards compatibility


Patch:
diff --git a/pkg/kube/ready.go b/pkg/kube/ready.go
index 19b93e386..106c0be51 100644
--- a/pkg/kube/ready.go
+++ b/pkg/kube/ready.go
@@ -227,7 +227,7 @@ func (c *ReadyChecker) jobReady(job *batchv1.Job) bool {
 		c.log("Job is failed: %s/%s", job.GetNamespace(), job.GetName())
 		return false
 	}
-	if job.Status.Succeeded < *job.Spec.Completions {
+	if job.Spec.Completions != nil && job.Status.Succeeded < *job.Spec.Completions {
 		c.log("Job is not completed: %s/%s", job.GetNamespace(), job.GetName())
 		return false
 	}


Test patch:
diff --git a/pkg/kube/ready_test.go b/pkg/kube/ready_test.go
index 8fd20bd9f..cece5352d 100644
--- a/pkg/kube/ready_test.go
+++ b/pkg/kube/ready_test.go
@@ -241,39 +241,44 @@ func Test_ReadyChecker_jobReady(t *testing.T) {
 	}{
 		{
 			name: "job is completed",
-			args: args{job: newJob("foo", 1, 1, 1, 0)},
+			args: args{job: newJob("foo", 1, intToInt32(1), 1, 0)},
 			want: true,
 		},
 		{
 			name: "job is incomplete",
-			args: args{job: newJob("foo", 1, 1, 0, 0)},
+			args: args{job: newJob("foo", 1, intToInt32(1), 0, 0)},
 			want: false,
 		},
 		{
 			name: "job is failed",
-			args: args{job: newJob("foo", 1, 1, 0, 1)},
+			args: args{job: newJob("foo", 1, intToInt32(1), 0, 1)},
 			want: false,
 		},
 		{
 			name: "job is completed with retry",
-			args: args{job: newJob("foo", 1, 1, 1, 1)},
+			args: args{job: newJob("foo", 1, intToInt32(1), 1, 1)},
 			want: true,
 		},
 		{
 			name: "job is failed with retry",
-			args: args{job: newJob("foo", 1, 1, 0, 2)},
+			args: args{job: newJob("foo", 1, intToInt32(1), 0, 2)},
 			want: false,
 		},
 		{
 			name: "job is completed single run",
-			args: args{job: newJob("foo", 0, 1, 1, 0)},
+			args: args{job: newJob("foo", 0, intToInt32(1), 1, 0)},
 			want: true,
 		},
 		{
 			name: "job is failed single run",
-			args: args{job: newJob("foo", 0, 1, 0, 1)},
+			args: args{job: newJob("foo", 0, intToInt32(1), 0, 1)},
 			want: false,
 		},
+		{
+			name: "job with null completions",
+			args: args{job: newJob("foo", 0, nil, 1, 0)},
+			want: true,
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -481,7 +486,7 @@ func newPersistentVolumeClaim(name string, phase corev1.PersistentVolumeClaimPha
 	}
 }
 
-func newJob(name string, backoffLimit, completions, succeeded, failed int) *batchv1.Job {
+func newJob(name string, backoffLimit int, completions *int32, succeeded int, failed int) *batchv1.Job {
 	return &batchv1.Job{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      name,
@@ -489,7 +494,7 @@ func newJob(name string, backoffLimit, completions, succeeded, failed int) *batc
 		},
 		Spec: batchv1.JobSpec{
 			BackoffLimit: intToInt32(backoffLimit),
-			Completions:  intToInt32(completions),
+			Completions:  completions,
 			Template: corev1.PodTemplateSpec{
 				ObjectMeta: metav1.ObjectMeta{
 					Name:   name,