From 2aa20ed44df19dbbe57da538e2d401e1137616e6 Mon Sep 17 00:00:00 2001 From: chenk Date: Mon, 18 Mar 2024 13:57:45 +0200 Subject: [PATCH] fix: generate scan reports for individual completed containers when pod scan failed (#1917) Signed-off-by: chenk --- pkg/kube/resources.go | 11 ++++- pkg/kube/resources_test.go | 2 +- pkg/vulnerabilityreport/controller/scanjob.go | 44 ++++++++++--------- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/pkg/kube/resources.go b/pkg/kube/resources.go index 2356e3ca9..6b97e0178 100644 --- a/pkg/kube/resources.go +++ b/pkg/kube/resources.go @@ -52,7 +52,7 @@ func GetContainerImagesFromContainersList(containers []corev1.Container) Contain // to container images from the specified v1.Job. // The mapping is encoded as JSON value of the AnnotationContainerImages // annotation. -func GetContainerImagesFromJob(job *batchv1.Job) (ContainerImages, error) { +func GetContainerImagesFromJob(job *batchv1.Job, completedContainers ...string) (ContainerImages, error) { var containerImagesAsJSON string var ok bool @@ -64,7 +64,14 @@ func GetContainerImagesFromJob(job *batchv1.Job) (ContainerImages, error) { if err != nil { return nil, fmt.Errorf("parsing annotation: %s: %w", trivyoperator.AnnotationContainerImages, err) } - return containerImages, nil + completed := make(map[string]string) + for _, container := range completedContainers { + if c, ok := containerImages[container]; ok { + completed[container] = c + } + + } + return completed, nil } // ComputeHash returns a hash value calculated from a given object. diff --git a/pkg/kube/resources_test.go b/pkg/kube/resources_test.go index 073e464fb..6870c1230 100644 --- a/pkg/kube/resources_test.go +++ b/pkg/kube/resources_test.go @@ -111,7 +111,7 @@ func TestGetContainerImagesFromJob(t *testing.T) { "trivy-operator.container-images": `{"nginx":"nginx:1.16","sidecar":"sidecar:1.32.7"}`, }, }, - }) + }, []string{"nginx", "sidecar"}...) require.NoError(t, err) assert.Equal(t, kube.ContainerImages{ "nginx": "nginx:1.16", diff --git a/pkg/vulnerabilityreport/controller/scanjob.go b/pkg/vulnerabilityreport/controller/scanjob.go index 5e5768d90..d7f9ecca3 100644 --- a/pkg/vulnerabilityreport/controller/scanjob.go +++ b/pkg/vulnerabilityreport/controller/scanjob.go @@ -75,20 +75,23 @@ func (r *ScanJobController) reconcileJobs() reconcile.Func { } switch jobCondition := job.Status.Conditions[0].Type; jobCondition { - case batchv1.JobComplete: - err = r.processCompleteScanJob(ctx, job) - case batchv1.JobFailed: - err = r.processFailedScanJob(ctx, job) + case batchv1.JobComplete, batchv1.JobFailed: + completedContainers, err := r.completedContainers(ctx, job) + if err != nil { + return ctrl.Result{}, r.deleteJob(ctx, job) + } + if len(completedContainers) == 0 { + return ctrl.Result{}, r.deleteJob(ctx, job) + } + return ctrl.Result{}, r.processCompleteScanJob(ctx, job, completedContainers...) + default: - err = fmt.Errorf("unrecognized scan job condition: %v", jobCondition) + return ctrl.Result{}, fmt.Errorf("unrecognized scan job condition: %v", jobCondition) } - - return ctrl.Result{}, err } - } -func (r *ScanJobController) processCompleteScanJob(ctx context.Context, job *batchv1.Job) error { +func (r *ScanJobController) processCompleteScanJob(ctx context.Context, job *batchv1.Job, completedContainers ...string) error { log := r.Logger.WithValues("job", fmt.Sprintf("%s/%s", job.Namespace, job.Name)) ownerRef, err := kube.ObjectRefFromObjectMeta(job.ObjectMeta) @@ -104,12 +107,6 @@ func (r *ScanJobController) processCompleteScanJob(ctx context.Context, job *bat } return fmt.Errorf("getting object from object ref: %w", err) } - - containerImages, err := kube.GetContainerImagesFromJob(job) - if err != nil { - return fmt.Errorf("getting container images: %w", err) - } - podSpecHash, ok := job.Labels[trivyoperator.LabelResourceSpecHash] if !ok { return fmt.Errorf("expected label %s not set", trivyoperator.LabelResourceSpecHash) @@ -121,6 +118,10 @@ func (r *ScanJobController) processCompleteScanJob(ctx context.Context, job *bat log.V(1).Info("Job complete") hasVulnReports := true + containerImages, err := kube.GetContainerImagesFromJob(job, completedContainers...) + if err != nil { + return fmt.Errorf("getting container images: %w", err) + } if r.Config.VulnerabilityScannerEnabled { hasVulnReports, err = hasVulnerabilityReports(ctx, r.VulnerabilityReadWriter, ownerRef, podSpecHash, containerImages) if err != nil { @@ -324,29 +325,30 @@ func (r *ScanJobController) processScanJobResults(ctx context.Context, return vulnerabilityReports, secretReports, sbomReports, nil } -func (r *ScanJobController) processFailedScanJob(ctx context.Context, scanJob *batchv1.Job) error { +func (r *ScanJobController) completedContainers(ctx context.Context, scanJob *batchv1.Job) ([]string, error) { log := r.Logger.WithValues("job", fmt.Sprintf("%s/%s", scanJob.Namespace, scanJob.Name)) statuses, err := r.GetTerminatedContainersStatusesByJob(ctx, scanJob) if err != nil { if k8sapierror.IsNotFound(err) { log.V(1).Info("Cached job must have been deleted") - return nil + return []string{}, nil } if kube.IsPodControlledByJobNotFound(err) { log.V(1).Info("Pod must have been deleted") - return r.deleteJob(ctx, scanJob) + return []string{}, nil } - return err + return nil, err } + completedContainers := make([]string, 0) for container, status := range statuses { if status.ExitCode == 0 { + completedContainers = append(completedContainers, container) continue } log.Error(nil, "Scan job container", "container", container, "status.reason", status.Reason, "status.message", status.Message) } - log.V(1).Info("Deleting failed scan job") - return r.deleteJob(ctx, scanJob) + return completedContainers, nil } func (r *ScanJobController) deleteJob(ctx context.Context, job *batchv1.Job) error {