Repository: redhat-openshift-ecosystem/openshift-preflight
Pull request #402 (2022-02-09 22:21:14)

implementing submit flag and saving cert image info and rpm manifest information to disk
- Relates: #369 
- Added in flags to support submission to pyxis
- Converting data that we get from `crane` about the image to pyxis format and saving to disk
- Converting data that we get about the RPMs in the image to pyxis format and saving to disk
- Adding in method to send test results to pyxis

Signed-off-by: Adam D. Cornett <adc@redhat.com>

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

This pull-request has been approved by: *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/402#" title="Author self-approved">acornett21</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **bcrochet** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @bcrochet` in a comment when ready.

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).

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

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

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":["bcrochet"]} -->
openshift-ci[bot]: Hi @acornett21. Thanks for your PR.

I'm waiting for a [redhat-openshift-ecosystem](https://github.com/orgs/redhat-openshift-ecosystem/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://github.com/orgs/redhat-openshift-ecosystem/people) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=redhat-openshift-ecosystem%2Fopenshift-preflight).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

acornett21: /do-not-merge/hold
acornett21: /do-not-merge
acornett21: /hold
bcrochet: /ok-to-test
openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/402#" title="Author self-approved">acornett21</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please ask for approval from **bcrochet** after the PR has been reviewed.

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).

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

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

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":["bcrochet"]} -->
openshift-ci[bot]: @acornett21: PR needs rebase.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
openshift-ci[bot]: @acornett21: The following test **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.8-images | 5f4a2532f872a4cd4e7bbc1a28e10e7f31eb9a35 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-openshift-ecosystem_openshift-preflight/402/pull-ci-redhat-openshift-ecosystem-openshift-preflight-main-4.8-images/1492238432146034688) | true | `/test 4.8-images`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-openshift-ecosystem&repo=openshift-preflight&pr=402). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:acornett21).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @acornett21: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.8-images | 5f4a2532f872a4cd4e7bbc1a28e10e7f31eb9a35 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-openshift-ecosystem_openshift-preflight/402/pull-ci-redhat-openshift-ecosystem-openshift-preflight-main-4.8-images/1492238432146034688) | true | `/test 4.8-images`
ci/prow/4.8-e2e | 5f4a2532f872a4cd4e7bbc1a28e10e7f31eb9a35 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-openshift-ecosystem_openshift-preflight/402/pull-ci-redhat-openshift-ecosystem-openshift-preflight-main-4.8-e2e/1492238431311368192) | true | `/test 4.8-e2e`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-openshift-ecosystem&repo=openshift-preflight&pr=402). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:acornett21).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @acornett21: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.8-e2e | 5f4a2532f872a4cd4e7bbc1a28e10e7f31eb9a35 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-openshift-ecosystem_openshift-preflight/402/pull-ci-redhat-openshift-ecosystem-openshift-preflight-main-4.8-e2e/1492238431311368192) | true | `/test 4.8-e2e`
ci/prow/4.8-images | dbf99a8f77373c1432f09324a6e60332c2e0cc96 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-openshift-ecosystem_openshift-preflight/402/pull-ci-redhat-openshift-ecosystem-openshift-preflight-main-4.8-images/1492261627385352192) | true | `/test 4.8-images`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-openshift-ecosystem&repo=openshift-preflight&pr=402). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:acornett21).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @acornett21: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.8-images | dbf99a8f77373c1432f09324a6e60332c2e0cc96 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-openshift-ecosystem_openshift-preflight/402/pull-ci-redhat-openshift-ecosystem-openshift-preflight-main-4.8-images/1492261627385352192) | true | `/test 4.8-images`
ci/prow/4.8-e2e | ea2d8156790295e483d75bca1218f4587fbeadde | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-openshift-ecosystem_openshift-preflight/402/pull-ci-redhat-openshift-ecosystem-openshift-preflight-main-4.8-e2e/1492263258210439168) | true | `/test 4.8-e2e`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-openshift-ecosystem&repo=openshift-preflight&pr=402). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:acornett21).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @acornett21: The following tests **failed**, say `/retest` to rerun all failed tests or `/retest-required` to rerun all mandatory failed tests:

Test name | Commit | Details | Required | Rerun command
--- | --- | --- | --- | ---
ci/prow/4.8-e2e | ea2d8156790295e483d75bca1218f4587fbeadde | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-openshift-ecosystem_openshift-preflight/402/pull-ci-redhat-openshift-ecosystem-openshift-preflight-main-4.8-e2e/1492263258210439168) | true | `/test 4.8-e2e`
ci/prow/4.8-images | ea2d8156790295e483d75bca1218f4587fbeadde | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/redhat-openshift-ecosystem_openshift-preflight/402/pull-ci-redhat-openshift-ecosystem-openshift-preflight-main-4.8-images/1492263258243993600) | true | `/test 4.8-images`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=redhat-openshift-ecosystem&repo=openshift-preflight&pr=402). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:acornett21).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
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/402#" title="Author self-approved">acornett21</a>*, *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/402#pullrequestreview-883656461" title="Approved">jomkz</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)~~ [jomkz]

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/.github/workflows/build-main.yml b/.github/workflows/build-main.yml
index 0d84f7c..f3e0ae9 100644
--- a/.github/workflows/build-main.yml
+++ b/.github/workflows/build-main.yml
@@ -4,9 +4,6 @@ on:
   push:
     branches: [ main, ghactions ]
 
-env:
-  IMAGE_NAME: preflight
-  
 jobs:
   build-main:
     name: Build and push main snapshot images
@@ -32,7 +29,7 @@ jobs:
       id: build-image
       uses: redhat-actions/buildah-build@v2
       with:
-        image: ${{ secrets.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}
+        image: ${{ secrets.IMAGE_REGISTRY }}/preflight
         tags: ${{ env.SHA_SHORT }}-${{ matrix.platform }}-${{ matrix.architecture }}
         archs: ${{ matrix.architecture }}
         build-args: |
@@ -46,7 +43,7 @@ jobs:
       id: push-image
       uses: redhat-actions/push-to-registry@v2
       with:
-        image: ${{ env.IMAGE_NAME }}
+        image: preflight
         tags: ${{ env.SHA_SHORT }}-${{ matrix.platform }}-${{ matrix.architecture }}
         registry: ${{ secrets.IMAGE_REGISTRY }}
         username: ${{ secrets.REGISTRY_USER }}
@@ -54,18 +51,3 @@ jobs:
 
     - name: Print image url
       run: echo "Image pushed to ${{ steps.push-image.outputs.registry-paths }}"
-    
-    outputs:
-      imageName: ${{ env.IMAGE_NAME }}
-      imageVersion: ${{ env.SHA_SHORT }}
-
-  build-multiarch:
-    needs: build-main
-    uses: ./.github/workflows/build-multiarch.yml
-    with:
-      name: ${{ needs.build-main.outputs.imageName }}
-      tag: ${{ needs.build-main.outputs.imageVersion }}
-    secrets:
-      registry: ${{ secrets.IMAGE_REGISTRY }}
-      user: ${{ secrets.REGISTRY_USER }}
-      password: ${{ secrets.REGISTRY_PASSWORD }}
diff --git a/.github/workflows/build-multiarch.yml b/.github/workflows/build-multiarch.yml
deleted file mode 100644
index 60fb768..0000000
--- a/.github/workflows/build-multiarch.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-name: Build and push a multi-arch manifest
-
-# Usage example:
-#    uses: ./.github/workflows/build-multiarch.yml
-#    with:
-#      name: preflight
-#      tag: 89123ab
-#    secrets:
-#      registry: ${{ secrets.IMAGE_REGISTRY }}
-#      user: ${{ secrets.REGISTRY_USER }}
-#      password: ${{ secrets.REGISTRY_PASSWORD }}
-
-on:
-  workflow_call:
-    inputs:
-      name:
-        required: true
-        type: string
-      tag:
-        required: true
-        type: string
-    secrets:
-      registry:
-        required: true
-      user:
-        required: true
-      password:
-        required: true
-        
-
-jobs:
-  create-and-push-multiarch-manifest:
-    name: create and push a multiarch manifest to the repo
-    runs-on: ubuntu-latest
-    steps:
-      - name: Create and add to manifest
-        run: |
-          buildah manifest create ${{ inputs.name }}
-          buildah manifest add ${{ inputs.name }} ${{ secrets.registry }}/${{ inputs.name }}:${{ inputs.tag }}-linux-amd64
-          buildah manifest add ${{ inputs.name }} ${{ secrets.registry }}/${{ inputs.name }}:${{ inputs.tag }}-linux-ppc64le
-          buildah manifest add ${{ inputs.name }} ${{ secrets.registry }}/${{ inputs.name }}:${{ inputs.tag }}-linux-arm64
-          buildah manifest add ${{ inputs.name }} ${{ secrets.registry }}/${{ inputs.name }}:${{ inputs.tag }}-linux-s390x
-
-      # Authenticate to container image registry to push the image
-      - name: Podman Login
-        uses: redhat-actions/podman-login@v1
-        with:
-          registry: ${{ secrets.registry }}
-          username: ${{ secrets.user }}
-          password: ${{ secrets.password }}
-          
-      - name: Push manifest
-        run: |
-            podman manifest push ${{ inputs.name }} ${{ secrets.registry }}/${{ inputs.name }}:${{ inputs.tag }}  --all
diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml
index 392eaf7..937b88d 100644
--- a/.github/workflows/build-release.yml
+++ b/.github/workflows/build-release.yml
@@ -6,9 +6,6 @@ on:
       - "[0-9]+.[0-9]+.[0-9]+"
     branches: [ ghactions ]
 
-env:
-  IMAGE_NAME: preflight
-
 jobs:
   build-release:
     name: Build and push tag images
@@ -29,7 +26,7 @@ jobs:
       id: build-image
       uses: redhat-actions/buildah-build@v2
       with:
-        image: ${{ secrets.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}
+        image: ${{ secrets.IMAGE_REGISTRY }}/preflight
         tags: ${{ env.RELEASE_TAG }}-${{ matrix.platform }}-${{ matrix.architecture }}
         archs: ${{ matrix.architecture }}
         build-args: |
@@ -42,7 +39,7 @@ jobs:
       id: push-image
       uses: redhat-actions/push-to-registry@v2
       with:
-        image: ${{ env.IMAGE_NAME }}
+        image: preflight
         tags: ${{ env.RELEASE_TAG }}-${{ matrix.platform }}-${{ matrix.architecture }}
         registry: ${{ secrets.IMAGE_REGISTRY }}
         username: ${{ secrets.REGISTRY_USER }}
@@ -50,18 +47,3 @@ jobs:
 
     - name: Print image url
       run: echo "Image pushed to ${{ steps.push-image.outputs.registry-paths }}"
-
-    outputs:
-      imageName: ${{ env.IMAGE_NAME }}
-      imageVersion: ${{ env.RELEASE_TAG }}
-
-  build-multiarch:
-    needs: build-release
-    uses: ./.github/workflows/build-multiarch.yml
-    with:
-      name: ${{ needs.build-release.outputs.imageName }}
-      tag: ${{ needs.build-release.outputs.imageVersion }}
-    secrets:
-      registry: ${{ secrets.IMAGE_REGISTRY }}
-      user: ${{ secrets.REGISTRY_USER }}
-      password: ${{ secrets.REGISTRY_PASSWORD }}
diff --git a/certification/defaults.go b/certification/defaults.go
new file mode 100644
index 0000000..d8057d3
--- /dev/null
+++ b/certification/defaults.go
@@ -0,0 +1,7 @@
+package certification
+
+var (
+	DefaultCertImageFilename   = "cert-image.json"
+	DefaultRPMManifestFilename = "rpm-manifest.json"
+	DefaultTestResultsFilename = "results.json"
+)
diff --git a/certification/errors/errors.go b/certification/errors/errors.go
index 549fa1e..e4c83e3 100644
--- a/certification/errors/errors.go
+++ b/certification/errors/errors.go
@@ -42,3 +42,6 @@ var ErrUnsupportedGoType = errors.New("go type unsupported")
 var ErrEmptyClusterServiceVersionFile = errors.New("the clusterserviceversion file was empty")
 var ErrInvalidPyxisHost = errors.New("invalid pyxis host specified")
 var ErrInvalidCertProjectId = errors.New("invalid certification project id")
+var ErrNoPyxisAPIKey = errors.New("no pyxis-api-token flag could be found")
+var ErrNoDockerConfig = errors.New("no docker-config flag could be found")
+var ErrSaveFileFailed = errors.New("failed to save file to artifacts directory")
diff --git a/certification/internal/engine/engine.go b/certification/internal/engine/engine.go
index 748592a..56990ba 100644
--- a/certification/internal/engine/engine.go
+++ b/certification/internal/engine/engine.go
@@ -4,23 +4,29 @@ import (
 	"archive/tar"
 	"bytes"
 	"crypto/md5"
+	"encoding/json"
 	"fmt"
 	"io"
 	"io/fs"
 	"os"
 	"path"
 	"path/filepath"
+	"regexp"
 	"sort"
+	"strings"
 	"time"
 
 	"github.com/google/go-containerregistry/pkg/crane"
-	log "github.com/sirupsen/logrus"
-
+	v1 "github.com/google/go-containerregistry/pkg/v1"
+	rpmdb "github.com/knqyf263/go-rpmdb/pkg"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification/artifacts"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification/errors"
+	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification/pyxis"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification/runtime"
 	preflightRuntime "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/runtime"
+	log "github.com/sirupsen/logrus"
+	"github.com/spf13/viper"
 )
 
 // CraneEngine implements a certification.CheckEngine, and leverage crane to interact with
@@ -93,6 +99,19 @@ func (c *CraneEngine) ExecuteChecks() error {
 		return fmt.Errorf("%w: %s", errors.ErrExtractingTarball, err)
 	}
 
+	// if --submit flag is set write json files to disk to be used to send to pyxis after all checks are complete
+	if viper.GetBool("submit") {
+		err := writeCertImage(img)
+		if err != nil {
+			return err
+		}
+
+		err = writeRPMManifest(containerFSPath)
+		if err != nil {
+			return err
+		}
+	}
+
 	// store the image internals in the engine image reference to pass to validations.
 	// TODO: pass this to validations when the new check interface includes it.
 	c.imageRef = certification.ImageReference{
@@ -279,3 +298,170 @@ func untar(dst string, r io.Reader) error {
 		}
 	}
 }
+
+func writeCertImage(img v1.Image) error {
+	config, err := img.ConfigFile()
+	if err != nil {
+		return fmt.Errorf("%w: %s", errors.ErrImageInspectFailed, err)
+	}
+
+	manifest, err := img.Manifest()
+	if err != nil {
+		return fmt.Errorf("%w: %s", errors.ErrImageInspectFailed, err)
+	}
+
+	digest, err := img.Digest()
+	if err != nil {
+		return fmt.Errorf("%w: %s", errors.ErrImageInspectFailed, err)
+	}
+
+	layers, err := img.Layers()
+	if err != nil {
+		return fmt.Errorf("%w: %s", errors.ErrImageInspectFailed, err)
+	}
+
+	rawConfig, err := img.RawConfigFile()
+	if err != nil {
+		return fmt.Errorf("%w: %s", errors.ErrImageInspectFailed, err)
+	}
+
+	size, err := img.Size()
+	if err != nil {
+		return fmt.Errorf("%w: %s", errors.ErrImageInspectFailed, err)
+	}
+
+	labels := convertLabels(config.Config.Labels)
+	layerSize := make([]pyxis.Layer, 10)
+
+	sumLayersSizeBytes, err := sumLayerSizeBytes(layers)
+	if err != nil {
+		return fmt.Errorf("%w: %s", errors.ErrImageInspectFailed, err)
+	}
+
+	certImage := pyxis.CertImage{
+		DockerImageDigest: digest.String(),
+		DockerImageID:     manifest.Config.Digest.String(),
+		ImageID:           digest.String(),
+		ParsedData: &pyxis.ParsedData{
+			Architecture:  config.Architecture,
+			Command:       strings.Join(config.Config.Cmd, " "),
+			Created:       config.Created.String(),
+			DockerVersion: config.DockerVersion,
+			ImageID:       digest.String(),
+			Labels:        labels,
+			OS:            config.OS,
+			Size:          size,
+			//TODO: figure out how to get/convert this data from crane
+			UncompressedLayerSizes: layerSize,
+		},
+		RawConfig:         string(rawConfig),
+		SumLayerSizeBytes: sumLayersSizeBytes,
+		//TODO: figure out how to get/convert this data from crane
+		UncompressedTopLayerId: "placeholder",
+	}
+
+	// calling MarshalIndent so the json file written to disk is human-readable when opened
+	certImageJson, err := json.MarshalIndent(certImage, "", "    ")
+	if err != nil {
+		return err
+	}
+
+	fileName, err := artifacts.WriteFile(certification.DefaultCertImageFilename, string(certImageJson))
+	if err != nil {
+		return fmt.Errorf("%w: %s", errors.ErrSaveFileFailed, err)
+	}
+
+	log.Tracef("image config written to disk: %s", fileName)
+
+	return nil
+}
+
+func writeRPMManifest(containerFSPath string) error {
+	db, err := rpmdb.Open(filepath.Join(containerFSPath, "var", "lib", "rpm", "Packages"))
+	if err != nil {
+		return err
+	}
+
+	pkgList, err := db.ListPackages()
+	if err != nil {
+		return err
+	}
+
+	// covert rpm struct to pxyis struct
+	rpms := make([]pyxis.RPM, 0, len(pkgList))
+	for _, packageInfo := range pkgList {
+
+		var bgName string
+		var endChop string
+		var srpmNevra string
+
+		// accounting for the fact that not all packages have a source rpm
+		if len(packageInfo.SourceRpm) > 0 {
+			bgName = packageInfo.SourceRpm[0:strings.LastIndex(strings.SplitAfter(packageInfo.SourceRpm, ".")[0], "-")]
+			endChop = strings.TrimPrefix(strings.TrimSuffix(regexp.MustCompile("(-[0-9].*)").FindString(packageInfo.SourceRpm), ".rpm"), "-")
+
+			srpmNevra = fmt.Sprintf("%s-%d:%s", bgName, packageInfo.Epoch, endChop)
+		}
+
+		rpm := pyxis.RPM{
+			Architecture: packageInfo.Arch,
+			Gpg:          "placeholder", //TODO: change this once GPG is implemented in go-rpm library
+			Name:         packageInfo.Name,
+			Nvra:         fmt.Sprintf("%s-%s-%s.%s", packageInfo.Name, packageInfo.Version, packageInfo.Release, packageInfo.Arch),
+			Release:      packageInfo.Release,
+			SrpmName:     bgName,
+			SrpmNevra:    srpmNevra,
+			Summary:      packageInfo.Summary,
+			Version:      packageInfo.Version,
+		}
+
+		rpms = append(rpms, rpm)
+	}
+
+	rpmManifest := pyxis.RPMManifest{
+		RPMS: rpms,
+	}
+
+	// calling MarshalIndent so the json file written to disk is human-readable when opened
+	rpmManifestJson, err := json.MarshalIndent(rpmManifest, "", "    ")
+	if err != nil {
+		return err
+	}
+
+	fileName, err := artifacts.WriteFile(certification.DefaultRPMManifestFilename, string(rpmManifestJson))
+	if err != nil {
+		return fmt.Errorf("%w: %s", errors.ErrSaveFileFailed, err)
+	}
+
+	log.Tracef("rpm manifest written to disk: %s", fileName)
+
+	return nil
+}
+
+func sumLayerSizeBytes(layers []v1.Layer) (int64, error) {
+	var sum int64
+	for _, layer := range layers {
+		size, err := layer.Size()
+		if err != nil {
+			return 0, err
+		}
+
+		sum = sum + size
+	}
+
+	return sum, nil
+}
+
+func convertLabels(imageLabels map[string]string) []pyxis.Label {
+	pyxisLabels := make([]pyxis.Label, 0, len(imageLabels))
+	for key, value := range imageLabels {
+		label := pyxis.Label{
+			Name:  key,
+			Value: value,
+		}
+
+		pyxisLabels = append(pyxisLabels, label)
+	}
+
+	return pyxisLabels
+}
diff --git a/certification/pyxis/pyxis.go b/certification/pyxis/pyxis.go
index d62fba8..0711ef9 100644
--- a/certification/pyxis/pyxis.go
+++ b/certification/pyxis/pyxis.go
@@ -134,7 +134,7 @@ func (p *pyxisEngine) GetProject(ctx context.Context) (*CertProject, error) {
 	return &certProject, nil
 }
 
-func (p *pyxisEngine) updateProject(ctx context.Context, certProjectId string, certProject *CertProject) (*CertProject, error) {
+func (p *pyxisEngine) updateProject(ctx context.Context, certProject *CertProject) (*CertProject, error) {
 	b, err := json.Marshal(certProject)
 	if err != nil {
 		log.Error(err)
@@ -172,6 +172,44 @@ func (p *pyxisEngine) updateProject(ctx context.Context, certProjectId string, c
 	return &newCertProject, nil
 }
 
+func (p *pyxisEngine) createTestResults(ctx context.Context, testResults *TestResults) (*TestResults, error) {
+	b, err := json.Marshal(testResults)
+	if err != nil {
+		log.Error(err)
+		return nil, err
+	}
+	req, err := newRequestWithApiToken(ctx, http.MethodPost, getPyxisUrl(fmt.Sprintf("projects/certification/id/%s/test-results", p.ProjectId)), p.ApiToken)
+	if err != nil {
+		log.Error(err)
+		return nil, err
+	}
+	req = withJson(req, b)
+
+	resp, err := p.Client.Do(req)
+	if err != nil {
+		log.Error(err)
+		return nil, err
+	}
+
+	if resp.Body != nil {
+		defer resp.Body.Close()
+	}
+
+	body, err := io.ReadAll(resp.Body)
+	if err != nil {
+		log.Error(err)
+		return nil, err
+	}
+
+	var newTestResults = TestResults{}
+	if err := json.Unmarshal(body, &newTestResults); err != nil {
+		log.Error(err)
+		return nil, err
+	}
+
+	return &newTestResults, nil
+}
+
 func newRequestWithApiToken(ctx context.Context, method string, url string, token string) (*http.Request, error) {
 	req, err := http.NewRequestWithContext(ctx, method, url, nil)
 	if err != nil {
diff --git a/certification/pyxis/submit.go b/certification/pyxis/submit.go
index 0b0e09b..64d67a2 100644
--- a/certification/pyxis/submit.go
+++ b/certification/pyxis/submit.go
@@ -6,7 +6,7 @@ import (
 	log "github.com/sirupsen/logrus"
 )
 
-func (p *pyxisEngine) SubmitResults(certProject *CertProject) (*CertProject, *CertImage, error) {
+func (p *pyxisEngine) SubmitResults(certProject *CertProject, certImage *CertImage, rpmManifest *RPMManifest, testResults *TestResults) (*CertProject, *CertImage, *TestResults, error) {
 	var err error
 	ctx := context.Background()
 	oldProject := certProject
@@ -16,26 +16,29 @@ func (p *pyxisEngine) SubmitResults(certProject *CertProject) (*CertProject, *Ce
 	}
 
 	if certProject != oldProject {
-		certProject, err = p.updateProject(ctx, p.ProjectId, certProject)
+		certProject, err = p.updateProject(ctx, certProject)
 		if err != nil {
 			log.Error(err, "could not update project")
-			return nil, nil, err
+			return nil, nil, nil, err
 		}
 	}
 
-	certImage := new(CertImage)
 	certImage, err = p.createImage(ctx, certImage)
 	if err != nil {
 		log.Error(err)
-		return nil, nil, err
+		return nil, nil, nil, err
 	}
 
-	rpms := make([]RPM, 10)
-	err = p.createRPMManifest(ctx, certImage.ImageID, rpms)
+	err = p.createRPMManifest(ctx, certImage.ImageID, rpmManifest.RPMS)
 	if err != nil {
 		log.Error(err)
-		return nil, nil, err
+		return nil, nil, nil, err
 	}
 
-	return certProject, certImage, nil
+	testResults, err = p.createTestResults(ctx, testResults)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	return certProject, certImage, testResults, nil
 }
diff --git a/certification/pyxis/types.go b/certification/pyxis/types.go
index bba36b6..1af9513 100644
--- a/certification/pyxis/types.go
+++ b/certification/pyxis/types.go
@@ -1,18 +1,17 @@
 package pyxis
 
-//todo-adam reduce these fields to just what we need for now
 type CertImage struct {
-	Certified              bool          `json:"certified" default:"false"` //todo-adam I'm going to assume we want to set this to false??
-	Deleted                bool          `json:"deleted" default:"false"`
-	DockerImageDigest      string        `json:"docker_image_digest,omitempty"`
-	DockerImageID          string        `json:"docker_image_id,omitempty"`
-	ImageID                string        `json:"image_id,omitempty"`
-	ISVPID                 string        `json:"isv_pid,omitempty"`
-	ParsedData             *ParsedData   `json:"parsed_data,omitempty"`
-	RawConfig              string        `json:"raw_config,omitempty"`
-	Repositories           *Repositories `json:"repositories,omitempty"`
-	SumLayerSizeBytes      int32         `json:"sum_layer_size_bytes,omitempty"`
-	UncompressedTopLayerId string        `json:"uncompressed_top_layer_id,omitempty"`
+	Certified              bool         `json:"certified" default:"false"`
+	Deleted                bool         `json:"deleted" default:"false"`
+	DockerImageDigest      string       `json:"docker_image_digest,omitempty"`
+	DockerImageID          string       `json:"docker_image_id,omitempty"`
+	ImageID                string       `json:"image_id,omitempty"`
+	ISVPID                 string       `json:"isv_pid,omitempty"`
+	ParsedData             *ParsedData  `json:"parsed_data,omitempty"`
+	RawConfig              string       `json:"raw_config,omitempty"`
+	Repositories           []Repository `json:"repositories,omitempty"`
+	SumLayerSizeBytes      int64        `json:"sum_layer_size_bytes,omitempty"`
+	UncompressedTopLayerId string       `json:"uncompressed_top_layer_id,omitempty"` //TODO: figure out how to populate this, it is not required
 }
 
 type ParsedData struct {
@@ -20,24 +19,30 @@ type ParsedData struct {
 	Command                string  `json:"command,omitempty"`
 	Comment                string  `json:"comment,omitempty"`
 	Container              string  `json:"container,omitempty"`
+	Created                string  `json:"created,omitempty"`
 	DockerVersion          string  `json:"docker_version,omitempty"`
-	FilesNum               int32   `json:"files#"`
 	ImageID                string  `json:"image_id,omitempty"`
+	Labels                 []Label `json:"labels,omitempty"` // required
 	OS                     string  `json:"os,omitempty"`
 	Ports                  string  `json:"ports,omitempty"`
-	Size                   int32   `json:"size,omitempty"`
-	UncompressedLayerSizes []Layer `json:"uncompressed_layer_sizes,omitempty"`
+	Size                   int64   `json:"size,omitempty"`
+	UncompressedLayerSizes []Layer `json:"uncompressed_layer_sizes,omitempty"` //TODO: figure out how to populate this its required
 }
 
-type Repositories []struct {
+type Repository struct {
 	Published  bool   `json:"published" default:"false"`
 	PushDate   string `json:"push_date,omitempty"` // time.Now
 	Registry   string `json:"registry,omitempty"`
 	Repository string `json:"repository,omitempty"`
-	Tags       *Tags  `json:"tags,omitempty"`
+	Tags       []Tag  `json:"tags,omitempty"`
 }
 
-type Tags []struct {
+type Label struct {
+	Name  string `json:"name"`
+	Value string `json:"value"`
+}
+
+type Tag struct {
 	AddedDate string `json:"added_date,omitempty"` // time.Now
 	Name      string `json:"name,omitempty"`
 }
@@ -59,7 +64,6 @@ type RPM struct {
 	Version      string `json:"version,omitempty"`
 }
 
-//todo-adam make sure to set certProject.certification_status to "In Progress"
 type CertProject struct {
 	CertificationStatus string    `json:"certification_status" default:"In Progress"`
 	Container           Container `json:"container"`
@@ -76,5 +80,58 @@ type Container struct {
 
 type Layer struct {
 	LayerId string `json:"layer_id"`
-	Size    uint64 `json:"size_bytes"`
+	Size    int64  `json:"size_bytes"`
+}
+
+type TestResults struct {
+	CertProject       string      `json:"cert_project"`       // TODO: see if this should be populated, if so with what?
+	CertificationHash string      `json:"certification_hash"` // TODO: see if this should be populated, if so with what?
+	Image             string      `json:"image"`
+	OrgID             int         `json:"org_id"`
+	Passed            bool        `json:"passed"`
+	Results           Results     `json:"results"`
+	TestLibrary       TestLibrary `json:"test_library"`
+	Version           string      `json:"version"` // TODO: see if this should be populated, if so with what?
+}
+
+type Errors struct {
+	CheckURL         string `json:"check_url"`
+	Description      string `json:"description"`
+	ElapsedTime      int    `json:"elapsed_time"`
+	Help             string `json:"help"`
+	KnowledgeBaseURL string `json:"knowledgebase_url"`
+	Name             string `json:"name"`
+	Suggestion       string `json:"suggestion"`
+}
+
+type Failed struct {
+	CheckURL         string `json:"check_url"`
+	Description      string `json:"description"`
+	ElapsedTime      int    `json:"elapsed_time"`
+	Help             string `json:"help"`
+	KnowledgeBaseURL string `json:"knowledgebase_url"`
+	Name             string `json:"name"`
+	Suggestion       string `json:"suggestion"`
+}
+
+type Passed struct {
+	CheckURL         string `json:"check_url"`
+	Description      string `json:"description"`
+	ElapsedTime      int    `json:"elapsed_time"`
+	Help             string `json:"help"`
+	KnowledgeBaseURL string `json:"knowledgebase_url"`
+	Name             string `json:"name"`
+	Suggestion       string `json:"suggestion"`
+}
+
+type Results struct {
+	Errors []Errors `json:"errors"`
+	Failed []Failed `json:"failed"`
+	Passed []Passed `json:"passed"`
+}
+
+type TestLibrary struct {
+	Commit  string `json:"commit"`
+	Name    string `json:"name"`
+	Version string `json:"version"`
 }
diff --git a/cmd/check_container.go b/cmd/check_container.go
index b8477a1..0b06ba9 100644
--- a/cmd/check_container.go
+++ b/cmd/check_container.go
@@ -2,13 +2,17 @@ package cmd
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"io"
 	"net/http"
 	"os"
+	"path"
 	"path/filepath"
 	"strings"
+	"time"
 
+	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification/artifacts"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification/engine"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/certification/errors"
@@ -37,6 +41,19 @@ var checkContainerCmd = &cobra.Command{
 		if len(args) != 1 {
 			return fmt.Errorf("%w: A container image positional argument is required", errors.ErrInsufficientPosArguments)
 		}
+
+		if s, _ := cmd.Flags().GetBool("submit"); s {
+			if d, _ := cmd.Flags().GetString("docker-config"); len(d) == 0 {
+				return fmt.Errorf("%w: A docker configuration file must be present when calling --submit", errors.ErrNoDockerConfig)
+			}
+			if p, _ := cmd.Flags().GetString("pyxis-api-token"); len(p) == 0 {
+				return fmt.Errorf("%w: A Pyxis API token must be present when calling --submit", errors.ErrNoPyxisAPIKey)
+			}
+			if p, _ := cmd.Flags().GetString("certification-project-id"); len(p) == 0 {
+				return fmt.Errorf("%w: A Certification Project ID must be present when calling --submit", errors.ErrEmptyProjectID)
+			}
+		}
+
 		return nil
 	},
 	// this fmt.Sprintf is in place to keep spacing consistent with cobras two spaces that's used in: Usage, Flags, etc
@@ -65,14 +82,14 @@ var checkContainerCmd = &cobra.Command{
 		}
 		apiToken := viper.GetString("pyxis_api_token")
 		ctx := context.Background()
-		pyxisEngine := pyxis.NewPyxisEngine(apiToken, projectId, &http.Client{})
-		project, err := pyxisEngine.GetProject(ctx)
+		pyxisEngine := pyxis.NewPyxisEngine(apiToken, projectId, &http.Client{Timeout: 60 * time.Second})
+		certProject, err := pyxisEngine.GetProject(ctx)
 		if err != nil {
 			log.Error(err, "could not retrieve project")
 			return err
 		}
-		log.Debugf("Certification project name is: %s", project.Name)
-		if project.OsContentType == "scratch" {
+		log.Debugf("Certification project name is: %s", certProject.Name)
+		if certProject.OsContentType == "scratch" {
 			cfg.EnabledChecks = engine.ScratchContainerPolicy()
 		}
 
@@ -117,7 +134,7 @@ var checkContainerCmd = &cobra.Command{
 			return err
 		}
 
-		fmt.Fprint(resultsOutputTarget, string(formattedResults))
+		fmt.Fprintln(resultsOutputTarget, string(formattedResults))
 		if err := resultsFile.Close(); err != nil {
 			return err
 		}
@@ -126,15 +143,85 @@ var checkContainerCmd = &cobra.Command{
 			return err
 		}
 
+		// submitting results to pxysis if submit flag is set
+		if s, _ := cmd.Flags().GetBool("submit"); s {
+			log.Info("preparing results that will be submitted to Red Hat")
+
+			certImageJsonFile, err := os.Open(path.Join(artifacts.Path(), certification.DefaultCertImageFilename))
+			defer certImageJsonFile.Close()
+			if err != nil {
+				return err
+			}
+
+			certImageBytes, err := io.ReadAll(certImageJsonFile)
+			if err != nil {
+				return err
+			}
+
+			certImage := new(pyxis.CertImage)
+			err = json.Unmarshal(certImageBytes, &certImage)
+			if err != nil {
+				return err
+			}
+
+			rpmManifestJsonFile, err := os.Open(path.Join(artifacts.Path(), certification.DefaultRPMManifestFilename))
+			defer rpmManifestJsonFile.Close()
+			if err != nil {
+				return err
+			}
+
+			rpmManifestBytes, err := io.ReadAll(rpmManifestJsonFile)
+			if err != nil {
+				return err
+			}
+
+			rpmManifest := new(pyxis.RPMManifest)
+			err = json.Unmarshal(rpmManifestBytes, rpmManifest)
+			if err != nil {
+				return err
+			}
+
+			testResultsJsonFile, err := os.Open(path.Join(artifacts.Path(), certification.DefaultTestResultsFilename))
+			defer testResultsJsonFile.Close()
+			if err != nil {
+				return err
+			}
+
+			testResultsBytes, err := io.ReadAll(testResultsJsonFile)
+			if err != nil {
+				return err
+			}
+
+			var testResults = new(pyxis.TestResults)
+			err = json.Unmarshal(testResultsBytes, &testResults)
+			if err != nil {
+				return err
+			}
+
+			//TODO: use the return values once we know what we need to display to the user
+			_, _, _, err = pyxisEngine.SubmitResults(certProject, certImage, rpmManifest, testResults)
+			if err != nil {
+				return err
+			}
+		}
+
 		return nil
 	},
 }
 
 func init() {
+	checkContainerCmd.Flags().BoolP("submit", "s", false, "submit check container results to red hat")
+	viper.BindPFlag("submit", checkContainerCmd.Flags().Lookup("submit"))
+
+	checkContainerCmd.Flags().StringP("docker-config", "d", "", "path to docker config.json file")
+	viper.BindPFlag("docker_config", checkContainerCmd.Flags().Lookup("docker-config"))
+
 	checkContainerCmd.Flags().String("pyxis-api-token", "", "API token for Pyxis authentication")
 	viper.BindPFlag("pyxis_api_token", checkContainerCmd.Flags().Lookup("pyxis-api-token"))
+
 	checkContainerCmd.Flags().String("pyxis-host", DefaultPyxisHost, "Host to use for Pyxis submissions.")
 	viper.BindPFlag("pyxis_host", checkContainerCmd.Flags().Lookup("pyxis-host"))
+
 	checkContainerCmd.Flags().String("certification-project-id", "", "Certification Project ID from conenct.redhat.com. Should be supplied without the ospid- prefix.")
 	viper.BindPFlag("certification_project_id", checkContainerCmd.Flags().Lookup("certification-project-id"))
 
diff --git a/go.mod b/go.mod
index 2626026..624a338 100644
--- a/go.mod
+++ b/go.mod
@@ -43,7 +43,6 @@ require (
 	github.com/evanphx/json-patch v4.12.0+incompatible // indirect
 	github.com/fsnotify/fsnotify v1.5.1 // indirect
 	github.com/go-logr/logr v1.2.0 // indirect
-	github.com/go-restruct/restruct v0.0.0-20191227155143-5734170a48a1 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
@@ -103,3 +102,5 @@ require (
 	sigs.k8s.io/structured-merge-diff/v4 v4.2.0 // indirect
 	sigs.k8s.io/yaml v1.3.0 // indirect
 )
+
+replace github.com/knqyf263/go-rpmdb v0.0.0-20201215100354-a9e3110d8ee1 => github.com/opdev/go-rpmdb v0.0.0-20220208191623-d02daff2dffb
diff --git a/go.sum b/go.sum
index 6a25fc5..475f680 100644
--- a/go.sum
+++ b/go.sum
@@ -539,8 +539,6 @@ github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/
 github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
 github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
 github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
-github.com/go-restruct/restruct v0.0.0-20191227155143-5734170a48a1 h1:LoN2wx/aN8JPGebG+2DaUyk4M+xRcqJXfuIbs8AWHdE=
-github.com/go-restruct/restruct v0.0.0-20191227155143-5734170a48a1/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@@ -849,8 +847,6 @@ github.com/klauspost/compress v1.14.2 h1:S0OHlFk/Gbon/yauFJ4FfJJF5V0fc5HbBTJazi2
 github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
 github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
 github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
-github.com/knqyf263/go-rpmdb v0.0.0-20201215100354-a9e3110d8ee1 h1:sRDvjjWoHLWAxtPXBKYRJp8Ot4ugxYE/ZyADl3jzc1g=
-github.com/knqyf263/go-rpmdb v0.0.0-20201215100354-a9e3110d8ee1/go.mod h1:RDPNeIkU5NWXtt0OMEoILyxwUC/DyXeRtK295wpqSi0=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -1053,6 +1049,8 @@ github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAl
 github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
 github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
 github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
+github.com/opdev/go-rpmdb v0.0.0-20220208191623-d02daff2dffb h1:rBk/OmckLACN83JRCu0TUu7krK6kpoaxYmHvpTbzTcs=
+github.com/opdev/go-rpmdb v0.0.0-20220208191623-d02daff2dffb/go.mod h1:r1F7T+vFFyJzPDKZZKEDUVdVkDRMoxOplYIi8Dytp7U=
 github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
 github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
 github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=


Test patch:
diff --git a/certification/pyxis/pyxis_suite_test.go b/certification/pyxis/pyxis_suite_test.go
index 1b17591..cddec48 100644
--- a/certification/pyxis/pyxis_suite_test.go
+++ b/certification/pyxis/pyxis_suite_test.go
@@ -36,6 +36,8 @@ func (fhc fakeHttpClient) Do(req *http.Request) (*http.Response, error) {
 		}
 	case strings.Contains(req.URL.Path, "images"):
 		results = `{"certified":false,"deleted":false}`
+	case strings.Contains(req.URL.Path, "test-results"):
+		results = `{"image": "quay.io/awesome/image:latest", "passed": false,}`
 	}
 
 	return &http.Response{Body: io.NopCloser(bytes.NewReader([]byte(results)))}, nil
diff --git a/certification/pyxis/submit_test.go b/certification/pyxis/submit_test.go
index 6affa81..041b41e 100644
--- a/certification/pyxis/submit_test.go
+++ b/certification/pyxis/submit_test.go
@@ -16,12 +16,12 @@ var _ = Describe("Pyxis Submit", func() {
 	Context("when a project is submitted", func() {
 		Context("and it is not already In Progress", func() {
 			It("should switch to In Progress", func() {
-				certProject, certImage, err := pyxisEngine.SubmitResults(&CertProject{})
+				certProject, certImage, testResults, err := pyxisEngine.SubmitResults(&CertProject{}, &CertImage{}, &RPMManifest{}, &TestResults{})
 				Expect(err).ToNot(HaveOccurred())
 				Expect(certProject).ToNot(BeNil())
 				Expect(certImage).ToNot(BeNil())
+				Expect(testResults).ToNot(BeNil())
 			})
 		})
 	})
-
 })