Skip to content

Commit

Permalink
Merge pull request #556 from komish/add-cert-input-builder
Browse files Browse the repository at this point in the history
Convert submission logic to use a builder struct
  • Loading branch information
acornett21 authored Apr 12, 2022
2 parents d8771ef + cdb5f90 commit f111169
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 120 deletions.
222 changes: 222 additions & 0 deletions certification/pyxis/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package pyxis

import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path"

log "github.com/sirupsen/logrus"

"github.com/redhat-openshift-ecosystem/openshift-preflight/certification/errors"
)

// certificationInputBuilder facilitates the building of CertificationInput for
// submitting an asset to Pyxis.
type certificationInputBuilder struct {
certificationInput
}

// NewCertificationInput accepts required values for submitting to Pyxis, and returns a CertificationInputBuilder for
// adding additional files as artifacts to the submission. The caller must call Finalize() in order to receive
// a *CertificationInput.
func NewCertificationInput(project *CertProject) (*certificationInputBuilder, error) {
if project == nil {
return nil, fmt.Errorf("a certification project was not provided and is required")
}

b := certificationInputBuilder{
certificationInput: certificationInput{
CertProject: project,
},
}

return &b, nil
}

// Finalize runs a collection of safeguards to try to ensure we get a reliable
// CertificationInput. This also wires up information that's shared across
// the various included assets (e.g. ISVPID) where applicable, and returns an
// unmodifiable CertificationInput.
//
// If any required values are not included, an error is thrown.
func (b *certificationInputBuilder) Finalize() (*certificationInput, error) {
// safeguards, make sure things aren't nil for any reason.
if b.CertImage == nil {
return nil, fmt.Errorf("a CertImage was not provided and is required")
}
if b.TestResults == nil {
return nil, fmt.Errorf("test results were not provided and are required")
}

if b.RpmManifest == nil {
return nil, fmt.Errorf("the RPM manifest was not provided and is required")
}

if b.Artifacts == nil {
// we assume artifacts can be empty, but not nil.
b.Artifacts = []Artifact{}
}

// connect values from different components as necessary.
b.CertImage.ISVPID = b.CertProject.Container.ISVPID
b.CertImage.Certified = b.TestResults.Passed

return &b.certificationInput, nil
}

// WithCertImageFromFile adds a pyxis.CertImage from a file on disk to the CertificationInput.
// Errors are logged, but will not halt execution.
func (b *certificationInputBuilder) WithCertImageFromFile(filepath string) *certificationInputBuilder {
if err := b.storeCertImage(filepath); err != nil {
log.Error(err)
return b
}

return b
}

// WithPreflightResultsFromFile adds formatters.UserResponse from a file on disk to the CertificationInput.
// Errors are logged, but will not halt execution.
func (b *certificationInputBuilder) WithPreflightResultsFromFile(filepath string) *certificationInputBuilder {
if err := b.storePreflightResults(filepath); err != nil {
log.Error(err)
return b
}

return b
}

// WithPreflightResultsFromFile adds the pyxis.RPMManifest from a file on disk to the CertificationInput.
// Errors are logged, but will not halt execution.
func (b *certificationInputBuilder) WithRPMManifestFromFile(filepath string) *certificationInputBuilder {
if err := b.storeRPMManifest(filepath); err != nil {
log.Error(err)
return b
}

return b
}

// WithArtifactFromFile reads a file at path and binds it as an artifact to include
// in the submission. Multiple calls to this will append artifacts. Errors are logged,
// but will not halt execution.
func (b *certificationInputBuilder) WithArtifactFromFile(filepath string) *certificationInputBuilder {
file, err := os.Open(filepath)
if err != nil {
log.Error(err)
return b
}
defer file.Close()

bytes, err := io.ReadAll(file)
if err != nil {
log.Error(err)
return b
}

info, err := file.Stat()
if err != nil {
log.Error(err)
return b
}

newArtifact := Artifact{
CertProject: b.CertProject.ID,
Content: base64.StdEncoding.EncodeToString(bytes),
ContentType: http.DetectContentType(bytes),
Filename: path.Base(filepath),
FileSize: info.Size(),
}

b.Artifacts = append(b.Artifacts, newArtifact)

return b
}

// storeRPMManifest reads the manifest from disk at path and stores it in
// the CertificationInput as an RPMManifest struct.
func (b *certificationInputBuilder) storeRPMManifest(filepath string) error {
bytes, err := os.ReadFile(filepath)
if err != nil {
return fmt.Errorf(
"%w: unable to read file from disk to include in submission: %s: %s",
errors.ErrSubmittingToPyxis,
filepath,
err,
)
}

var manifest RPMManifest
err = json.Unmarshal(bytes, &manifest)
if err != nil {
return fmt.Errorf(
"%w: data for the %s appears to be malformed: %s",
errors.ErrSubmittingToPyxis,
"rpm manifest",
err,
)
}

b.RpmManifest = &manifest
return nil
}

// storePreflightResults reads the results from disk at path and stores it in
// the CertificationInput as TestResults.
func (b *certificationInputBuilder) storePreflightResults(filepath string) error {
bytes, err := os.ReadFile(filepath)
if err != nil {
return fmt.Errorf(
"%w: unable to read file from disk to include in submission: %s: %s",
errors.ErrSubmittingToPyxis,
filepath,
err,
)
}

var testResults TestResults
err = json.Unmarshal(bytes, &testResults)
if err != nil {
return fmt.Errorf(
"%w: data for the %s appears to be malformed: %s",
errors.ErrSubmittingToPyxis,
"preflight results.json",
err,
)
}

b.TestResults = &testResults
return nil
}

// storeCertImage reads the image from disk at path and stores it in
// the CertificationInput as a CertImage
func (b *certificationInputBuilder) storeCertImage(filepath string) error {
bytes, err := os.ReadFile(filepath)
if err != nil {
return fmt.Errorf(
"%w: unable to read file from disk to include in submission: %s: %s",
errors.ErrSubmittingToPyxis,
filepath,
err,
)
}

var image CertImage
err = json.Unmarshal(bytes, &image)
if err != nil {
return fmt.Errorf(
"%w: data for the %s appears to be malformed: %s",
errors.ErrSubmittingToPyxis,
"certImage",
err,
)
}

b.CertImage = &image
return nil
}
2 changes: 1 addition & 1 deletion certification/pyxis/submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

// SubmitResults takes certInput and sends requests to Pyxis to create or update entries
// based on certInput.
func (p *pyxisClient) SubmitResults(ctx context.Context, certInput *CertificationInput) (*CertificationResults, error) {
func (p *pyxisClient) SubmitResults(ctx context.Context, certInput *certificationInput) (*CertificationResults, error) {
var err error

certProject := certInput.CertProject
Expand Down
18 changes: 9 additions & 9 deletions certification/pyxis/submit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ var _ = Describe("Pyxis Submit", func() {
})
Context("and it is not already In Progress", func() {
It("should switch to In Progress", func() {
certResults, err := pyxisClient.SubmitResults(ctx, &CertificationInput{
certResults, err := pyxisClient.SubmitResults(ctx, &certificationInput{
CertProject: &CertProject{CertificationStatus: "Started"},
CertImage: &CertImage{
Repositories: []Repository{
Expand Down Expand Up @@ -56,7 +56,7 @@ var _ = Describe("Pyxis Submit", func() {
Context("when a project is submitted", func() {
Context("and the client sends a bad token", func() {
It("should get an unauthorized", func() {
certResults, err := pyxisClient.SubmitResults(ctx, &CertificationInput{
certResults, err := pyxisClient.SubmitResults(ctx, &certificationInput{
CertProject: &CertProject{CertificationStatus: "Started"},
CertImage: &CertImage{
Repositories: []Repository{
Expand Down Expand Up @@ -84,7 +84,7 @@ var _ = Describe("Pyxis Submit", func() {
Context("when a project is submitted", func() {
Context("and the image already exists", func() {
It("should get a conflict and handle it", func() {
certResults, err := pyxisClient.SubmitResults(ctx, &CertificationInput{
certResults, err := pyxisClient.SubmitResults(ctx, &certificationInput{
CertProject: &CertProject{},
CertImage: &CertImage{
Repositories: []Repository{
Expand Down Expand Up @@ -115,7 +115,7 @@ var _ = Describe("Pyxis Submit", func() {
Context("when a project is submitted", func() {
Context("and the api token is invalid", func() {
It("should get an unauthorized result", func() {
certResults, err := pyxisClient.SubmitResults(ctx, &CertificationInput{
certResults, err := pyxisClient.SubmitResults(ctx, &certificationInput{
CertProject: &CertProject{CertificationStatus: "Started"},
CertImage: &CertImage{
Repositories: []Repository{
Expand Down Expand Up @@ -143,7 +143,7 @@ var _ = Describe("Pyxis Submit", func() {
Context("when a project is submitted", func() {
Context("and a bad token is sent to getImage and createImage is in conflict", func() {
It("should error", func() {
certResults, err := pyxisClient.SubmitResults(ctx, &CertificationInput{
certResults, err := pyxisClient.SubmitResults(ctx, &certificationInput{
CertProject: &CertProject{CertificationStatus: "Started"},
CertImage: &CertImage{
Repositories: []Repository{
Expand Down Expand Up @@ -171,7 +171,7 @@ var _ = Describe("Pyxis Submit", func() {
Context("when a project is submitted", func() {
Context("and the RPM manifest already exists", func() {
It("should retry and return success", func() {
certResults, err := pyxisClient.SubmitResults(ctx, &CertificationInput{
certResults, err := pyxisClient.SubmitResults(ctx, &certificationInput{
CertProject: &CertProject{},
CertImage: &CertImage{
Repositories: []Repository{
Expand Down Expand Up @@ -202,7 +202,7 @@ var _ = Describe("Pyxis Submit", func() {
Context("when a project is submitted", func() {
Context("and a bad token is sent to createRPMManifest", func() {
It("should error", func() {
certResults, err := pyxisClient.SubmitResults(ctx, &CertificationInput{
certResults, err := pyxisClient.SubmitResults(ctx, &certificationInput{
CertProject: &CertProject{CertificationStatus: "Started"},
CertImage: &CertImage{
Repositories: []Repository{
Expand Down Expand Up @@ -230,7 +230,7 @@ var _ = Describe("Pyxis Submit", func() {
Context("when a project is submitted", func() {
Context("and a bad token is sent to getRPMManifest and createRPMManifest is in conflict", func() {
It("should error", func() {
certResults, err := pyxisClient.SubmitResults(ctx, &CertificationInput{
certResults, err := pyxisClient.SubmitResults(ctx, &certificationInput{
CertProject: &CertProject{CertificationStatus: "Started"},
CertImage: &CertImage{
Repositories: []Repository{
Expand Down Expand Up @@ -258,7 +258,7 @@ var _ = Describe("Pyxis Submit", func() {
Context("when a project is submitted", func() {
Context("and a bad api token is sent to createTestResults", func() {
It("should error", func() {
certResults, err := pyxisClient.SubmitResults(ctx, &CertificationInput{
certResults, err := pyxisClient.SubmitResults(ctx, &certificationInput{
CertProject: &CertProject{CertificationStatus: "Started"},
CertImage: &CertImage{
Repositories: []Repository{
Expand Down
2 changes: 1 addition & 1 deletion certification/pyxis/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"github.com/redhat-openshift-ecosystem/openshift-preflight/certification/formatters"
)

type CertificationInput struct {
type certificationInput struct {
CertProject *CertProject
CertImage *CertImage
TestResults *TestResults
Expand Down
Loading

0 comments on commit f111169

Please sign in to comment.