From 6e7e0f116b5edece671abb4250d8b761c4541f93 Mon Sep 17 00:00:00 2001 From: Erik Godding Boye Date: Tue, 17 Jan 2023 14:32:54 +0100 Subject: [PATCH] refactor: establish struct for image scan spec (#65) --- api/v1alpha1/containerimagescan_types.go | 13 +++-- api/v1alpha1/zz_generated.deepcopy.go | 20 +++++++- .../containerimagescan_controller_test.go | 8 +-- .../testdata/scan-job/expected-scan-job.yaml | 2 +- controllers/types.go | 9 +++- controllers/types_test.go | 50 +++++++++++-------- controllers/workload_controller.go | 11 ++-- controllers/workload_controller_test.go | 2 +- internal/metrics/collector_test.go | 28 +++++++---- 9 files changed, 94 insertions(+), 49 deletions(-) diff --git a/api/v1alpha1/containerimagescan_types.go b/api/v1alpha1/containerimagescan_types.go index abefe241..4a0e9b98 100644 --- a/api/v1alpha1/containerimagescan_types.go +++ b/api/v1alpha1/containerimagescan_types.go @@ -16,7 +16,6 @@ const ( type Image struct { Name string `json:"name"` Digest digest.Digest `json:"digest"` - Tag string `json:"tag,omitempty"` } type Workload struct { @@ -74,11 +73,17 @@ func (cis ContainerImageScan) HasVulnerabilityOverflow() bool { return stalledCondition.Reason == ReasonVulnerabilityOverflow } -// ContainerImageScanSpec contains a resolved container image in use by owning workload. -type ContainerImageScanSpec struct { +// ImageScanSpec represents the specification for the container image scan. +type ImageScanSpec struct { Image `json:",inline"` ScanConfig `json:",inline"` - Workload Workload `json:"workload"` +} + +// ContainerImageScanSpec contains a resolved container image in use by owning workload. +type ContainerImageScanSpec struct { + ImageScanSpec `json:",inline"` + Tag string `json:"tag,omitempty"` + Workload Workload `json:"workload"` } // ContainerImageScanStatus defines the observed state of ContainerImageScan. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 4d4fbbbd..c7470ca1 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -91,8 +91,7 @@ func (in *ContainerImageScanList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ContainerImageScanSpec) DeepCopyInto(out *ContainerImageScanSpec) { *out = *in - out.Image = in.Image - in.ScanConfig.DeepCopyInto(&out.ScanConfig) + in.ImageScanSpec.DeepCopyInto(&out.ImageScanSpec) out.Workload = in.Workload } @@ -161,6 +160,23 @@ func (in *Image) DeepCopy() *Image { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImageScanSpec) DeepCopyInto(out *ImageScanSpec) { + *out = *in + out.Image = in.Image + in.ScanConfig.DeepCopyInto(&out.ScanConfig) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageScanSpec. +func (in *ImageScanSpec) DeepCopy() *ImageScanSpec { + if in == nil { + return nil + } + out := new(ImageScanSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ScanConfig) DeepCopyInto(out *ScanConfig) { *out = *in diff --git a/controllers/containerimagescan_controller_test.go b/controllers/containerimagescan_controller_test.go index a6ca3d9a..054a1a1d 100644 --- a/controllers/containerimagescan_controller_test.go +++ b/controllers/containerimagescan_controller_test.go @@ -39,9 +39,11 @@ var _ = Describe("ContainerImageScan controller", func() { Namespace: "default", }, Spec: stasv1alpha1.ContainerImageScanSpec{ - Image: stasv1alpha1.Image{ - Name: "docker.io/nginxinc/nginx-unprivileged", - Digest: "sha256:a96370b18b3d7e70b7b34d49dcb621a805c15cf71217ee8c77be5a98cc793fd3", + ImageScanSpec: stasv1alpha1.ImageScanSpec{ + Image: stasv1alpha1.Image{ + Name: "docker.io/nginxinc/nginx-unprivileged", + Digest: "sha256:a96370b18b3d7e70b7b34d49dcb621a805c15cf71217ee8c77be5a98cc793fd3", + }, }, }, } diff --git a/controllers/testdata/scan-job/expected-scan-job.yaml b/controllers/testdata/scan-job/expected-scan-job.yaml index 49ba6331..b15816a7 100644 --- a/controllers/testdata/scan-job/expected-scan-job.yaml +++ b/controllers/testdata/scan-job/expected-scan-job.yaml @@ -14,7 +14,7 @@ metadata: workload.statnett.no/name: echo workload.statnett.no/namespace: replica-set namespace: image-scanner-jobs - name: echo-6bdfc76c56-8ae43-2693c + name: echo-6bdfc76c56-8ae43-b63f8 spec: activeDeadlineSeconds: 3600 backoffLimit: 3 diff --git a/controllers/types.go b/controllers/types.go index a6669c8b..0d0c5d19 100644 --- a/controllers/types.go +++ b/controllers/types.go @@ -7,8 +7,13 @@ import ( stasv1alpha1 "github.com/statnett/image-scanner-operator/api/v1alpha1" ) -func NewImageFromContainerStatus(containerStatus corev1.ContainerStatus) (stasv1alpha1.Image, error) { - image := stasv1alpha1.Image{} +type podContainerImage struct { + stasv1alpha1.Image + Tag string +} + +func newImageFromContainerStatus(containerStatus corev1.ContainerStatus) (podContainerImage, error) { + image := podContainerImage{} idRef, err := reference.ParseAnyReference(containerStatus.ImageID) if err != nil { diff --git a/controllers/types_test.go b/controllers/types_test.go index b91ece4b..c5c4ac61 100644 --- a/controllers/types_test.go +++ b/controllers/types_test.go @@ -10,8 +10,8 @@ import ( var _ = Describe("ImageReference", func() { DescribeTable("Creating from container status", - func(containerStatus corev1.ContainerStatus, expectedImage stasv1alpha1.Image) { - actualImage, err := NewImageFromContainerStatus(containerStatus) + func(containerStatus corev1.ContainerStatus, expectedImage podContainerImage) { + actualImage, err := newImageFromContainerStatus(containerStatus) Expect(err).To(Succeed()) Expect(actualImage).To(Equal(expectedImage)) }, @@ -20,48 +20,58 @@ var _ = Describe("ImageReference", func() { Image: "my.registry/repository/app:f54a333e", ImageID: "my.registry/repository/app@sha256:4b59f7dacd37c688968756d176139715df69d89eb0be1802e059316f9d58d9ef", }, - stasv1alpha1.Image{ - Name: "my.registry/repository/app", - Digest: "sha256:4b59f7dacd37c688968756d176139715df69d89eb0be1802e059316f9d58d9ef", - Tag: "f54a333e", + podContainerImage{ + Image: stasv1alpha1.Image{ + Name: "my.registry/repository/app", + Digest: "sha256:4b59f7dacd37c688968756d176139715df69d89eb0be1802e059316f9d58d9ef", + }, + Tag: "f54a333e", }), Entry("Standard digested image", corev1.ContainerStatus{ Image: "stas/echo-server@sha256:793485b42b5c6d97ab10f8cea08467b77711b865e4512aae6a7e70a38145469e", ImageID: "docker.io/stas/echo-server@sha256:793485b42b5c6d97ab10f8cea08467b77711b865e4512aae6a7e70a38145469e", }, - stasv1alpha1.Image{ - Name: "docker.io/stas/echo-server", - Digest: "sha256:793485b42b5c6d97ab10f8cea08467b77711b865e4512aae6a7e70a38145469e", + podContainerImage{ + Image: stasv1alpha1.Image{ + Name: "docker.io/stas/echo-server", + Digest: "sha256:793485b42b5c6d97ab10f8cea08467b77711b865e4512aae6a7e70a38145469e", + }, }), Entry("Standard digested image in k3s", corev1.ContainerStatus{ Image: "sha256:793485b42b5c6d97ab10f8cea08467b77711b865e4512aae6a7e70a38145469e", ImageID: "docker.io/stas/echo-server@sha256:793485b42b5c6d97ab10f8cea08467b77711b865e4512aae6a7e70a38145469e", }, - stasv1alpha1.Image{ - Name: "docker.io/stas/echo-server", - Digest: "sha256:793485b42b5c6d97ab10f8cea08467b77711b865e4512aae6a7e70a38145469e", + podContainerImage{ + Image: stasv1alpha1.Image{ + Name: "docker.io/stas/echo-server", + Digest: "sha256:793485b42b5c6d97ab10f8cea08467b77711b865e4512aae6a7e70a38145469e", + }, }), Entry("Image imported into k3s", corev1.ContainerStatus{ Image: "docker.io/application-operator/controller:latest", ImageID: "sha256:f991b3a7a93c5c0070dde555a1542d5a34508f16e52eced9237f0967e28ddaff", }, - stasv1alpha1.Image{ - Name: "docker.io/application-operator/controller", - Digest: "sha256:f991b3a7a93c5c0070dde555a1542d5a34508f16e52eced9237f0967e28ddaff", - Tag: "latest", + podContainerImage{ + Image: stasv1alpha1.Image{ + Name: "docker.io/application-operator/controller", + Digest: "sha256:f991b3a7a93c5c0070dde555a1542d5a34508f16e52eced9237f0967e28ddaff", + }, + Tag: "latest", }), Entry("Untagged Docker Hub image on corporate OCP", corev1.ContainerStatus{ Image: "dummy.registry.mycorp.com/mysql:latest", ImageID: "dummy.registry.mycorp.com/mysql@sha256:83469837189400492f32d23cadbfc97fae3dc019871337a841609f0b71a34907", }, - stasv1alpha1.Image{ - Name: "dummy.registry.mycorp.com/mysql", - Digest: "sha256:83469837189400492f32d23cadbfc97fae3dc019871337a841609f0b71a34907", - Tag: "latest", + podContainerImage{ + Image: stasv1alpha1.Image{ + Name: "dummy.registry.mycorp.com/mysql", + Digest: "sha256:83469837189400492f32d23cadbfc97fae3dc019871337a841609f0b71a34907", + }, + Tag: "latest", }), ) }) diff --git a/controllers/workload_controller.go b/controllers/workload_controller.go index c50ce82b..a55535db 100644 --- a/controllers/workload_controller.go +++ b/controllers/workload_controller.go @@ -127,14 +127,15 @@ func (r *PodReconciler) reconcile(ctx context.Context, pod *corev1.Pod) error { for containerName, image := range images { cis := &stasv1alpha1.ContainerImageScan{} cis.Namespace = pod.Namespace - cis.Name = imageScanName(podController, containerName, image) + cis.Name = imageScanName(podController, containerName, image.Image) mutateFn := func() error { cis.Labels = pod.GetLabels() cis.Spec.Workload.Group = podController.GetObjectKind().GroupVersionKind().Group cis.Spec.Workload.Kind = podController.GetObjectKind().GroupVersionKind().Kind cis.Spec.Workload.Name = podController.GetName() cis.Spec.Workload.ContainerName = containerName - cis.Spec.Image = image + cis.Spec.Image = image.Image + cis.Spec.Tag = image.Tag if v := podController.GetAnnotations()[stasv1alpha1.WorkloadAnnotationKeyIgnoreUnfixed]; v == "true" { cis.Spec.IgnoreUnfixed = pointer.Bool(true) @@ -215,12 +216,12 @@ func (r *PodReconciler) getImageScansOwnedByPodContainer(ctx context.Context, po return CISes, nil } -func containerImages(pod *corev1.Pod) (map[string]stasv1alpha1.Image, error) { - images := make(map[string]stasv1alpha1.Image) +func containerImages(pod *corev1.Pod) (map[string]podContainerImage, error) { + images := make(map[string]podContainerImage) for _, containerStatus := range pod.Status.ContainerStatuses { if containerStatus.Image != "" && containerStatus.ImageID != "" { - image, err := NewImageFromContainerStatus(containerStatus) + image, err := newImageFromContainerStatus(containerStatus) if err != nil { return nil, err } diff --git a/controllers/workload_controller_test.go b/controllers/workload_controller_test.go index 425e4959..2dbf7d1f 100644 --- a/controllers/workload_controller_test.go +++ b/controllers/workload_controller_test.go @@ -51,7 +51,6 @@ var _ = Describe("Workload controller", func() { expectedImage := stasv1alpha1.Image{ Name: "my.registry/repository/app", Digest: "sha256:4b59f7dacd37c688968756d176139715df69d89eb0be1802e059316f9d58d9ef", - Tag: "f54a333e", } imageScans := &stasv1alpha1.ContainerImageScanList{} @@ -61,6 +60,7 @@ var _ = Describe("Workload controller", func() { } Eventually(komega.ObjectList(imageScans, listOps...), timeout, interval).Should(HaveField("Items", HaveLen(1))) Expect(imageScans.Items[0].Spec.Image).To(Equal(expectedImage)) + Expect(imageScans.Items[0].Spec.Tag).To(Equal("f54a333e")) }, Entry("ReplicaSet", "replica-set", newReplicaSet), Entry("StatefulSet", "stateful-set", newStatefulSet), diff --git a/internal/metrics/collector_test.go b/internal/metrics/collector_test.go index 0bd37345..4ba7f7ea 100644 --- a/internal/metrics/collector_test.go +++ b/internal/metrics/collector_test.go @@ -84,9 +84,11 @@ func newClientWithTestdata() client.Client { }, }, Spec: stasv1alpha1.ContainerImageScanSpec{ - Image: stasv1alpha1.Image{ - Name: "my.registry.com/my-namespace/good-image", - Digest: "sha256:293d59096e2bf7bce8c8af44086f5d3f81c98cad87928837b1cb52a61041e5d5", + ImageScanSpec: stasv1alpha1.ImageScanSpec{ + Image: stasv1alpha1.Image{ + Name: "my.registry.com/my-namespace/good-image", + Digest: "sha256:293d59096e2bf7bce8c8af44086f5d3f81c98cad87928837b1cb52a61041e5d5", + }, }, }, }, @@ -100,11 +102,13 @@ func newClientWithTestdata() client.Client { }, }, Spec: stasv1alpha1.ContainerImageScanSpec{ - Image: stasv1alpha1.Image{ - Name: "my.registry.com/my-namespace/bad-app", - Digest: "sha256:aa5f8d668258d929ee42f000b71318379a86b56e72e301ced34df8887ccbc76a", - Tag: "latest", + ImageScanSpec: stasv1alpha1.ImageScanSpec{ + Image: stasv1alpha1.Image{ + Name: "my.registry.com/my-namespace/bad-app", + Digest: "sha256:aa5f8d668258d929ee42f000b71318379a86b56e72e301ced34df8887ccbc76a", + }, }, + Tag: "latest", }, Status: stasv1alpha1.ContainerImageScanStatus{ VulnerabilitySummary: &stasv1alpha1.VulnerabilitySummary{ @@ -124,11 +128,13 @@ func newClientWithTestdata() client.Client { }, }, Spec: stasv1alpha1.ContainerImageScanSpec{ - Image: stasv1alpha1.Image{ - Name: "my.registry.com/my-namespace/scan-error", - Digest: "sha256:babaa4d10a7e388a37b8d41069438518184f13cdec20c580f16114b84819618b", - Tag: "latest", + ImageScanSpec: stasv1alpha1.ImageScanSpec{ + Image: stasv1alpha1.Image{ + Name: "my.registry.com/my-namespace/scan-error", + Digest: "sha256:babaa4d10a7e388a37b8d41069438518184f13cdec20c580f16114b84819618b", + }, }, + Tag: "latest", }, Status: stasv1alpha1.ContainerImageScanStatus{ Conditions: []metav1.Condition{{