Repository: redhat-openshift-ecosystem/openshift-preflight
Pull request #123 (2021-07-21 21:06:07)

Add podman create, remove, copyFrom, and skopeo inspect
## Context

In implementing #35 (Operator Related Image Schema Version Check), I'll need to temporarily "run" a container so that I can copy files out of the running container.

In addition, I need to utilize `skopeo inspect --raw` in order to parse the schemaVersion value from the rawManifest.

This PR extends both the Podman and Skopeo engines and interfaces, adding the necessary functionality that I can leverage for to implement the check.

## Change Summary

- Defines `Create(...)` for the Podman Engine which allows us to run `podman create` in order to create a running container. I've also defined options for this method, and only included the specific option I need for my implementation. The FakePodmanEngine and BadPodmanEngine have corresponding methods to ensure they implement the interfaces.

- Defines `CopyFrom(...)` for the Podman Engine  which allows us to run `podman cp` with implicit direction (copy from container to host). FakePodmanEngine and BadPodmanEngine are updated with corresponding methods.

- Defines `Remove(...)` for the Podman Engine which allows us to run `podman rm` to clean up the container we're creating above. FakePodmanEngine and BadPodmanEngine are updated with corresponding methods.

- Defines `Inspect(...)` for the Skopeo Engine which allows us to run `skopeo inspect` to inspect images as we need. Podman inspect was already implemented, but I don't believe it has the ability to inspect the raw manifest as skopeo does. FakeSkopeoEngine has been updated to ensure it continues to implement the interface.

Signed-off-by: Jose R. Gonzalez <josegonzalez89@gmail.com>

bcrochet: /lgtm
openshift-ci[bot]: New changes are detected. LGTM label has been removed.

Patch:
diff --git a/README.md b/README.md
index dad8bbc..53d77fc 100644
--- a/README.md
+++ b/README.md
@@ -25,10 +25,10 @@ cluster. which is not provided by the Vagrantfile
 
 ## Usage
 
-The `preflight` utility allows one to confirm that container and Operator projects
+The `preflight` allows you to confirm that your container and Operator projects
 comply with container and Operator certification policies.
 
-A brief summary of the available sub-commands is as follows:
+A brief summary of the available subcommands is as follows:
 
 ```text
 A utility that allows you to pre-test your bundles, operators, and container before submitting for Red Hat Certification.
@@ -41,60 +41,18 @@ Available Commands:
   check       Run checks for an operator or container
   help        Help about any command
   support     Submits a support request
-
-Flags:
-  -h, --help      help for preflight
-  -v, --version   version for preflight
-
-Use "preflight [command] --help" for more information about a command.
 ```
 
-To check a container, utilize the `check container` sub-command:
+(note that `certify` and `support` subcommands are pending implementation)
+
+To check a container, utilize the `check container` subcommand:
 
 ```text
 preflight check container quay.io/example-namespace/example-container:0.0.1
 ```
 
-The check an Operator bundle, utilize the `check Operator` sub-command:
+The check an Operator bundle, utilize the `check Operator` subcommand:
 
 ```text
 preflight check operator quay.io/example-namespace/example-operator:0.0.1
 ```
-
-## Installation
-
-Before installing `preflight`, ensure that the [required dependencies](#requirements) have been installed on the local machine.
-
-### Install Prebuilt Release
-
-One of the prebuilt [release binaries][releases_link] for the supported
-architectures can be downloaded and installed to the local machine.
-
-### Install From Source
-
-Once the repository has been cloned locally, the `preflight` binary can be built
-from source by using the provided target from within the root of the project directory.
-
-```bash
-make build
-```
-
-The `preflight` binary will be created in the root of the project directory. The binary can then be copied manually to a location in the local `$PATH`.
-
-```bash
-sudo mv preflight /usr/local/bin/
-```
-
-Verify that the `preflight` binary can run successfully.
-
-```bash
-preflight --version
-```
-
-The version information should be displayed.
-
-```bash
-preflight version 0.0.0 <commit: 2d3bb671bff8a95d385621382f31215234877d44>
-```
-
-[releases_link]:https://github.com/redhat-openshift-ecosystem/openshift-preflight/releases
diff --git a/certification/internal/shell/has_license.go b/certification/internal/shell/has_license.go
index 31bcdd2..b996a2d 100644
--- a/certification/internal/shell/has_license.go
+++ b/certification/internal/shell/has_license.go
@@ -1,7 +1,6 @@
 package shell
 
 import (
-	"fmt"
 	"strings"
 
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification"
@@ -9,52 +8,40 @@ import (
 	log "github.com/sirupsen/logrus"
 )
 
-const (
-	licensePath         = "/licenses"
-	newLine             = "\n"
-	minLicenseFileCount = 1
-)
-
 type HasLicenseCheck struct{}
 
 func (p *HasLicenseCheck) Validate(image string) (bool, error) {
-	licenseFileList, err := p.getDataToValidate(image)
+	line, err := p.getDataToValidate(image)
 	if err != nil {
 		return false, err
 	}
-	return p.validate(licenseFileList)
+	return p.validate(line)
 }
 
 func (p *HasLicenseCheck) getDataToValidate(image string) (string, error) {
 	runOpts := cli.ImageRunOptions{
-		EntryPoint:     "stat",
-		EntryPointArgs: []string{licensePath},
+		EntryPoint:     "ls",
+		EntryPointArgs: []string{"-A", "/licenses"},
 		LogLevel:       "debug",
 		Image:          image,
 	}
 	runReport, err := podmanEngine.Run(runOpts)
 	if err != nil {
-		log.Error(fmt.Sprintf("Error when checking for %s : ", licensePath), err)
-		log.Errorf("Stdout: %s", runReport.Stdout)
-		log.Debugf("Stderr: %s", runReport.Stderr)
-		return "", err
-	}
-	runOpts.EntryPoint = "ls"
-	runOpts.EntryPointArgs = []string{"-1", licensePath}
-	runReport, err = podmanEngine.Run(runOpts)
-	if err != nil {
-		log.Error(fmt.Sprintf("Error when listing %s : ", licensePath), err)
-		log.Errorf("Stdout: %s", runReport.Stdout)
-		log.Debugf("Stderr: %s", runReport.Stderr)
-		return "", err
+		ok, _ := p.validate(runReport.Stdout)
+		if ok {
+			log.Error("some error attempting to identify if /licenses container the license: ", err)
+			log.Debugf("Stdout: %s", runReport.Stdout)
+			log.Debugf("Stderr: %s", runReport.Stderr)
+			return "", err
+		}
+
 	}
 	return runReport.Stdout, nil
 }
 
-func (p *HasLicenseCheck) validate(licenseFileList string) (bool, error) {
-	licenseFileCount := strings.Count(licenseFileList, newLine)
-	log.Infof("%d Licenses found", licenseFileCount)
-	return licenseFileCount >= minLicenseFileCount, nil
+func (p *HasLicenseCheck) validate(line string) (bool, error) {
+
+	return !strings.Contains(line, "No such file or directory") && line != "", nil
 }
 
 func (p *HasLicenseCheck) Name() string {
diff --git a/certification/internal/shell/podman.go b/certification/internal/shell/podman.go
index 187fbe0..fe5b0f3 100644
--- a/certification/internal/shell/podman.go
+++ b/certification/internal/shell/podman.go
@@ -9,6 +9,7 @@ import (
 	"os/exec"
 	"path/filepath"
 	"regexp"
+	"strings"
 
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification/errors"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification/internal/utils/file"
@@ -42,9 +43,9 @@ func (pe PodmanCLIEngine) Run(opts cli.ImageRunOptions) (*cli.ImageRunReport, er
 	cmd.Stderr = &stderr
 	err := cmd.Run()
 	if err != nil {
-		return &cli.ImageRunReport{Stdout: stdout.String(), Stderr: stderr.String(), ExitCode: cmd.ProcessState.ExitCode()}, err
+		return &cli.ImageRunReport{Stdout: stdout.String(), Stderr: stderr.String()}, err
 	}
-	return &cli.ImageRunReport{Stdout: stdout.String(), Stderr: stderr.String(), ExitCode: cmd.ProcessState.ExitCode()}, nil
+	return &cli.ImageRunReport{Stdout: stdout.String(), Stderr: stderr.String()}, nil
 }
 
 func (pe PodmanCLIEngine) Save(nameOrID string, tags []string, opts cli.ImageSaveOptions) error {
@@ -84,7 +85,6 @@ func (pe PodmanCLIEngine) InspectImage(rawImage string, opts cli.ImageInspectOpt
 // ScanImage takes the `image` and runs `oscap-podman` against the image.
 // It also saves results to the vuln.html file in the current directory,
 // for the end users' reference.
-
 func (pe PodmanCLIEngine) ScanImage(image string) (*cli.ImageScanReport, error) {
 	ovalFileUrl := fmt.Sprintf("%s%s", ovalUrl, ovalFilename)
 	dir, err := ioutil.TempDir(".", "oval-")
@@ -136,3 +136,89 @@ func (pe PodmanCLIEngine) ScanImage(image string) (*cli.ImageScanReport, error)
 
 	return &cli.ImageScanReport{Stdout: out.String(), Stderr: stderr.String()}, nil
 }
+
+// Create simply creates a stopped container from the image provided and returns the container ID.
+// It is the responsibility of the caller to clean up the image after use.
+func (pe PodmanCLIEngine) Create(rawImage string, opts *cli.PodmanCreateOptions) (*cli.PodmanCreateReport, error) {
+	cmdArgs := []string{"create"}
+
+	if opts.Entrypoint != "" {
+		cmdArgs = append(cmdArgs, "--entrypoint", opts.Entrypoint)
+	}
+
+	cmdArgs = append(cmdArgs, rawImage)
+
+	cmd := exec.Command("podman", cmdArgs...)
+	var stdout bytes.Buffer
+	var stderr bytes.Buffer
+	cmd.Stdout = &stdout
+	cmd.Stderr = &stderr
+
+	log.Trace("Running podman with the following invocation", cmdArgs)
+	err := cmd.Run()
+	if err != nil {
+		return &cli.PodmanCreateReport{
+			Stdout: stdout.String(),
+			Stderr: stderr.String(),
+		}, nil
+	}
+
+	return &cli.PodmanCreateReport{
+		Stdout:      stdout.String(),
+		Stderr:      stderr.String(),
+		ContainerID: strings.TrimSpace(stdout.String()),
+	}, nil
+}
+
+// CopyFrom will copy the sourcePath from the container at the specified containerID to the destinationPath.
+func (pe PodmanCLIEngine) CopyFrom(containerID, sourcePath, destinationPath string) (*cli.PodmanCopyReport, error) {
+	cmdArgs := []string{"cp"}
+	sourceArg := strings.Join([]string{containerID, sourcePath}, ":")
+
+	cmdArgs = append(cmdArgs, sourceArg, destinationPath)
+	cmd := exec.Command("podman", cmdArgs...)
+	var stdout bytes.Buffer
+	var stderr bytes.Buffer
+	cmd.Stdout = &stdout
+	cmd.Stderr = &stderr
+
+	log.Trace("Running podman with the following invocation", cmdArgs)
+	err := cmd.Run()
+	if err != nil {
+		return &cli.PodmanCopyReport{
+			Stdout: stdout.String(),
+			Stderr: stderr.String(),
+		}, err
+	}
+
+	return &cli.PodmanCopyReport{
+		Stdout: stdout.String(),
+		Stderr: stderr.String(),
+	}, nil
+}
+
+// Remove will attempt to remove a given container from the system.
+func (pe PodmanCLIEngine) Remove(containerID string) (*cli.PodmanRemoveReport, error) {
+	cmdArgs := []string{"rm"}
+	cmdArgs = append(cmdArgs, containerID)
+
+	cmd := exec.Command("podman", cmdArgs...)
+	var stdout bytes.Buffer
+	var stderr bytes.Buffer
+	cmd.Stdout = &stdout
+	cmd.Stderr = &stderr
+	log.Trace("Running podman with the following invocation", cmdArgs)
+
+	err := cmd.Run()
+	if err != nil {
+		return &cli.PodmanRemoveReport{
+			Stdout: stdout.String(),
+			Stderr: stderr.String(),
+		}, err
+	}
+
+	return &cli.PodmanRemoveReport{
+		Stdout: stdout.String(),
+		Stderr: stderr.String(),
+	}, nil
+}
diff --git a/certification/internal/shell/scorecard_check.go b/certification/internal/shell/scorecard_check.go
index 2c4e1bf..8f05537 100644
--- a/certification/internal/shell/scorecard_check.go
+++ b/certification/internal/shell/scorecard_check.go
@@ -18,7 +18,6 @@ func (p *scorecardCheck) validate(items []cli.OperatorSdkScorecardItem) (bool, e
 	for _, item := range items {
 		for _, result := range item.Status.Results {
 			if strings.Contains(result.State, "fail") {
-				log.Error(result.Log)
 				foundTestFailed = true
 			}
 		}
diff --git a/certification/internal/shell/skopeo.go b/certification/internal/shell/skopeo.go
index 91d2ffa..c2d77ec 100644
--- a/certification/internal/shell/skopeo.go
+++ b/certification/internal/shell/skopeo.go
@@ -71,3 +71,33 @@ func (e SkopeoCLIEngine) imageName(image string) (string, error) {
 
 	return "", errors.ErrInvalidImageName
 }
+
+// InspectImage will use skopeo to inspect the input image given input options.
+func (e SkopeoCLIEngine) InspectImage(image string, opts cli.SkopeoInspectOptions) (*cli.SkopeoInspectReport, error) {
+	cmdArgs := []string{"inspect"}
+
+	// add options to the command string
+	if opts.Raw {
+		cmdArgs = append(cmdArgs, "--raw")
+	}
+
+	cmdArgs = append(cmdArgs, "docker://"+image)
+
+	log.Trace("running skopeo with the following invocation", cmdArgs)
+	cmd := exec.Command("skopeo", cmdArgs...)
+
+	var stdout, stderr bytes.Buffer
+	cmd.Stdout = &stdout
+	cmd.Stderr = &stderr
+	err := cmd.Run()
+
+	if err != nil {
+		return &cli.SkopeoInspectReport{Stdout: stdout.String(), Stderr: stderr.String()}, err
+	}
+
+	return &cli.SkopeoInspectReport{
+		Stdout: stdout.String(),
+		Stderr: stderr.String(),
+		Blob:   stdout.Bytes(),
+	}, nil
+}
diff --git a/cli/podman.go b/cli/podman.go
index 469a0c4..a751a78 100644
--- a/cli/podman.go
+++ b/cli/podman.go
@@ -24,9 +24,8 @@ type ImageRunOptions struct {
 }
 
 type ImageRunReport struct {
-	Stdout   string
-	Stderr   string
-	ExitCode int
+	Stdout string
+	Stderr string
 }
 
 type ImageSaveOptions struct {
@@ -55,10 +54,35 @@ type ImageScanReport struct {
 	Stderr string
 }
 
+type PodmanCopyReport struct {
+	Stdout string
+	Stderr string
+}
+
+type PodmanCreateReport struct {
+	Stdout      string
+	Stderr      string
+	ContainerID string
+}
+
+type PodmanCreateOptions struct {
+	// Entrypoint is an explicit entrypoint to pass to the Create command. Leaving this
+	// empty will not add the --entrypoint flag to the command invocation.
+	Entrypoint string
+}
+
+type PodmanRemoveReport struct {
+	Stdout string
+	Stderr string
+}
+
 type PodmanEngine interface {
 	InspectImage(rawImage string, opts ImageInspectOptions) (*ImageInspectReport, error)
 	Pull(rawImage string, opts ImagePullOptions) (*ImagePullReport, error)
 	Run(opts ImageRunOptions) (*ImageRunReport, error)
 	Save(nameOrID string, tags []string, opts ImageSaveOptions) error
 	ScanImage(image string) (*ImageScanReport, error)
+	Create(rawImage string, opts *PodmanCreateOptions) (*PodmanCreateReport, error)
+	CopyFrom(containerID, sourcePath, destinationPath string) (*PodmanCopyReport, error)
+	Remove(containerID string) (*PodmanRemoveReport, error)
 }
diff --git a/cli/skopeo.go b/cli/skopeo.go
index 6ba7917..1162cab 100644
--- a/cli/skopeo.go
+++ b/cli/skopeo.go
@@ -6,6 +6,24 @@ type SkopeoListTagsReport struct {
 	Tags   []string
 }
 
+// SkopeoInspectReport contains information about the execution of a
+// skopeo inspect.
+type SkopeoInspectReport struct {
+	Stdout string
+	Stderr string
+	// Blob represents the JSON response from Skopeo.
+	// This response could vary in format so it is up to the caller
+	// to serialize this as they expect based on SkopeoInspectOptions
+	Blob []byte
+}
+
+// SkopeoInspectOptions represent options to pass to `skopeo inspect` command
+// invocations
+type SkopeoInspectOptions struct {
+	Raw bool
+}
+
 type SkopeoEngine interface {
 	ListTags(rawImage string) (*SkopeoListTagsReport, error)
+	InspectImage(rawImage string, inspectOptions SkopeoInspectOptions) (*SkopeoInspectReport, error)
 }
diff --git a/cmd/check_operator.go b/cmd/check_operator.go
index 5e62e4e..29af045 100644
--- a/cmd/check_operator.go
+++ b/cmd/check_operator.go
@@ -61,9 +61,7 @@ var checkOperatorCmd = &cobra.Command{
 		resultsOutputTarget := io.MultiWriter(os.Stdout, resultsFile)
 
 		// execute the checks
-		if err := engine.ExecuteChecks(); err != nil {
-			return err
-		}
+		engine.ExecuteChecks()
 		results := engine.Results()
 
 		// return results to the user and then close output files


Test patch:
diff --git a/certification/internal/shell/has_license_test.go b/certification/internal/shell/has_license_test.go
index a7df72b..3340f40 100644
--- a/certification/internal/shell/has_license_test.go
+++ b/certification/internal/shell/has_license_test.go
@@ -14,25 +14,25 @@ var _ = Describe("HasLicense", func() {
 
 	BeforeEach(func() {
 		fakeEngine = FakePodmanEngine{
-			RunReportStdout: "license\n",
+			RunReportStdout: `/licenses`,
 			RunReportStderr: "",
 		}
 	})
-	Describe("Checking if licenses can be found", func() {
-		Context("When license(s) are found", func() {
+	Describe("Checking if license can be found", func() {
+		Context("When license is found", func() {
 			BeforeEach(func() {
 				podmanEngine = fakeEngine
 			})
-			It("Should pass Validate", func() {
+			It("should pass Validate", func() {
 				ok, err := HasLicense.Validate("dummy/image")
 				Expect(err).ToNot(HaveOccurred())
 				Expect(ok).To(BeTrue())
 			})
 		})
-		Context("When licenses directory is not found", func() {
+		Context("When license is not found", func() {
 			BeforeEach(func() {
 				engine := fakeEngine.(FakePodmanEngine)
-				engine.RunReportStdout = "stat: cannot stat '/licenses': No such file or directory"
+				engine.RunReportStdout = "No such file or directory"
 				podmanEngine = engine
 			})
 			It("Should not pass Validate", func() {
@@ -41,7 +41,7 @@ var _ = Describe("HasLicense", func() {
 				Expect(ok).To(BeFalse())
 			})
 		})
-		Context("Licenses can't be found when directory exists", func() {
+		Context("License can't be found when directory exists", func() {
 			BeforeEach(func() {
 				engine := fakeEngine.(FakePodmanEngine)
 				engine.RunReportStdout = ""
diff --git a/certification/internal/shell/shell_suite_test.go b/certification/internal/shell/shell_suite_test.go
index 6e0f9e7..d9d3a03 100644
--- a/certification/internal/shell/shell_suite_test.go
+++ b/certification/internal/shell/shell_suite_test.go
@@ -49,17 +49,18 @@ var _ = AfterSuite(func() {
 type FakePodmanEngine struct {
 	RunReportStdout     string
 	RunReportStderr     string
-	RunReportExitCode   int
 	PullReportStdouterr string
 	ImageInspectReport  cli.ImageInspectReport
 	ImageScanReport     cli.ImageScanReport
+	CreateReport        cli.PodmanCreateReport
+	CopyFromReport      cli.PodmanCopyReport
+	RemoveReport        cli.PodmanRemoveReport
 }
 
 func (fpe FakePodmanEngine) Run(opts cli.ImageRunOptions) (*cli.ImageRunReport, error) {
 	runReport := cli.ImageRunReport{
-		Stdout:   fpe.RunReportStdout,
-		Stderr:   fpe.RunReportStderr,
-		ExitCode: fpe.RunReportExitCode,
+		Stdout: fpe.RunReportStdout,
+		Stderr: fpe.RunReportStderr,
 	}
 	return &runReport, nil
 }
@@ -84,13 +85,24 @@ func (fpe FakePodmanEngine) ScanImage(rawImage string) (*cli.ImageScanReport, er
 	return &fpe.ImageScanReport, nil
 }
 
+func (fpe FakePodmanEngine) Create(rawImage string, opts *cli.PodmanCreateOptions) (*cli.PodmanCreateReport, error) {
+	return &fpe.CreateReport, nil
+}
+
+func (fpe FakePodmanEngine) CopyFrom(containerID, sourcePath, destinationPath string) (*cli.PodmanCopyReport, error) {
+	return &fpe.CopyFromReport, nil
+}
+
+func (fpe FakePodmanEngine) Remove(containerID string) (*cli.PodmanRemoveReport, error) {
+	return &fpe.RemoveReport, nil
+}
+
 type BadPodmanEngine struct{}
 
 func (bpe BadPodmanEngine) Run(cli.ImageRunOptions) (*cli.ImageRunReport, error) {
 	runReport := cli.ImageRunReport{
-		Stdout:   "Bad stadout",
-		Stderr:   "Bad stderr",
-		ExitCode: -1,
+		Stdout: "Bad stadout",
+		Stderr: "Bad stderr",
 	}
 	return &runReport, errors.New("the Podman Run has failed")
 }
@@ -114,6 +126,18 @@ func (bpe BadPodmanEngine) ScanImage(rawImage string) (*cli.ImageScanReport, err
 	return nil, errors.New("the Podman Scan Image has failed")
 }
 
+func (bpe BadPodmanEngine) Create(rawImage string, opts *cli.PodmanCreateOptions) (*cli.PodmanCreateReport, error) {
+	return nil, errors.New("The Podman Create operation has failed")
+}
+
+func (bpe BadPodmanEngine) CopyFrom(containerID, sourcePath, destinationPath string) (*cli.PodmanCopyReport, error) {
+	return nil, errors.New("The Podman Copy From operation has failed")
+}
+
+func (bpe BadPodmanEngine) Remove(containerID string) (*cli.PodmanRemoveReport, error) {
+	return nil, errors.New("The Podman Remove operator has failed")
+}
+
 /*
 ------------------- Skopeo Engine ---------------------
 */
@@ -122,6 +146,7 @@ type FakeSkopeoEngine struct {
 	SkopeoReportStdout string
 	SkopeoReportStderr string
 	Tags               []string
+	InspectReport      cli.SkopeoInspectReport
 }
 
 type SkopeoData struct {
@@ -138,6 +163,10 @@ func (fse FakeSkopeoEngine) ListTags(image string) (*cli.SkopeoListTagsReport, e
 	return &skopeoReport, nil
 }
 
+func (fse FakeSkopeoEngine) InspectImage(rawImage string, inspectOptions cli.SkopeoInspectOptions) (*cli.SkopeoInspectReport, error) {
+	return &fse.InspectReport, nil
+}
+
 type BadSkopeoEngine struct{}
 
 func (bse BadSkopeoEngine) ListTags(string) (*cli.SkopeoListTagsReport, error) {
@@ -149,6 +178,10 @@ func (bse BadSkopeoEngine) ListTags(string) (*cli.SkopeoListTagsReport, error) {
 	return &skopeoReport, errors.New("the Skopeo ListTags has failed")
 }
 
+func (bse BadSkopeoEngine) InspectImage(rawImage string, inspectOptions cli.SkopeoInspectOptions) (*cli.SkopeoInspectReport, error) {
+	return &cli.SkopeoInspectReport{Stdout: "", Stderr: "some error output"}, errors.New("the skopeo image inspection has failed")
+}
+
 /*
 ------------------- Operator Sdk Engine -------------------
 */
diff --git a/test/containerfiles/successful-bundle-assets/manifests/operators.my.domain_mykinds.yaml b/test/containerfiles/successful-bundle-assets/manifests/operators.my.domain_mykinds.yaml
index a6df118..880e0cf 100644
--- a/test/containerfiles/successful-bundle-assets/manifests/operators.my.domain_mykinds.yaml
+++ b/test/containerfiles/successful-bundle-assets/manifests/operators.my.domain_mykinds.yaml
@@ -13,35 +13,35 @@ spec:
     plural: mykinds
     singular: mykind
   scope: Namespaced
-  subresources:
-    status: {}
   versions:
   - name: v1alpha1
+    schema:
+      openAPIV3Schema:
+        description: MyKind is the Schema for the mykinds API
+        properties:
+          apiVersion:
+            description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+            type: string
+          kind:
+            description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+            type: string
+          metadata:
+            type: object
+          spec:
+            description: MyKindSpec defines the desired state of MyKind
+            properties:
+              foo:
+                description: Foo is an example field of MyKind. Edit mykind_types.go to remove/update
+                type: string
+            type: object
+          status:
+            description: MyKindStatus defines the observed state of MyKind
+            type: object
+        type: object
     served: true
     storage: true
-  schema:
-    openAPIV3Schema:
-      description: MyKind is the Schema for the mykinds API
-      properties:
-        apiVersion:
-          description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
-          type: string
-        kind:
-          description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
-          type: string
-        metadata:
-          type: object
-        spec:
-          description: MyKindSpec defines the desired state of MyKind
-          properties:
-            foo:
-              description: Foo is an example field of MyKind. Edit mykind_types.go to remove/update
-              type: string
-          type: object
-        status:
-          description: MyKindStatus defines the observed state of MyKind
-          type: object
-        type: object
+    subresources:
+      status: {}
 status:
   acceptedNames:
     kind: ""
diff --git a/test/containerfiles/successful-bundle-assets/manifests/sample-operator.clusterserviceversion.yaml b/test/containerfiles/successful-bundle-assets/manifests/sample-operator.clusterserviceversion.yaml
index 9a2da6c..3ed395e 100644
--- a/test/containerfiles/successful-bundle-assets/manifests/sample-operator.clusterserviceversion.yaml
+++ b/test/containerfiles/successful-bundle-assets/manifests/sample-operator.clusterserviceversion.yaml
@@ -29,22 +29,6 @@ spec:
       kind: MyKind
       name: mykinds.operators.my.domain
       version: v1alpha1
-      resources:
-       - kind: Pod
-         name: ''
-         version: v1
-      specDescriptors:
-        - description: Foo
-          displayName: Foo
-          path: foo
-          x-descriptors:
-            - 'urn:alm:descriptor'
-      statusDescriptors:
-        - description: Accepted names
-          displayName: Accepted Names
-          path: acceptedNames
-          x-descriptors:
-            - 'urn:alm:descriptor'
   description: Test operator that should pass operator validation
   displayName: Preflight Sample Operator
   icon: