Repository: redhat-openshift-ecosystem/openshift-preflight
Pull request #746 (2022-07-27 14:47:17)

Re-adds basedOnUBI check to root policy exception
In https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/680 I left out the BasedOnUBI check for the container root policy exception check list. This PR re-adds this check.

Signed-off-by: Jose R. Gonzalez <jose@flutes.dev>

openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/746#" title="Author self-approved">komish</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=redhat-openshift-ecosystem%2Fopenshift-preflight).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/redhat-openshift-ecosystem/openshift-preflight/blob/main/OWNERS)~~ [komish]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/746#pullrequestreview-1052647187" title="Approved">acornett21</a>*, *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/746#" title="Author self-approved">komish</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=redhat-openshift-ecosystem%2Fopenshift-preflight).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/redhat-openshift-ecosystem/openshift-preflight/blob/main/OWNERS)~~ [acornett21,komish]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
coveralls: 
[![Coverage Status](https://coveralls.io/builds/51203434/badge)](https://coveralls.io/builds/51203434)

Coverage increased (+0.02%) to 84.228% when pulling **da3ef8bbdeec6b707da0a80fe44789ef5ab4bbf3 on komish:add-missing-ubi-chk** into **2b6d03d94934ed6b7eaa0a294ef5814e7b8a0e56 on redhat-openshift-ecosystem:main**.

openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/746#pullrequestreview-1052647187" title="Approved">acornett21</a>*, *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/746#pullrequestreview-1052660173" title="LGTM">bcrochet</a>*, *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/746#" title="Author self-approved">komish</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=redhat-openshift-ecosystem%2Fopenshift-preflight).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/redhat-openshift-ecosystem/openshift-preflight/blob/main/OWNERS)~~ [acornett21,bcrochet,komish]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/certification/engine/engine.go b/certification/engine/engine.go
index 19ac310..701c612 100644
--- a/certification/engine/engine.go
+++ b/certification/engine/engine.go
@@ -86,6 +86,11 @@ func initializeChecks(ctx context.Context, p policy.Policy, cfg certification.Co
 			&containerpol.HasNoProhibitedPackagesCheck{},
 			&containerpol.HasRequiredLabelsCheck{},
 			&containerpol.HasModifiedFilesCheck{},
+			containerpol.NewBasedOnUbiCheck(pyxis.NewPyxisClient(
+				certification.DefaultPyxisHost,
+				cfg.PyxisAPIToken(),
+				cfg.CertificationProjectID(),
+				&http.Client{Timeout: 60 * time.Second})),
 		}, nil
 	case policy.PolicyScratch:
 		return []certification.Check{
diff --git a/certification/internal/policy/operator/related_images.go b/certification/internal/policy/operator/related_images.go
new file mode 100644
index 0000000..b1adffd
--- /dev/null
+++ b/certification/internal/policy/operator/related_images.go
@@ -0,0 +1,89 @@
+package operator
+
+import (
+	"context"
+	"fmt"
+	"path/filepath"
+
+	operatorv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
+	log "github.com/sirupsen/logrus"
+
+	"github.com/operator-framework/operator-manifest-tools/pkg/image"
+	"github.com/operator-framework/operator-manifest-tools/pkg/pullspec"
+	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification"
+	"sigs.k8s.io/yaml"
+)
+
+type RelatedImagesCheck struct{}
+
+func (p *RelatedImagesCheck) Validate(ctx context.Context, imgRef certification.ImageReference) (bool, error) {
+	images, manifests, err := p.dataToValidate(ctx, imgRef.ImageFSPath)
+	if err != nil {
+		return false, err
+	}
+
+	return p.validate(ctx, images, manifests)
+}
+
+func (p *RelatedImagesCheck) dataToValidate(ctx context.Context, imagePath string) ([]string, map[string]struct{}, error) {
+	operatorManifests, err := pullspec.FromDirectory(filepath.Join(imagePath, "manifests"), pullspec.DefaultHeuristic)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	imageNames, err := image.Extract(operatorManifests)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	relatedImages := make(map[string]struct{}, 0)
+	for _, manifest := range operatorManifests {
+		if manifest.HasRelatedImages() {
+			csvBytes, err := manifest.ToYaml()
+			if err != nil {
+				return nil, nil, fmt.Errorf("could not get manifest yaml: %v", err)
+			}
+			var csv operatorv1alpha1.ClusterServiceVersion
+			err = yaml.Unmarshal(csvBytes, &csv)
+			if err != nil {
+				return nil, nil, fmt.Errorf("malformed CSV detected: %v", err)
+			}
+
+			for _, relatedImage := range csv.Spec.RelatedImages {
+				relatedImages[relatedImage.Image] = struct{}{}
+			}
+			break
+		}
+	}
+
+	return imageNames, relatedImages, nil
+}
+
+func (p *RelatedImagesCheck) validate(ctx context.Context, images []string, relatedImages map[string]struct{}) (bool, error) {
+	for _, image := range images {
+		if _, ok := relatedImages[image]; !ok {
+			log.Warningf("Image %s is not in relatedImages. This will eventually cause this check to fail", image)
+		}
+	}
+	return true, nil
+}
+
+func (p *RelatedImagesCheck) Name() string {
+	return "AllImageRefsInRelatedImages"
+}
+
+func (p *RelatedImagesCheck) Metadata() certification.Metadata {
+	return certification.Metadata{
+		Description:      "Check that all images in the CSV are listed in RelatedImages section",
+		Level:            "optional",
+		KnowledgeBaseURL: "https://access.redhat.com/documentation/en-us/red_hat_software_certification/8.45/html/red_hat_openshift_software_certification_policy_guide/assembly-products-managed-by-an-operator_openshift-sw-cert-policy-container-images#con-operator-requirements_openshift-sw-cert-policy-products-managed",
+		CheckURL:         "https://access.redhat.com/documentation/en-us/red_hat_software_certification/8.45/html/red_hat_openshift_software_certification_policy_guide/assembly-products-managed-by-an-operator_openshift-sw-cert-policy-container-images#con-operator-requirements_openshift-sw-cert-policy-products-managed",
+	}
+}
+
+func (p *RelatedImagesCheck) Help() certification.HelpText {
+	return certification.HelpText{
+		Message:    "Check that all images refereneced in the CSV are in RelatedImages",
+		Suggestion: "Either manually or with a tool, populate the RelatedImages section of the CSV",
+	}
+}


Test patch:
diff --git a/certification/internal/policy/operator/related_images_test.go b/certification/internal/policy/operator/related_images_test.go
new file mode 100644
index 0000000..1967e3f
--- /dev/null
+++ b/certification/internal/policy/operator/related_images_test.go
@@ -0,0 +1,104 @@
+package operator
+
+import (
+	"context"
+	"os"
+	"path/filepath"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification"
+)
+
+var _ = Describe("RelatedImages", func() {
+	const (
+		manifestsDir                  = "manifests"
+		clusterServiceVersionFilename = "myoperator.clusterserviceversion.yaml"
+	)
+	var (
+		relatedImagesCheck *RelatedImagesCheck
+		imageRef           certification.ImageReference
+		csvContents        = `kind: ClusterServiceVersion
+apiVersion: operators.coreos.com/v1alpha1
+spec:
+  install:
+    spec:
+      deployments:
+      - spec:
+          template:
+            spec:
+              containers:
+              - image: registry.example.io/foo/bar@sha256:f000432f07cd187469f0310e3ed9dcf9a5db2be14b8bab9c5293dd1ee8518176
+                name: the-operator
+  relatedImages:
+  - name: the-operator
+    image: registry.example.io/foo/bar@sha256:f000432f07cd187469f0310e3ed9dcf9a5db2be14b8bab9c5293dd1ee8518176
+  - name: the-proxy
+    image: registry.example.io/foo/proxy@sha256:5e33f9d095952866b9743cc8268fb740cce6d93439f00ce333a2de1e5974837e`
+	)
+
+	BeforeEach(func() {
+		relatedImagesCheck = &RelatedImagesCheck{}
+		tmpDir, err := os.MkdirTemp("", "related-images-bundle-*")
+		Expect(err).ToNot(HaveOccurred())
+		imageRef.ImageFSPath = tmpDir
+		DeferCleanup(os.RemoveAll, tmpDir)
+
+		err = os.Mkdir(filepath.Join(tmpDir, manifestsDir), 0o755)
+		Expect(err).ToNot(HaveOccurred())
+
+		err = os.WriteFile(filepath.Join(tmpDir, manifestsDir, clusterServiceVersionFilename), []byte(csvContents), 0o644)
+		Expect(err).ToNot(HaveOccurred())
+	})
+	When("given a good CSV", func() {
+		It("should succeed", func() {
+			result, err := relatedImagesCheck.Validate(context.TODO(), imageRef)
+			Expect(err).ToNot(HaveOccurred())
+			Expect(result).To(BeTrue())
+		})
+	})
+	When("there is no CSV", func() {
+		It("should fail", func() {
+			Expect(os.RemoveAll(imageRef.ImageFSPath)).To(Succeed())
+			result, err := relatedImagesCheck.Validate(context.TODO(), imageRef)
+			Expect(err).To(HaveOccurred())
+			Expect(result).To(BeFalse())
+		})
+	})
+	When("the CSV is malformed", func() {
+		It("should fail", func() {
+			csvContents := `kind: ClusterServiceVersion
+apiVersion: operators.coreos.com/v1alpha1
+spec:
+`
+			Expect(os.WriteFile(filepath.Join(imageRef.ImageFSPath, manifestsDir, clusterServiceVersionFilename), []byte(csvContents), 0o644)).To(Succeed())
+			result, err := relatedImagesCheck.Validate(context.TODO(), imageRef)
+			Expect(err).To(HaveOccurred())
+			Expect(result).To(BeFalse())
+		})
+	})
+	When("the image is not in RelatedImages", func() {
+		It("should still pass", func() {
+			csvContents := `kind: ClusterServiceVersion
+apiVersion: operators.coreos.com/v1alpha1
+spec:
+  install:
+    spec:
+      deployments:
+      - spec:
+          template:
+            spec:
+              containers:
+              - image: registry.example.io/foo/bar@sha256:f000432f07cd187469f0310e3ed9dcf9a5db2be14b8bab9c5293dd1ee8518176
+                name: the-operator
+  relatedImages:
+  - name: the-proxy
+    image: registry.example.io/foo/proxy@sha256:5e33f9d095952866b9743cc8268fb740cce6d93439f00ce333a2de1e5974837e`
+			Expect(os.WriteFile(filepath.Join(imageRef.ImageFSPath, manifestsDir, clusterServiceVersionFilename), []byte(csvContents), 0o644)).To(Succeed())
+			result, err := relatedImagesCheck.Validate(context.TODO(), imageRef)
+			Expect(err).ToNot(HaveOccurred())
+			Expect(result).To(BeTrue())
+		})
+	})
+	AssertMetaData(relatedImagesCheck)
+})