diff --git a/internal/resource/job.go b/internal/resource/job.go index ddeaa3bb..5354a66c 100644 --- a/internal/resource/job.go +++ b/internal/resource/job.go @@ -153,6 +153,7 @@ func (r *Job) Fields(ns string) Row { func (*Job) toContainers(p v1.PodSpec) (string, string) { cc := make([]string, 0, len(p.InitContainers)+len(p.Containers)) ii := make([]string, 0, len(cc)) + for _, c := range p.InitContainers { cc = append(cc, c.Name) ii = append(ii, c.Image) @@ -162,12 +163,13 @@ func (*Job) toContainers(p v1.PodSpec) (string, string) { ii = append(ii, c.Image) } + const maxShow = 2 // Limit to 2 of each... - if len(cc) > 2 { - cc = append(cc[:2], "...") + if len(cc) > maxShow { + cc = append(cc[:2], fmt.Sprintf("(+%d)...", len(cc)-maxShow)) } - if len(ii) > 2 { - ii = append(ii[:2], "...") + if len(ii) > maxShow { + ii = append(ii[:2], fmt.Sprintf("(+%d)...", len(ii)-maxShow)) } return strings.Join(cc, ","), strings.Join(ii, ",") @@ -177,23 +179,31 @@ func (*Job) toCompletion(spec batchv1.JobSpec, status batchv1.JobStatus) (s stri if spec.Completions != nil { return fmt.Sprintf("%d/%d", status.Succeeded, *spec.Completions) } - var parallelism int32 - if spec.Parallelism != nil { - parallelism = *spec.Parallelism + + if spec.Parallelism == nil { + return fmt.Sprintf("%d/1", status.Succeeded) } - if parallelism > 1 { - return fmt.Sprintf("%d/1 of %d", status.Succeeded, parallelism) + + p := *spec.Parallelism + if p > 1 { + return fmt.Sprintf("%d/1 of %d", status.Succeeded, p) } return fmt.Sprintf("%d/1", status.Succeeded) } func (*Job) toDuration(status batchv1.JobStatus) string { - switch { - case status.StartTime == nil: - case status.CompletionTime == nil: - return duration.HumanDuration(time.Since(status.StartTime.Time)) + if status.StartTime == nil { + return MissingValue } - return duration.HumanDuration(status.CompletionTime.Sub(status.StartTime.Time)) + var d time.Duration + switch { + case status.CompletionTime == nil: + d = time.Since(status.StartTime.Time) + default: + d = status.CompletionTime.Sub(status.StartTime.Time) + } + + return duration.HumanDuration(d) } diff --git a/internal/resource/job_int_test.go b/internal/resource/job_int_test.go new file mode 100644 index 00000000..62257df3 --- /dev/null +++ b/internal/resource/job_int_test.go @@ -0,0 +1,153 @@ +package resource + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + batchv1 "k8s.io/api/batch/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestJobToCompletion(t *testing.T) { + t0 := testTime() + t1, t2 := metav1.Time{t0}, metav1.Time{t0.Add(10 * time.Second)} + var c, p int32 = 10, 20 + + uu := []struct { + j batchv1.JobSpec + s batchv1.JobStatus + e string + }{ + { + batchv1.JobSpec{ + Completions: &c, + Parallelism: &p, + }, + batchv1.JobStatus{ + Succeeded: 1, + Active: 1, + Failed: 0, + StartTime: &t1, + CompletionTime: &t2, + }, + "1/10", + }, + { + batchv1.JobSpec{ + Parallelism: &p, + }, + batchv1.JobStatus{ + Succeeded: 1, + Active: 1, + Failed: 0, + StartTime: &t1, + CompletionTime: &t2, + }, + "1/1 of 20", + }, + { + batchv1.JobSpec{ + Completions: &c, + }, + batchv1.JobStatus{ + Succeeded: 1, + Active: 1, + Failed: 0, + StartTime: &t1, + CompletionTime: &t2, + }, + "1/10", + }, + { + batchv1.JobSpec{}, + batchv1.JobStatus{ + Succeeded: 1, + Active: 1, + Failed: 0, + StartTime: &t1, + CompletionTime: &t2, + }, + "1/1", + }, + } + + var j *Job + for _, u := range uu { + assert.Equal(t, u.e, j.toCompletion(u.j, u.s)) + } +} + +func TestJobToDuration(t *testing.T) { + t0 := testTime() + t1, t2 := metav1.Time{t0}, metav1.Time{t0.Add(10 * time.Second)} + + uu := []struct { + s batchv1.JobStatus + e string + }{ + { + batchv1.JobStatus{ + StartTime: &t1, + CompletionTime: &t2, + }, + "10s", + }, + { + batchv1.JobStatus{ + StartTime: &t1, + }, + "101d", + }, + { + batchv1.JobStatus{ + CompletionTime: &t2, + }, + MissingValue, + }, + } + + var j *Job + for _, u := range uu { + assert.Equal(t, u.e, j.toDuration(u.s)) + } +} + +func TestJobToContainers(t *testing.T) { + uu := []struct { + s v1.PodSpec + c, i string + }{ + { + v1.PodSpec{ + InitContainers: []v1.Container{ + {Name: "i1", Image: "fred"}, + }, + Containers: []v1.Container{ + {Name: "c1", Image: "blee"}, + }, + }, + "i1,c1", "fred,blee", + }, + { + v1.PodSpec{ + InitContainers: []v1.Container{ + {Name: "i1", Image: "fred"}, + }, + Containers: []v1.Container{ + {Name: "c1", Image: "blee"}, + {Name: "c2", Image: "duh"}, + }, + }, + "i1,c1,(+1)...", "fred,blee,(+1)...", + }, + } + + var j *Job + for _, u := range uu { + c, i := j.toContainers(u.s) + assert.Equal(t, u.c, c) + assert.Equal(t, u.i, i) + } +}