diff --git a/certification/runtime/config.go b/certification/runtime/config.go index 9dc7f18f2..5fb6177e9 100644 --- a/certification/runtime/config.go +++ b/certification/runtime/config.go @@ -56,6 +56,28 @@ func NewConfigFrom(vcfg viper.Viper) (*Config, error) { return &cfg, nil } +func NewManualContainerConfig(image, responseFormat, artifactsDir string, submit, writeJUnit bool) *Config { + return &Config{ + Image: image, + Submit: submit, + WriteJUnit: writeJUnit, + ResponseFormat: responseFormat, + Artifacts: artifactsDir, + } +} + +func NewManualOperatorConfig(image, responseFormat, artifactsDir string, writeJUnit bool) *Config { + return &Config{ + Image: image, + Submit: false, // operator results are not submitted + WriteJUnit: writeJUnit, + ResponseFormat: responseFormat, + Artifacts: artifactsDir, + Bundle: false, + Scratch: false, + } +} + // storeContainerPolicyConfiguration reads container-policy-specific config // items in viper, normalizes them, and stores them in Config. func (c *Config) storeContainerPolicyConfiguration(vcfg viper.Viper) { diff --git a/cmd/check.go b/cmd/check.go index 68d30e98a..ad9283b51 100644 --- a/cmd/check.go +++ b/cmd/check.go @@ -3,7 +3,6 @@ package cmd import ( "bytes" "context" - "fmt" "strings" "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/artifacts" @@ -63,25 +62,6 @@ func resultsFilenameWithExtension(ext string) string { return strings.Join([]string{"results", ext}, ".") } -func buildConnectURL(projectID string) string { - connectURL := fmt.Sprintf("https://connect.redhat.com/projects/%s", projectID) - - pyxisEnv := viper.GetString("pyxis_env") - if len(pyxisEnv) > 0 && pyxisEnv != "prod" { - connectURL = fmt.Sprintf("https://connect.%s.redhat.com/projects/%s", viper.GetString("pyxis_env"), projectID) - } - - return connectURL -} - -func buildOverviewURL(projectID string) string { - return fmt.Sprintf("%s/overview", buildConnectURL(projectID)) -} - -func buildScanResultsURL(projectID string, imageID string) string { - return fmt.Sprintf("%s/images/%s/scan-results", buildConnectURL(projectID), imageID) -} - func convertPassedOverall(passedOverall bool) string { if passedOverall { return "PASSED" diff --git a/cmd/check_container.go b/cmd/check_container.go index 8e0a21801..e9bd995f0 100644 --- a/cmd/check_container.go +++ b/cmd/check_container.go @@ -1,15 +1,13 @@ package cmd import ( - "context" "fmt" "strings" "github.com/redhat-openshift-ecosystem/openshift-preflight/certification" - "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/engine" "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/formatters" - "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/policy" "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/runtime" + "github.com/redhat-openshift-ecosystem/openshift-preflight/lib" "github.com/redhat-openshift-ecosystem/openshift-preflight/version" log "github.com/sirupsen/logrus" @@ -52,53 +50,6 @@ func checkContainerCmd() *cobra.Command { return checkContainerCmd } -// checkContainerRunner contains all of the components necessary to run checkContainer. -type checkContainerRunner struct { - cfg *runtime.Config - pc pyxisClient - eng engine.CheckEngine - formatter formatters.ResponseFormatter - rw resultWriter - rs resultSubmitter -} - -func newCheckContainerRunner(ctx context.Context, cfg *runtime.Config) (*checkContainerRunner, error) { - cfg.Policy = policy.PolicyContainer - cfg.Submit = submit - - pyxisClient := newPyxisClient(ctx, cfg.ReadOnly()) - // If we have a pyxisClient, we can query for container policy exceptions. - if pyxisClient != nil { - policy, err := getContainerPolicyExceptions(ctx, pyxisClient) - if err != nil { - return nil, err - } - - cfg.Policy = policy - } - - engine, err := engine.NewForConfig(ctx, cfg.ReadOnly()) - if err != nil { - return nil, err - } - - fmttr, err := formatters.NewForConfig(cfg.ReadOnly()) - if err != nil { - return nil, err - } - - rs := resolveSubmitter(pyxisClient, cfg.ReadOnly()) - - return &checkContainerRunner{ - cfg: cfg, - pc: pyxisClient, - eng: engine, - formatter: fmttr, - rw: &runtime.ResultWriterFile{}, - rs: rs, - }, nil -} - // checkContainerRunE executes checkContainer using the user args to inform the execution. func checkContainerRunE(cmd *cobra.Command, args []string) error { log.Info("certification library version ", version.Version.String()) @@ -114,62 +65,23 @@ func checkContainerRunE(cmd *cobra.Command, args []string) error { cfg.Image = containerImage cfg.ResponseFormat = formatters.DefaultFormat - checkContainer, err := newCheckContainerRunner(ctx, cfg) + checkContainer, err := lib.NewCheckContainerRunner(ctx, cfg, submit) if err != nil { return err } // Run the container check. cmd.SilenceUsage = true - return preflightCheck(ctx, - checkContainer.cfg, - checkContainer.pc, - checkContainer.eng, - checkContainer.formatter, - checkContainer.rw, - checkContainer.rs, + return lib.PreflightCheck(ctx, + checkContainer.Cfg, + checkContainer.Pc, + checkContainer.Eng, + checkContainer.Formatter, + checkContainer.Rw, + checkContainer.Rs, ) } -// resolveSubmitter will build out a resultSubmitter if the provided pyxisClient, pc, is not nil. -// The pyxisClient is a required component of the submitter. If pc is nil, then a noop submitter -// is returned instead, which does nothing. -func resolveSubmitter(pc pyxisClient, cfg certification.Config) resultSubmitter { - if pc != nil { - return &containerCertificationSubmitter{ - certificationProjectID: cfg.CertificationProjectID(), - pyxis: pc, - dockerConfig: cfg.DockerConfig(), - preflightLogFile: cfg.LogFile(), - } - } - - return &noopSubmitter{emitLog: true} -} - -// getContainerPolicyExceptions will query Pyxis to determine if -// a given project has a certification excemptions, such as root or scratch. -// This will then return the corresponding policy. -// -// If no policy exception flags are found on the project, the standard -// container policy is returned. -func getContainerPolicyExceptions(ctx context.Context, pc pyxisClient) (policy.Policy, error) { - certProject, err := pc.GetProject(ctx) - if err != nil { - return "", fmt.Errorf("could not retrieve project: %w", err) - } - log.Debugf("Certification project name is: %s", certProject.Name) - if certProject.Container.Type == "scratch" { - return policy.PolicyScratch, nil - } - - // if a partner sets `Host Level Access` in connect to `Privileged`, enable RootExceptionContainerPolicy checks - if certProject.Container.Privileged { - return policy.PolicyRoot, nil - } - return policy.PolicyContainer, nil -} - func checkContainerPositionalArgs(cmd *cobra.Command, args []string) error { if len(args) != 1 { return fmt.Errorf("a container image positional argument is required") diff --git a/cmd/check_container_cmd_test.go b/cmd/check_container_cmd_test.go new file mode 100644 index 000000000..e843f0a94 --- /dev/null +++ b/cmd/check_container_cmd_test.go @@ -0,0 +1,163 @@ +package cmd + +import ( + "bytes" + "context" + "os" + "path/filepath" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/runtime" + "github.com/redhat-openshift-ecosystem/openshift-preflight/lib" + "github.com/spf13/viper" +) + +var _ = Describe("Check Container Command", func() { + BeforeEach(createAndCleanupDirForArtifactsAndLogs) + + Context("when running the check container subcommand", func() { + Context("With all of the required parameters", func() { + It("should reach the core logic, but throw an error because of the placeholder values for the container image", func() { + _, err := executeCommand(checkContainerCmd(), "example.com/example/image:mytag") + Expect(err).To(HaveOccurred()) + }) + }) + }) + + Context("When validating check container arguments and flags", func() { + Context("and the user provided more than 1 positional arg", func() { + It("should fail to run", func() { + _, err := executeCommand(checkContainerCmd(), "foo", "bar") + Expect(err).To(HaveOccurred()) + }) + }) + + Context("and the user provided less than 1 positional arg", func() { + It("should fail to run", func() { + _, err := executeCommand(checkContainerCmd()) + Expect(err).To(HaveOccurred()) + }) + }) + + DescribeTable("and the user has enabled the submit flag", + func(errString string, args []string) { + out, err := executeCommand(checkContainerCmd(), args...) + Expect(err).To(HaveOccurred()) + Expect(out).To(ContainSubstring(errString)) + }, + Entry("certification-project-id and pyxis-api-token are not supplied", "certification Project ID must be specified when --submit is present", []string{"--submit", "foo"}), + Entry("pyxis-api-token is not supplied", "pyxis API Token must be specified when --submit is present", []string{"foo", "--submit", "--certification-project-id=fooid"}), + Entry("certification-project-id is not supplied", "certification Project ID must be specified when --submit is present", []string{"--submit", "foo", "--pyxis-api-token=footoken"}), + Entry("pyxis-api-token flag is present but empty because of '='", "cannot be empty when --submit is present", []string{"foo", "--submit", "--certification-project-id=fooid", "--pyxis-api-token="}), + Entry("certification-project-id flag is present but empty because of '='", "cannot be empty when --submit is present", []string{"foo", "--submit", "--certification-project-id=", "--pyxis-api-token=footoken"}), + Entry("submit is passed after empty api token", "pyxis API token and certification ID are required when --submit is present", []string{"foo", "--certification-project-id=fooid", "--pyxis-api-token", "--submit"}), + Entry("submit is passed with explicit value after empty api token", "pyxis API token and certification ID are required when --submit is present", []string{"foo", "--certification-project-id=fooid", "--pyxis-api-token", "--submit=true"}), + ) + + When("the user enables the submit flag", func() { + When("environment variables are used for certification ID and api token", func() { + BeforeEach(func() { + os.Setenv("PFLT_CERTIFICATION_PROJECT_ID", "certid") + os.Setenv("PFLT_PYXIS_API_TOKEN", "tokenid") + DeferCleanup(os.Unsetenv, "PFLT_CERTIFICATION_PROJECT_ID") + DeferCleanup(os.Unsetenv, "PFLT_PYXIS_API_TOKEN") + }) + It("should still execute with no error", func() { + submit = true + + err := checkContainerPositionalArgs(checkContainerCmd(), []string{"foo"}) + Expect(err).ToNot(HaveOccurred()) + Expect(viper.GetString("pyxis_api_token")).To(Equal("tokenid")) + Expect(viper.GetString("certification_project_id")).To(Equal("certid")) + }) + }) + When("a config file is used", func() { + BeforeEach(func() { + config := `pyxis_api_token: mytoken +certification_project_id: mycertid` + tempDir, err := os.MkdirTemp("", "check-container-submit-*") + Expect(err).ToNot(HaveOccurred()) + err = os.WriteFile(filepath.Join(tempDir, "config.yaml"), bytes.NewBufferString(config).Bytes(), 0o644) + Expect(err).ToNot(HaveOccurred()) + viper.AddConfigPath(tempDir) + DeferCleanup(os.RemoveAll, tempDir) + }) + It("should still execute with no error", func() { + // Make sure that we've read the config file + initConfig() + submit = true + + err := checkContainerPositionalArgs(checkContainerCmd(), []string{"foo"}) + Expect(err).ToNot(HaveOccurred()) + Expect(viper.GetString("pyxis_api_token")).To(Equal("mytoken")) + Expect(viper.GetString("certification_project_id")).To(Equal("mycertid")) + }) + }) + }) + }) + + Context("When validating the certification-project-id flag", func() { + Context("and the flag is set properly", func() { + BeforeEach(func() { + viper.Set("certification_project_id", "123456789") + }) + It("should not change the flag value", func() { + err := validateCertificationProjectID(checkContainerCmd(), []string{"foo"}) + Expect(err).ToNot(HaveOccurred()) + Expect(viper.GetString("certification_project_id")).To(Equal("123456789")) + }) + }) + Context("and a valid ospid format is provided", func() { + BeforeEach(func() { + viper.Set("certification_project_id", "ospid-123456789") + }) + It("should strip ospid- from the flag value", func() { + err := validateCertificationProjectID(checkContainerCmd(), []string{"foo"}) + Expect(err).ToNot(HaveOccurred()) + Expect(viper.GetString("certification_project_id")).To(Equal("123456789")) + }) + }) + Context("and a legacy format with ospid is provided", func() { + BeforeEach(func() { + viper.Set("certification_project_id", "ospid-62423-f26c346-6cc1dc7fae92") + }) + It("should throw an error", func() { + err := validateCertificationProjectID(checkContainerCmd(), []string{"foo"}) + Expect(err).To(HaveOccurred()) + }) + }) + Context("and a legacy format without ospid is provided", func() { + BeforeEach(func() { + viper.Set("certification_project_id", "62423-f26c346-6cc1dc7fae92") + }) + It("should throw an error", func() { + err := validateCertificationProjectID(checkContainerCmd(), []string{"foo"}) + Expect(err).To(HaveOccurred()) + }) + }) + }) + + Context("When instantiating a checkContainerRunner", func() { + var cfg *runtime.Config + + Context("and the user did not pass the submit flag", func() { + var origSubmitValue bool + BeforeEach(func() { + origSubmitValue = submit + submit = false + }) + + AfterEach(func() { + submit = origSubmitValue + }) + It("should return a noopSubmitter resultSubmitter", func() { + runner, err := lib.NewCheckContainerRunner(context.TODO(), cfg, false) + Expect(err).ToNot(HaveOccurred()) + _, rsIsCorrectType := runner.Rs.(*lib.NoopSubmitter) + Expect(rsIsCorrectType).To(BeTrue()) + }) + }) + + }) +}) diff --git a/cmd/check_operator.go b/cmd/check_operator.go index 6199720eb..9a6bf3883 100644 --- a/cmd/check_operator.go +++ b/cmd/check_operator.go @@ -1,14 +1,12 @@ package cmd import ( - "context" "fmt" "os" - "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/engine" "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/formatters" - "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/policy" "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/runtime" + "github.com/redhat-openshift-ecosystem/openshift-preflight/lib" "github.com/redhat-openshift-ecosystem/openshift-preflight/version" log "github.com/sirupsen/logrus" @@ -48,38 +46,6 @@ func checkOperatorCmd() *cobra.Command { return checkOperatorCmd } -// checkOperatorRunner contains all of the components necessary to run checkOperator. -type checkOperatorRunner struct { - cfg *runtime.Config - eng engine.CheckEngine - formatter formatters.ResponseFormatter - rw resultWriter -} - -// newCheckOperatorRunner returns a checkOperatorRunner containing all of the tooling necessary -// to run checkOperator. -func newCheckOperatorRunner(ctx context.Context, cfg *runtime.Config) (*checkOperatorRunner, error) { - cfg.Policy = policy.PolicyOperator - cfg.Submit = false // there's no such thing as submitting for operators today. - - engine, err := engine.NewForConfig(ctx, cfg.ReadOnly()) - if err != nil { - return nil, err - } - - fmttr, err := formatters.NewForConfig(cfg.ReadOnly()) - if err != nil { - return nil, err - } - - return &checkOperatorRunner{ - cfg: cfg, - eng: engine, - formatter: fmttr, - rw: &runtime.ResultWriterFile{}, - }, nil -} - // ensureKubeconfigIsSet ensures that the KUBECONFIG environment variable has a value. func ensureKubeconfigIsSet() error { if _, ok := os.LookupEnv("KUBECONFIG"); !ok { @@ -116,20 +82,20 @@ func checkOperatorRunE(cmd *cobra.Command, args []string) error { cfg.Bundle = true cfg.Scratch = true - checkOperator, err := newCheckOperatorRunner(ctx, cfg) + checkOperator, err := lib.NewCheckOperatorRunner(ctx, cfg) if err != nil { return err } // Run the operator check cmd.SilenceUsage = true - return preflightCheck(ctx, - checkOperator.cfg, + return lib.PreflightCheck(ctx, + checkOperator.Cfg, nil, // no pyxisClient is necessary - checkOperator.eng, - checkOperator.formatter, - checkOperator.rw, - &noopSubmitter{}, // we do not submit these results. + checkOperator.Eng, + checkOperator.Formatter, + checkOperator.Rw, + &lib.NoopSubmitter{}, // we do not submit these results. ) } diff --git a/cmd/check_operator_test.go b/cmd/check_operator_test.go index a4844d7e0..94573572b 100644 --- a/cmd/check_operator_test.go +++ b/cmd/check_operator_test.go @@ -8,6 +8,7 @@ import ( "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/formatters" "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/policy" "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/runtime" + "github.com/redhat-openshift-ecosystem/openshift-preflight/lib" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -128,18 +129,18 @@ var _ = Describe("Check Operator", func() { Context("with a valid policy formatter", func() { It("should return with no error, and the appropriate formatter", func() { cfg.ResponseFormat = "xml" - runner, err := newCheckOperatorRunner(context.TODO(), cfg) + runner, err := lib.NewCheckOperatorRunner(context.TODO(), cfg) Expect(err).ToNot(HaveOccurred()) expectedFormatter, err := formatters.NewByName(cfg.ResponseFormat) Expect(err).ToNot(HaveOccurred()) - Expect(runner.formatter.PrettyName()).To(Equal(expectedFormatter.PrettyName())) + Expect(runner.Formatter.PrettyName()).To(Equal(expectedFormatter.PrettyName())) }) }) Context("with an invalid policy formatter", func() { It("should return an error", func() { cfg.ResponseFormat = "foo" - _, err := newCheckOperatorRunner(context.TODO(), cfg) + _, err := lib.NewCheckOperatorRunner(context.TODO(), cfg) Expect(err).To(HaveOccurred()) }) }) @@ -148,12 +149,12 @@ var _ = Describe("Check Operator", func() { It("should return the container policy engine anyway", func() { cfg.Policy = "badpolicy" beforeCfg := *cfg - runner, err := newCheckOperatorRunner(context.TODO(), cfg) + runner, err := lib.NewCheckOperatorRunner(context.TODO(), cfg) Expect(err).ToNot(HaveOccurred()) _, err = engine.NewForConfig(context.TODO(), cfg.ReadOnly()) - Expect(runner.cfg.Policy).ToNot(Equal(beforeCfg.Policy)) - Expect(runner.cfg.Policy).To(Equal(policy.PolicyOperator)) + Expect(runner.Cfg.Policy).ToNot(Equal(beforeCfg.Policy)) + Expect(runner.Cfg.Policy).To(Equal(policy.PolicyOperator)) Expect(err).ToNot(HaveOccurred()) }) }) @@ -161,15 +162,15 @@ var _ = Describe("Check Operator", func() { Context("with an invalid formatter definition", func() { It("should return an error", func() { cfg.ResponseFormat = "foo" - _, err := newCheckOperatorRunner(context.TODO(), cfg) + _, err := lib.NewCheckOperatorRunner(context.TODO(), cfg) Expect(err).To(HaveOccurred()) }) }) It("should contain a ResultWriterFile resultWriter", func() { - runner, err := newCheckOperatorRunner(context.TODO(), cfg) + runner, err := lib.NewCheckOperatorRunner(context.TODO(), cfg) Expect(err).ToNot(HaveOccurred()) - _, rwIsExpectedType := runner.rw.(*runtime.ResultWriterFile) + _, rwIsExpectedType := runner.Rw.(*runtime.ResultWriterFile) Expect(rwIsExpectedType).To(BeTrue()) }) }) diff --git a/cmd/check_test.go b/cmd/check_test.go index 3422ff056..85a28a9af 100644 --- a/cmd/check_test.go +++ b/cmd/check_test.go @@ -7,6 +7,7 @@ import ( "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/artifacts" "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/runtime" + "github.com/redhat-openshift-ecosystem/openshift-preflight/lib" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -43,30 +44,13 @@ var _ = Describe("cmd package check command", func() { os.Unsetenv("PFLT_PYXIS_ENV") os.Unsetenv("PFLT_PYXIS_HOST") }) - Context("Regular Connect URL", func() { - It("should return a URL with just a project ID", func() { - expected := "https://connect.redhat.com/projects/this-is-my-project-id" - actual := buildConnectURL(projectID) - Expect(expected).To(Equal(actual)) - }) - }) - Context("QA Connect URL", func() { - BeforeEach(func() { - os.Setenv("PFLT_PYXIS_ENV", "qa") - }) - It("should return a URL for QA", func() { - expected := "https://connect.qa.redhat.com/projects/this-is-my-project-id" - actual := buildConnectURL(projectID) - Expect(expected).To(Equal(actual)) - }) - }) Context("UAT Scan Results URL", func() { BeforeEach(func() { os.Setenv("PFLT_PYXIS_ENV", "uat") }) It("should return a URL for UAT", func() { expected := "https://connect.uat.redhat.com/projects/this-is-my-project-id/images/my-image-id/scan-results" - actual := buildScanResultsURL(projectID, imageID) + actual := lib.BuildScanResultsURL(projectID, imageID) Expect(expected).To(Equal(actual)) }) }) @@ -76,7 +60,7 @@ var _ = Describe("cmd package check command", func() { }) It("should return a URL for QA", func() { expected := "https://connect.qa.redhat.com/projects/this-is-my-project-id/overview" - actual := buildOverviewURL(projectID) + actual := lib.BuildOverviewURL(projectID) Expect(expected).To(Equal(actual)) }) }) @@ -86,12 +70,12 @@ var _ = Describe("cmd package check command", func() { }) It("should return a Prod overview URL", func() { expected := "https://connect.redhat.com/projects/this-is-my-project-id/overview" - actual := buildOverviewURL(projectID) + actual := lib.BuildOverviewURL(projectID) Expect(expected).To(Equal(actual)) }) It("should return a Prod scan URL", func() { expected := "https://connect.redhat.com/projects/this-is-my-project-id/images/my-image-id/scan-results" - actual := buildScanResultsURL(projectID, imageID) + actual := lib.BuildScanResultsURL(projectID, imageID) Expect(expected).To(Equal(actual)) }) }) diff --git a/cmd/preflight_check.go b/cmd/preflight_check.go deleted file mode 100644 index acdd4af8c..000000000 --- a/cmd/preflight_check.go +++ /dev/null @@ -1,76 +0,0 @@ -package cmd - -import ( - "context" - "fmt" - "io" - "os" - "strings" - - "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/formatters" - "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/runtime" - - log "github.com/sirupsen/logrus" -) - -// preflightCheck executes checks, interacts with pyxis, format output, writes, and submits results. -func preflightCheck( - ctx context.Context, - cfg *runtime.Config, - pc pyxisClient, //nolint:unparam // pyxisClient is currently unused. - eng engine.CheckEngine, - formatter formatters.ResponseFormatter, - rw resultWriter, - rs resultSubmitter, -) error { - // configure the artifacts directory if the user requested a different directory. - if cfg.Artifacts != "" { - artifacts.SetDir(cfg.Artifacts) - } - - // create the results file early to catch cases where we are not - // able to write to the filesystem before we attempt to execute checks. - resultsFilePath, err := artifacts.WriteFile(resultsFilenameWithExtension(formatter.FileExtension()), strings.NewReader("")) - if err != nil { - return err - } - resultsFile, err := rw.OpenFile(resultsFilePath) - if err != nil { - return err - } - defer resultsFile.Close() - - resultsOutputTarget := io.MultiWriter(os.Stdout, resultsFile) - - // execute the checks - if err := eng.ExecuteChecks(ctx); err != nil { - return err - } - results := eng.Results(ctx) - - // return results to the user and then close output files - formattedResults, err := formatter.Format(ctx, results) - if err != nil { - return err - } - - fmt.Fprintln(resultsOutputTarget, string(formattedResults)) - - if cfg.WriteJUnit { - if err := writeJUnit(ctx, results); err != nil { - return err - } - } - - if cfg.Submit { - if err := rs.Submit(ctx); err != nil { - return err - } - } - - log.Infof("Preflight result: %s", convertPassedOverall(results.PassedOverall)) - - return nil -} diff --git a/go.mod b/go.mod index 62a1ff55e..aaa1dae81 100644 --- a/go.mod +++ b/go.mod @@ -28,8 +28,6 @@ require ( ) require ( - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20220418222510-f25a4f6275ed // indirect github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -47,8 +45,8 @@ require ( github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.5 // indirect - github.com/go-openapi/swag v0.19.14 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/swag v0.22.3 // 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 @@ -65,9 +63,9 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.15.11 // indirect github.com/magiconair/properties v1.8.6 // indirect - github.com/mailru/easyjson v0.7.6 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-sqlite3 v1.14.12 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -81,8 +79,8 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.12.2 // indirect github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.32.1 // indirect - github.com/prometheus/procfs v0.7.3 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect diff --git a/go.sum b/go.sum index 517e27de3..b32b106da 100644 --- a/go.sum +++ b/go.sum @@ -46,9 +46,7 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -136,9 +134,11 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2 github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= @@ -152,14 +152,14 @@ github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUe github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -290,13 +290,13 @@ github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPK github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM= +github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -360,6 +360,7 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.12.2 h1:51L9cDoUHVrXx4zWYlcLQIZ+d+VXHgqnYKkIuq4g/34= github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -370,14 +371,16 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= +github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= +github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/redhat-openshift-ecosystem/ocp-olm-catalog-validator v0.1.0 h1:4EHstmf5aq2aalaQwb4Fila8GJeDd+O3A6ASC70fQio= github.com/redhat-openshift-ecosystem/ocp-olm-catalog-validator v0.1.0/go.mod h1:GfYbSrt58GvB8zQlTIaSAMx7d9uAJGnXLDggRqw7C3s= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -549,6 +552,8 @@ golang.org/x/net v0.0.0-20210224082022-3d97a244fca7/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -561,6 +566,7 @@ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -623,6 +629,7 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -631,6 +638,7 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -641,6 +649,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/cmd/check_container_test.go b/lib/check_container_test.go similarity index 59% rename from cmd/check_container_test.go rename to lib/check_container_test.go index b35d5560d..0fbfa2cd7 100644 --- a/cmd/check_container_test.go +++ b/lib/check_container_test.go @@ -1,4 +1,4 @@ -package cmd +package lib import ( "bytes" @@ -6,7 +6,6 @@ import ( "encoding/json" "os" "path" - "path/filepath" "strings" "github.com/redhat-openshift-ecosystem/openshift-preflight/certification" @@ -21,27 +20,17 @@ import ( . "github.com/onsi/gomega" "github.com/sirupsen/logrus" "github.com/spf13/afero" - "github.com/spf13/viper" ) var _ = Describe("Check Container Command", func() { - BeforeEach(createAndCleanupDirForArtifactsAndLogs) - - Context("when running the check container subcommand", func() { - Context("With all of the required parameters", func() { - It("should reach the core logic, but throw an error because of the placeholder values for the container image", func() { - _, err := executeCommand(checkContainerCmd(), "example.com/example/image:mytag") - Expect(err).To(HaveOccurred()) - }) - }) - }) + // BeforeEach(createAndCleanupDirForArtifactsAndLogs) Context("When determining container policy exceptions", func() { - var fakePC *fakePyxisClient + var fakePC *FakePyxisClient BeforeEach(func() { // reset the fake pyxis client before each execution // as a precaution. - fakePC = &fakePyxisClient{ + fakePC = &FakePyxisClient{ findImagesByDigestFunc: fidbFuncNoop, getProjectsFunc: gpFuncNoop, submitResultsFunc: srFuncNoop, @@ -50,35 +39,35 @@ var _ = Describe("Check Container Command", func() { It("should throw an error if unable to get the project from the API", func() { fakePC.getProjectsFunc = gpFuncReturnError - _, err := getContainerPolicyExceptions(context.TODO(), fakePC) + _, err := GetContainerPolicyExceptions(context.TODO(), fakePC) Expect(err).To(HaveOccurred()) }) It("should return a scratch policy exception if the project has the flag in the API", func() { fakePC.getProjectsFunc = gpFuncReturnScratchException - p, err := getContainerPolicyExceptions(context.TODO(), fakePC) + p, err := GetContainerPolicyExceptions(context.TODO(), fakePC) Expect(p).To(Equal(policy.PolicyScratch)) Expect(err).ToNot(HaveOccurred()) }) It("should return a root policy exception if the project has the flag in the API", func() { fakePC.getProjectsFunc = gpFuncReturnRootException - p, err := getContainerPolicyExceptions(context.TODO(), fakePC) + p, err := GetContainerPolicyExceptions(context.TODO(), fakePC) Expect(p).To(Equal(policy.PolicyRoot)) Expect(err).ToNot(HaveOccurred()) }) It("should return a container policy exception if the project no exceptions in the API", func() { fakePC.getProjectsFunc = gpFuncReturnNoException - p, err := getContainerPolicyExceptions(context.TODO(), fakePC) + p, err := GetContainerPolicyExceptions(context.TODO(), fakePC) Expect(p).To(Equal(policy.PolicyContainer)) Expect(err).ToNot(HaveOccurred()) }) }) Context("When using the containerCertificationSubmitter", func() { - var sbmt *containerCertificationSubmitter - var fakePC *fakePyxisClient + var sbmt *ContainerCertificationSubmitter + var fakePC *FakePyxisClient var dockerConfigPath string var preflightLogPath string @@ -87,23 +76,19 @@ var _ = Describe("Check Container Command", func() { BeforeEach(func() { dockerConfigPath = path.Join(artifacts.Path(), dockerconfigFilename) preflightLogPath = path.Join(artifacts.Path(), preflightLogFilename) - // Normalize a fakePyxisClient with noop functions. - fakePC = &fakePyxisClient{ - findImagesByDigestFunc: fidbFuncNoop, - getProjectsFunc: gpFuncNoop, - submitResultsFunc: srFuncNoop, - } + // Normalize a FakePyxisClient with noop functions. + fakePC = NewFakePyxisClientNoop() // Most tests will need a passing getProjects func so set that to // avoid having to perform multiple BeforeEaches fakePC.setGPFuncReturnBaseProject("") // configure the submitter - sbmt = &containerCertificationSubmitter{ - certificationProjectID: fakePC.baseProject("").ID, - pyxis: fakePC, - dockerConfig: dockerConfigPath, - preflightLogFile: preflightLogPath, + sbmt = &ContainerCertificationSubmitter{ + CertificationProjectID: fakePC.baseProject("").ID, + Pyxis: fakePC, + DockerConfig: dockerConfigPath, + PreflightLogFile: preflightLogPath, } certImageJSONBytes, err := json.Marshal(pyxis.CertImage{ @@ -159,7 +144,7 @@ var _ = Describe("Check Container Command", func() { fakePC.setSRFuncSubmitSuccessfully("", "") }) It("should not throw an error", func() { - sbmt.dockerConfig = "" + sbmt.DockerConfig = "" err := os.Remove(dockerConfigPath) Expect(err).ToNot(HaveOccurred()) @@ -261,24 +246,24 @@ var _ = Describe("Check Container Command", func() { Context("When using the noop submitter", func() { var bf *bytes.Buffer - var noop *noopSubmitter + var noop *NoopSubmitter BeforeEach(func() { bufferLogger := logrus.New() bf = bytes.NewBuffer([]byte{}) bufferLogger.SetOutput(bf) - noop = &noopSubmitter{log: bufferLogger} + noop = NewNoopSubmitter(false, "", bufferLogger) }) Context("and enabling log emitting", func() { BeforeEach(func() { - noop.emitLog = true + noop.SetEmitLog(true) }) It("should include the reason in the emitted log if specified", func() { testReason := "test reason" - noop.reason = testReason + noop.SetReason(testReason) err := noop.Submit(context.TODO()) Expect(err).ToNot(HaveOccurred()) Expect(bf.String()).To(ContainSubstring(testReason)) @@ -293,7 +278,7 @@ var _ = Describe("Check Container Command", func() { Context("and disabling log emitting", func() { It("should not emit logs when calling submit", func() { - noop.emitLog = false + noop.SetEmitLog(false) err := noop.Submit(context.TODO()) Expect(err).ToNot(HaveOccurred()) Expect(bf.String()).To(BeEmpty()) @@ -311,12 +296,12 @@ var _ = Describe("Check Container Command", func() { LogFile: "logfile", } - pc := newPyxisClient(context.TODO(), cfg.ReadOnly()) + pc := NewPyxisClient(context.TODO(), cfg.ReadOnly()) Expect(pc).ToNot(BeNil()) It("should return a containerCertificationSubmitter", func() { - submitter := resolveSubmitter(pc, cfg.ReadOnly()) - typed, ok := submitter.(*containerCertificationSubmitter) + submitter := ResolveSubmitter(pc, cfg.ReadOnly()) + typed, ok := submitter.(*ContainerCertificationSubmitter) Expect(typed).ToNot(BeNil()) Expect(ok).To(BeTrue()) }) @@ -325,8 +310,8 @@ var _ = Describe("Check Container Command", func() { Context("With no pyxis client", func() { cfg := runtime.Config{} It("should return a no-op submitter", func() { - submitter := resolveSubmitter(nil, cfg.ReadOnly()) - typed, ok := submitter.(*noopSubmitter) + submitter := ResolveSubmitter(nil, cfg.ReadOnly()) + typed, ok := submitter.(*NoopSubmitter) Expect(typed).ToNot(BeNil()) Expect(ok).To(BeTrue()) }) @@ -338,7 +323,7 @@ var _ = Describe("Check Container Command", func() { cfgNoCertProjectID := runtime.Config{} It("Should return a nil pyxis client", func() { - pc := newPyxisClient(context.TODO(), cfgNoCertProjectID.ReadOnly()) + pc := NewPyxisClient(context.TODO(), cfgNoCertProjectID.ReadOnly()) Expect(pc).To(BeNil()) }) }) @@ -360,13 +345,13 @@ var _ = Describe("Check Container Command", func() { } It("Should return a nil pyxis client", func() { - pc := newPyxisClient(context.TODO(), cfgMissingCertProjectID.ReadOnly()) + pc := NewPyxisClient(context.TODO(), cfgMissingCertProjectID.ReadOnly()) Expect(pc).To(BeNil()) - pc = newPyxisClient(context.TODO(), cfgMissingPyxisHost.ReadOnly()) + pc = NewPyxisClient(context.TODO(), cfgMissingPyxisHost.ReadOnly()) Expect(pc).To(BeNil()) - pc = newPyxisClient(context.TODO(), cfgMissingPyxisAPIToken.ReadOnly()) + pc = NewPyxisClient(context.TODO(), cfgMissingPyxisAPIToken.ReadOnly()) Expect(pc).To(BeNil()) }) }) @@ -379,7 +364,7 @@ var _ = Describe("Check Container Command", func() { } It("should return a pyxis client", func() { - pc := newPyxisClient(context.TODO(), cfgValid.ReadOnly()) + pc := NewPyxisClient(context.TODO(), cfgValid.ReadOnly()) Expect(pc).ToNot(BeNil()) }) }) @@ -397,27 +382,9 @@ var _ = Describe("Check Container Command", func() { Context("and the user passed the submit flag, but no credentials", func() { It("should return a noop submitter as credentials are required for submission", func() { - runner, err := newCheckContainerRunner(context.TODO(), cfg) - Expect(err).ToNot(HaveOccurred()) - _, rsIsCorrectType := runner.rs.(*noopSubmitter) - Expect(rsIsCorrectType).To(BeTrue()) - }) - }) - - Context("and the user did not pass the submit flag", func() { - var origSubmitValue bool - BeforeEach(func() { - origSubmitValue = submit - submit = false - }) - - AfterEach(func() { - submit = origSubmitValue - }) - It("should return a noopSubmitter resultSubmitter", func() { - runner, err := newCheckContainerRunner(context.TODO(), cfg) + runner, err := NewCheckContainerRunner(context.TODO(), cfg, false) Expect(err).ToNot(HaveOccurred()) - _, rsIsCorrectType := runner.rs.(*noopSubmitter) + _, rsIsCorrectType := runner.Rs.(*NoopSubmitter) Expect(rsIsCorrectType).To(BeTrue()) }) }) @@ -425,21 +392,21 @@ var _ = Describe("Check Container Command", func() { Context("with a valid policy formatter", func() { It("should return with no error, and the appropriate formatter", func() { cfg.ResponseFormat = "xml" - runner, err := newCheckContainerRunner(context.TODO(), cfg) + runner, err := NewCheckContainerRunner(context.TODO(), cfg, false) Expect(err).ToNot(HaveOccurred()) expectedFormatter, err := formatters.NewByName(cfg.ResponseFormat) Expect(err).ToNot(HaveOccurred()) - Expect(runner.formatter.PrettyName()).To(Equal(expectedFormatter.PrettyName())) + Expect(runner.Formatter.PrettyName()).To(Equal(expectedFormatter.PrettyName())) }) }) Context("with an invalid policy definition", func() { It("should return the container policy engine anyway", func() { - runner, err := newCheckContainerRunner(context.TODO(), cfg) + runner, err := NewCheckContainerRunner(context.TODO(), cfg, false) Expect(err).ToNot(HaveOccurred()) expectedEngine, err := engine.NewForConfig(context.TODO(), cfg.ReadOnly()) - Expect(runner.eng).To(BeEquivalentTo(expectedEngine)) + Expect(runner.Eng).To(BeEquivalentTo(expectedEngine)) Expect(err).ToNot(HaveOccurred()) }) }) @@ -449,129 +416,16 @@ var _ = Describe("Check Container Command", func() { Context("with an invalid formatter definition", func() { It("should return an error", func() { cfg.ResponseFormat = "foo" - _, err := newCheckContainerRunner(context.TODO(), cfg) + _, err := NewCheckContainerRunner(context.TODO(), cfg, false) Expect(err).To(HaveOccurred()) }) }) It("should contain a ResultWriterFile resultWriter", func() { - runner, err := newCheckContainerRunner(context.TODO(), cfg) + runner, err := NewCheckContainerRunner(context.TODO(), cfg, false) Expect(err).ToNot(HaveOccurred()) - _, rwIsExpectedType := runner.rw.(*runtime.ResultWriterFile) + _, rwIsExpectedType := runner.Rw.(*runtime.ResultWriterFile) Expect(rwIsExpectedType).To(BeTrue()) }) }) - - Context("When validating check container arguments and flags", func() { - Context("and the user provided more than 1 positional arg", func() { - It("should fail to run", func() { - _, err := executeCommand(checkContainerCmd(), "foo", "bar") - Expect(err).To(HaveOccurred()) - }) - }) - - Context("and the user provided less than 1 positional arg", func() { - It("should fail to run", func() { - _, err := executeCommand(checkContainerCmd()) - Expect(err).To(HaveOccurred()) - }) - }) - - DescribeTable("and the user has enabled the submit flag", - func(errString string, args []string) { - out, err := executeCommand(checkContainerCmd(), args...) - Expect(err).To(HaveOccurred()) - Expect(out).To(ContainSubstring(errString)) - }, - Entry("certification-project-id and pyxis-api-token are not supplied", "certification Project ID must be specified when --submit is present", []string{"--submit", "foo"}), - Entry("pyxis-api-token is not supplied", "pyxis API Token must be specified when --submit is present", []string{"foo", "--submit", "--certification-project-id=fooid"}), - Entry("certification-project-id is not supplied", "certification Project ID must be specified when --submit is present", []string{"--submit", "foo", "--pyxis-api-token=footoken"}), - Entry("pyxis-api-token flag is present but empty because of '='", "cannot be empty when --submit is present", []string{"foo", "--submit", "--certification-project-id=fooid", "--pyxis-api-token="}), - Entry("certification-project-id flag is present but empty because of '='", "cannot be empty when --submit is present", []string{"foo", "--submit", "--certification-project-id=", "--pyxis-api-token=footoken"}), - Entry("submit is passed after empty api token", "pyxis API token and certification ID are required when --submit is present", []string{"foo", "--certification-project-id=fooid", "--pyxis-api-token", "--submit"}), - Entry("submit is passed with explicit value after empty api token", "pyxis API token and certification ID are required when --submit is present", []string{"foo", "--certification-project-id=fooid", "--pyxis-api-token", "--submit=true"}), - ) - - When("the user enables the submit flag", func() { - When("environment variables are used for certification ID and api token", func() { - BeforeEach(func() { - os.Setenv("PFLT_CERTIFICATION_PROJECT_ID", "certid") - os.Setenv("PFLT_PYXIS_API_TOKEN", "tokenid") - DeferCleanup(os.Unsetenv, "PFLT_CERTIFICATION_PROJECT_ID") - DeferCleanup(os.Unsetenv, "PFLT_PYXIS_API_TOKEN") - }) - It("should still execute with no error", func() { - submit = true - - err := checkContainerPositionalArgs(checkContainerCmd(), []string{"foo"}) - Expect(err).ToNot(HaveOccurred()) - Expect(viper.GetString("pyxis_api_token")).To(Equal("tokenid")) - Expect(viper.GetString("certification_project_id")).To(Equal("certid")) - }) - }) - When("a config file is used", func() { - BeforeEach(func() { - config := `pyxis_api_token: mytoken -certification_project_id: mycertid` - tempDir, err := os.MkdirTemp("", "check-container-submit-*") - Expect(err).ToNot(HaveOccurred()) - err = os.WriteFile(filepath.Join(tempDir, "config.yaml"), bytes.NewBufferString(config).Bytes(), 0o644) - Expect(err).ToNot(HaveOccurred()) - viper.AddConfigPath(tempDir) - DeferCleanup(os.RemoveAll, tempDir) - }) - It("should still execute with no error", func() { - // Make sure that we've read the config file - initConfig() - submit = true - - err := checkContainerPositionalArgs(checkContainerCmd(), []string{"foo"}) - Expect(err).ToNot(HaveOccurred()) - Expect(viper.GetString("pyxis_api_token")).To(Equal("mytoken")) - Expect(viper.GetString("certification_project_id")).To(Equal("mycertid")) - }) - }) - }) - }) - - Context("When validating the certification-project-id flag", func() { - Context("and the flag is set properly", func() { - BeforeEach(func() { - viper.Set("certification_project_id", "123456789") - }) - It("should not change the flag value", func() { - err := validateCertificationProjectID(checkContainerCmd(), []string{"foo"}) - Expect(err).ToNot(HaveOccurred()) - Expect(viper.GetString("certification_project_id")).To(Equal("123456789")) - }) - }) - Context("and a valid ospid format is provided", func() { - BeforeEach(func() { - viper.Set("certification_project_id", "ospid-123456789") - }) - It("should strip ospid- from the flag value", func() { - err := validateCertificationProjectID(checkContainerCmd(), []string{"foo"}) - Expect(err).ToNot(HaveOccurred()) - Expect(viper.GetString("certification_project_id")).To(Equal("123456789")) - }) - }) - Context("and a legacy format with ospid is provided", func() { - BeforeEach(func() { - viper.Set("certification_project_id", "ospid-62423-f26c346-6cc1dc7fae92") - }) - It("should throw an error", func() { - err := validateCertificationProjectID(checkContainerCmd(), []string{"foo"}) - Expect(err).To(HaveOccurred()) - }) - }) - Context("and a legacy format without ospid is provided", func() { - BeforeEach(func() { - viper.Set("certification_project_id", "62423-f26c346-6cc1dc7fae92") - }) - It("should throw an error", func() { - err := validateCertificationProjectID(checkContainerCmd(), []string{"foo"}) - Expect(err).To(HaveOccurred()) - }) - }) - }) }) diff --git a/cmd/fakes_test.go b/lib/fakes.go similarity index 87% rename from cmd/fakes_test.go rename to lib/fakes.go index af368641c..e1ffb71d2 100644 --- a/cmd/fakes_test.go +++ b/lib/fakes.go @@ -1,4 +1,4 @@ -package cmd +package lib import ( "context" @@ -19,9 +19,17 @@ type ( srFunc func(context.Context, *pyxis.CertificationInput) (*pyxis.CertificationResults, error) ) -// fakePyxisClient is a configurable pyxisClient for use in testing. It accepts function definitions to +func NewFakePyxisClientNoop() *FakePyxisClient { + return &FakePyxisClient{ + findImagesByDigestFunc: fidbFuncNoop, + getProjectsFunc: gpFuncNoop, + submitResultsFunc: srFuncNoop, + } +} + +// FakePyxisClient is a configurable pyxisClient for use in testing. It accepts function definitions to // use to implement a cmd.pyxisClient. -type fakePyxisClient struct { +type FakePyxisClient struct { findImagesByDigestFunc fibdFunc getProjectsFunc gpFunc submitResultsFunc srFunc @@ -29,7 +37,7 @@ type fakePyxisClient struct { // baseProject returns a pyxis.CertProject with an id of projectID, or a base value // if none is provided. -func (pc *fakePyxisClient) baseProject(projectID string) pyxis.CertProject { +func (pc *FakePyxisClient) baseProject(projectID string) pyxis.CertProject { pid := "000000000000" if len(projectID) > 0 { pid = projectID @@ -44,7 +52,7 @@ func (pc *fakePyxisClient) baseProject(projectID string) pyxis.CertProject { // successfulCertResults returns a pyxis.CertificationResults for use in tests emulating successful // submission. -func (pc *fakePyxisClient) successfulCertResults(projectID, certImageID string) pyxis.CertificationResults { +func (pc *FakePyxisClient) successfulCertResults(projectID, certImageID string) pyxis.CertificationResults { pid := "000000000000" if len(projectID) > 0 { pid = projectID @@ -65,14 +73,14 @@ func (pc *fakePyxisClient) successfulCertResults(projectID, certImageID string) } // setGPFuncReturnBaseProject sets pc.getProjectFunc to a function that returns baseProject. -// This is a fakePyxisClient method because it enables standardizing on a single value of -// a CertificationProject for GetProject calls, tied to the instance of fakePyxisClient -func (pc *fakePyxisClient) setGPFuncReturnBaseProject(projectID string) { +// This is a FakePyxisClient method because it enables standardizing on a single value of +// a CertificationProject for GetProject calls, tied to the instance of FakePyxisClient +func (pc *FakePyxisClient) setGPFuncReturnBaseProject(projectID string) { baseproj := pc.baseProject(projectID) pc.getProjectsFunc = func(context.Context) (*pyxis.CertProject, error) { return &baseproj, nil } } -func (pc *fakePyxisClient) setSRFuncSubmitSuccessfully(projectID, certImageID string) { +func (pc *FakePyxisClient) setSRFuncSubmitSuccessfully(projectID, certImageID string) { baseproj := pc.baseProject(projectID) certresults := pc.successfulCertResults(baseproj.ID, certImageID) pc.submitResultsFunc = func(context.Context, *pyxis.CertificationInput) (*pyxis.CertificationResults, error) { @@ -80,15 +88,15 @@ func (pc *fakePyxisClient) setSRFuncSubmitSuccessfully(projectID, certImageID st } } -func (pc *fakePyxisClient) FindImagesByDigest(ctx context.Context, digests []string) ([]pyxis.CertImage, error) { +func (pc *FakePyxisClient) FindImagesByDigest(ctx context.Context, digests []string) ([]pyxis.CertImage, error) { return pc.findImagesByDigestFunc(ctx, digests) } -func (pc *fakePyxisClient) GetProject(ctx context.Context) (*pyxis.CertProject, error) { +func (pc *FakePyxisClient) GetProject(ctx context.Context) (*pyxis.CertProject, error) { return pc.getProjectsFunc(ctx) } -func (pc *fakePyxisClient) SubmitResults(ctx context.Context, ci *pyxis.CertificationInput) (*pyxis.CertificationResults, error) { +func (pc *FakePyxisClient) SubmitResults(ctx context.Context, ci *pyxis.CertificationInput) (*pyxis.CertificationResults, error) { return pc.submitResultsFunc(ctx, ci) } @@ -131,17 +139,17 @@ func srFuncReturnError(ctx context.Context, ci *pyxis.CertificationInput) (*pyxi return nil, errors.New("some submission error") } -// fidbFuncNoop implements a fidbFunc, best to use while instantiating fakePyxisClient. +// fidbFuncNoop implements a fidbFunc, best to use while instantiating FakePyxisClient. func fidbFuncNoop(ctx context.Context, digests []string) ([]pyxis.CertImage, error) { return nil, nil } -// gpFuncNoop implements a gpFunc, best to use while instantiating fakePyxisClient. +// gpFuncNoop implements a gpFunc, best to use while instantiating FakePyxisClient. func gpFuncNoop(ctx context.Context) (*pyxis.CertProject, error) { return nil, nil } -// srFuncNoop implements a srFuncNoop, best to use while instantiating fakePyxisClient. +// srFuncNoop implements a srFuncNoop, best to use while instantiating FakePyxisClient. func srFuncNoop(ctx context.Context, ci *pyxis.CertificationInput) (*pyxis.CertificationResults, error) { return nil, nil } diff --git a/lib/lib.go b/lib/lib.go new file mode 100644 index 000000000..1ef90d475 --- /dev/null +++ b/lib/lib.go @@ -0,0 +1,229 @@ +package lib + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "strings" + + "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/formatters" + "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/policy" + "github.com/redhat-openshift-ecosystem/openshift-preflight/certification/runtime" + log "github.com/sirupsen/logrus" +) + +// CheckContainerRunner contains all of the components necessary to run checkContainer. +type CheckContainerRunner struct { + Cfg *runtime.Config + Pc PyxisClient + Eng engine.CheckEngine + Formatter formatters.ResponseFormatter + Rw ResultWriter + Rs ResultSubmitter +} + +func NewCheckContainerRunner(ctx context.Context, cfg *runtime.Config, submit bool) (*CheckContainerRunner, error) { + cfg.Policy = policy.PolicyContainer + cfg.Submit = submit + + pyxisClient := NewPyxisClient(ctx, cfg.ReadOnly()) + // If we have a pyxisClient, we can query for container policy exceptions. + if pyxisClient != nil { + policy, err := GetContainerPolicyExceptions(ctx, pyxisClient) + if err != nil { + return nil, err + } + + cfg.Policy = policy + } + + engine, err := engine.NewForConfig(ctx, cfg.ReadOnly()) + if err != nil { + return nil, err + } + + fmttr, err := formatters.NewForConfig(cfg.ReadOnly()) + if err != nil { + return nil, err + } + + rs := ResolveSubmitter(pyxisClient, cfg.ReadOnly()) + + return &CheckContainerRunner{ + Cfg: cfg, + Pc: pyxisClient, + Eng: engine, + Formatter: fmttr, + Rw: &runtime.ResultWriterFile{}, + Rs: rs, + }, nil +} + +// checkOperatorRunner contains all of the components necessary to run checkOperator. +type CheckOperatorRunner struct { + Cfg *runtime.Config + Eng engine.CheckEngine + Formatter formatters.ResponseFormatter + Rw ResultWriter +} + +// newCheckOperatorRunner returns a checkOperatorRunner containing all of the tooling necessary +// to run checkOperator. +func NewCheckOperatorRunner(ctx context.Context, cfg *runtime.Config) (*CheckOperatorRunner, error) { + cfg.Policy = policy.PolicyOperator + cfg.Submit = false // there's no such thing as submitting for operators today. + + engine, err := engine.NewForConfig(ctx, cfg.ReadOnly()) + if err != nil { + return nil, err + } + + fmttr, err := formatters.NewForConfig(cfg.ReadOnly()) + if err != nil { + return nil, err + } + + return &CheckOperatorRunner{ + Cfg: cfg, + Eng: engine, + Formatter: fmttr, + Rw: &runtime.ResultWriterFile{}, + }, nil +} + +// resolveSubmitter will build out a resultSubmitter if the provided pyxisClient, pc, is not nil. +// The pyxisClient is a required component of the submitter. If pc is nil, then a noop submitter +// is returned instead, which does nothing. +func ResolveSubmitter(pc PyxisClient, cfg certification.Config) ResultSubmitter { + if pc != nil { + return &ContainerCertificationSubmitter{ + CertificationProjectID: cfg.CertificationProjectID(), + Pyxis: pc, + DockerConfig: cfg.DockerConfig(), + PreflightLogFile: cfg.LogFile(), + } + } + return NewNoopSubmitter(true, "", nil) +} + +// GetContainerPolicyExceptions will query Pyxis to determine if +// a given project has a certification excemptions, such as root or scratch. +// This will then return the corresponding policy. +// +// If no policy exception flags are found on the project, the standard +// container policy is returned. +func GetContainerPolicyExceptions(ctx context.Context, pc PyxisClient) (policy.Policy, error) { + certProject, err := pc.GetProject(ctx) + if err != nil { + return "", fmt.Errorf("could not retrieve project: %w", err) + } + // log.Debugf("Certification project name is: %s", certProject.Name) + if certProject.Container.Type == "scratch" { + return policy.PolicyScratch, nil + } + + // if a partner sets `Host Level Access` in connect to `Privileged`, enable RootExceptionContainerPolicy checks + if certProject.Container.Privileged { + return policy.PolicyRoot, nil + } + return policy.PolicyContainer, nil +} + +// PreflightCheck executes checks, interacts with pyxis, format output, writes, and submits results. +func PreflightCheck( + ctx context.Context, + cfg *runtime.Config, + pc PyxisClient, //nolint:unparam // pyxisClient is currently unused. + eng engine.CheckEngine, + formatter formatters.ResponseFormatter, + rw ResultWriter, + rs ResultSubmitter, +) error { + // configure the artifacts directory if the user requested a different directory. + if cfg.Artifacts != "" { + artifacts.SetDir(cfg.Artifacts) + } + + // create the results file early to catch cases where we are not + // able to write to the filesystem before we attempt to execute checks. + resultsFilePath, err := artifacts.WriteFile(resultsFilenameWithExtension(formatter.FileExtension()), strings.NewReader("")) + if err != nil { + return err + } + resultsFile, err := rw.OpenFile(resultsFilePath) + if err != nil { + return err + } + defer resultsFile.Close() + + resultsOutputTarget := io.MultiWriter(os.Stdout, resultsFile) + + // execute the checks + if err := eng.ExecuteChecks(ctx); err != nil { + return err + } + results := eng.Results(ctx) + + // return results to the user and then close output files + formattedResults, err := formatter.Format(ctx, results) + if err != nil { + return err + } + + fmt.Fprintln(resultsOutputTarget, string(formattedResults)) + + if cfg.WriteJUnit { + if err := writeJUnit(ctx, results); err != nil { + return err + } + } + + if cfg.Submit { + if err := rs.Submit(ctx); err != nil { + return err + } + } + + log.Infof("Preflight result: %s", convertPassedOverall(results.PassedOverall)) + + return nil +} + +func writeJUnit(ctx context.Context, results runtime.Results) error { + var cfg runtime.Config + cfg.ResponseFormat = "junitxml" + + junitformatter, err := formatters.NewForConfig(cfg.ReadOnly()) + if err != nil { + return err + } + junitResults, err := junitformatter.Format(ctx, results) + if err != nil { + return err + } + + junitFilename, err := artifacts.WriteFile("results-junit.xml", bytes.NewReader((junitResults))) + if err != nil { + return err + } + log.Tracef("JUnitXML written to %s", junitFilename) + + return nil +} + +func resultsFilenameWithExtension(ext string) string { + return strings.Join([]string{"results", ext}, ".") +} + +func convertPassedOverall(passedOverall bool) string { + if passedOverall { + return "PASSED" + } + + return "FAILED" +} diff --git a/lib/lib_test.go b/lib/lib_test.go new file mode 100644 index 000000000..312c78018 --- /dev/null +++ b/lib/lib_test.go @@ -0,0 +1,2 @@ +package lib + diff --git a/cmd/preflight_check_test.go b/lib/preflight_check.go similarity index 87% rename from cmd/preflight_check_test.go rename to lib/preflight_check.go index 8597db170..da7321c72 100644 --- a/cmd/preflight_check_test.go +++ b/lib/preflight_check.go @@ -1,4 +1,4 @@ -package cmd +package lib import ( "context" @@ -23,11 +23,11 @@ var _ = Describe("Preflight Check Func", func() { var localArtifactsDir string var cfg *runtime.Config - var pc pyxisClient + var pc PyxisClient var eng engine.CheckEngine var fmttr formatters.ResponseFormatter - var rw resultWriter - var rs resultSubmitter + var rw ResultWriter + var rs ResultSubmitter BeforeEach(func() { // instantiate err to make sure we can equal-assign in the following line. @@ -45,7 +45,7 @@ var _ = Describe("Preflight Check Func", func() { Artifacts: localArtifactsDir, } - pc = &fakePyxisClient{ + pc = &FakePyxisClient{ findImagesByDigestFunc: fidbFuncNoop, getProjectsFunc: gpFuncNoop, submitResultsFunc: srFuncNoop, @@ -58,7 +58,7 @@ var _ = Describe("Preflight Check Func", func() { fmttr, _ = formatters.NewByName(formatters.DefaultFormat) rw = &runtime.ResultWriterFile{} - rs = &noopSubmitter{} + rs = NewNoopSubmitter(false, "", nil) DeferCleanup(os.RemoveAll, localTempDir) DeferCleanup(os.RemoveAll, localArtifactsDir) @@ -68,7 +68,7 @@ var _ = Describe("Preflight Check Func", func() { Context("with a customized artifacts directory", func() { It("should set the artifacts directory accordingly", func() { // it's possible this will throw an error, but we dont' care for this test. - _ = preflightCheck(context.TODO(), cfg, pc, eng, fmttr, rw, rs) + _ = PreflightCheck(context.TODO(), cfg, pc, eng, fmttr, rw, rs) Expect(artifacts.Path()).To(Equal(localArtifactsDir)) }) }) @@ -79,7 +79,7 @@ var _ = Describe("Preflight Check Func", func() { }) It("should throw an error", func() { - err := preflightCheck(context.TODO(), cfg, pc, eng, fmttr, rw, rs) + err := PreflightCheck(context.TODO(), cfg, pc, eng, fmttr, rw, rs) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("some result writer error")) }) @@ -92,7 +92,7 @@ var _ = Describe("Preflight Check Func", func() { eng = fakeCheckEngine{errorRunningChecks: true, errorMsg: msg} }) It("should thrown an error", func() { - err := preflightCheck(context.TODO(), cfg, pc, eng, fmttr, rw, rs) + err := PreflightCheck(context.TODO(), cfg, pc, eng, fmttr, rw, rs) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring(msg)) }) @@ -106,7 +106,7 @@ var _ = Describe("Preflight Check Func", func() { }) It("should throw an error", func() { - err := preflightCheck(context.TODO(), cfg, pc, eng, fmttr, rw, rs) + err := PreflightCheck(context.TODO(), cfg, pc, eng, fmttr, rw, rs) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring(msg)) }) @@ -117,7 +117,7 @@ var _ = Describe("Preflight Check Func", func() { cfg.WriteJUnit = true }) It("should write a junit file in the artifacts directory", func() { - err := preflightCheck(context.TODO(), cfg, pc, eng, fmttr, rw, rs) + err := PreflightCheck(context.TODO(), cfg, pc, eng, fmttr, rw, rs) Expect(err).ToNot(HaveOccurred()) Expect(path.Join(artifacts.Path(), "results-junit.xml")).To(BeAnExistingFile()) }) @@ -134,7 +134,7 @@ var _ = Describe("Preflight Check Func", func() { }) It("should throw an error", func() { - err := preflightCheck(context.TODO(), cfg, pc, eng, fmttr, rw, rs) + err := PreflightCheck(context.TODO(), cfg, pc, eng, fmttr, rw, rs) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring(msg)) }) @@ -146,7 +146,7 @@ var _ = Describe("Preflight Check Func", func() { }) It("should complete with no errors", func() { - err := preflightCheck(context.TODO(), cfg, pc, eng, fmttr, rw, rs) + err := PreflightCheck(context.TODO(), cfg, pc, eng, fmttr, rw, rs) Expect(err).ToNot(HaveOccurred()) }) }) diff --git a/lib/preflight_check_test.go b/lib/preflight_check_test.go new file mode 100644 index 000000000..55c21f80a --- /dev/null +++ b/lib/preflight_check_test.go @@ -0,0 +1 @@ +package lib diff --git a/cmd/types.go b/lib/types.go similarity index 72% rename from cmd/types.go rename to lib/types.go index 472031def..4f6e31c5f 100644 --- a/cmd/types.go +++ b/lib/types.go @@ -1,4 +1,4 @@ -package cmd +package lib import ( "context" @@ -13,23 +13,24 @@ import ( "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/pyxis" + "github.com/spf13/viper" log "github.com/sirupsen/logrus" ) // resultWriter defines methods associated with writing check results. -type resultWriter interface { +type ResultWriter interface { OpenFile(name string) (io.WriteCloser, error) io.WriteCloser } // resultSubmitter defines methods associated with submitting results to Red HAt. -type resultSubmitter interface { +type ResultSubmitter interface { Submit(context.Context) error } // pyxisClient defines pyxis API interactions that are relevant to check executions in cmd. -type pyxisClient interface { +type PyxisClient interface { FindImagesByDigest(ctx context.Context, digests []string) ([]pyxis.CertImage, error) GetProject(context.Context) (*pyxis.CertProject, error) SubmitResults(context.Context, *pyxis.CertificationInput) (*pyxis.CertificationResults, error) @@ -40,7 +41,7 @@ type pyxisClient interface { // Callers should treat a nil pyxis client as an indicator that pyxis calls should not be made. // //nolint:unparam // ctx is unused. Keep for future use. -func newPyxisClient(ctx context.Context, cfg certification.Config) pyxisClient { +func NewPyxisClient(ctx context.Context, cfg certification.Config) PyxisClient { if cfg.CertificationProjectID() == "" || cfg.PyxisAPIToken() == "" || cfg.PyxisHost() == "" { return nil } @@ -53,20 +54,20 @@ func newPyxisClient(ctx context.Context, cfg certification.Config) pyxisClient { ) } -// containerCertificationSubmitter submits container results to Pyxis, and implements +// ContainerCertificationSubmitter submits container results to Pyxis, and implements // a resultSubmitter. -type containerCertificationSubmitter struct { - certificationProjectID string - pyxis pyxisClient - dockerConfig string - preflightLogFile string +type ContainerCertificationSubmitter struct { + CertificationProjectID string + Pyxis PyxisClient + DockerConfig string + PreflightLogFile string } -func (s *containerCertificationSubmitter) Submit(ctx context.Context) error { +func (s *ContainerCertificationSubmitter) Submit(ctx context.Context) error { log.Info("preparing results that will be submitted to Red Hat") // get the project info from pyxis - certProject, err := s.pyxis.GetProject(ctx) + certProject, err := s.Pyxis.GetProject(ctx) if err != nil { return fmt.Errorf("could not retrieve project: %w", err) } @@ -83,11 +84,11 @@ func (s *containerCertificationSubmitter) Submit(ctx context.Context) error { // only read the dockerfile if the user provides a location for the file // at this point in the flow, if `cfg.DockerConfig` is empty we know the repo is public and can continue the submission flow - if s.dockerConfig != "" { - dockerConfigJSONBytes, err := os.ReadFile(s.dockerConfig) + if s.DockerConfig != "" { + dockerConfigJSONBytes, err := os.ReadFile(s.DockerConfig) if err != nil { return fmt.Errorf("could not open file for submission: %s: %w", - s.dockerConfig, + s.DockerConfig, err, ) } @@ -100,7 +101,7 @@ func (s *containerCertificationSubmitter) Submit(ctx context.Context) error { // if we were to send what pyixs just sent us in a update call, pyxis would throw a validation error saying it's not valid json // the below code aims to set the DockerConfigJSON to an empty string, and since this field is `omitempty` when we marshall it // we will not get a validation error - if s.dockerConfig == "" { + if s.DockerConfig == "" { certProject.Container.DockerConfigJSON = "" } @@ -137,11 +138,11 @@ func (s *containerCertificationSubmitter) Submit(ctx context.Context) error { } defer rpmManifest.Close() - logfile, err := os.Open(s.preflightLogFile) + logfile, err := os.Open(s.PreflightLogFile) if err != nil { return fmt.Errorf( "could not open file for submission: %s: %w", - s.preflightLogFile, + s.PreflightLogFile, err, ) } @@ -155,14 +156,14 @@ func (s *containerCertificationSubmitter) Submit(ctx context.Context) error { // The certification engine writes the rpmManifest for images not based on scratch. WithRPMManifest(rpmManifest). // Include the preflight execution log file. - WithArtifact(logfile, filepath.Base(s.preflightLogFile)) + WithArtifact(logfile, filepath.Base(s.PreflightLogFile)) input, err := submission.Finalize() if err != nil { return fmt.Errorf("unable to finalize data that would be sent to pyxis: %w", err) } - certResults, err := s.pyxis.SubmitResults(ctx, input) + certResults, err := s.Pyxis.SubmitResults(ctx, input) if err != nil { return fmt.Errorf("could not submit to pyxis: %w", err) } @@ -170,23 +171,31 @@ func (s *containerCertificationSubmitter) Submit(ctx context.Context) error { log.Info("Test results have been submitted to Red Hat.") log.Info("These results will be reviewed by Red Hat for final certification.") log.Infof("The container's image id is: %s.", certResults.CertImage.ID) - log.Infof("Please check %s to view scan results.", buildScanResultsURL(s.certificationProjectID, certResults.CertImage.ID)) - log.Infof("Please check %s to monitor the progress.", buildOverviewURL(s.certificationProjectID)) + log.Infof("Please check %s to view scan results.", BuildScanResultsURL(s.CertificationProjectID, certResults.CertImage.ID)) + log.Infof("Please check %s to monitor the progress.", BuildOverviewURL(s.CertificationProjectID)) return nil } // noopSubmitter is a no-op resultSubmitter that optionally logs a message // and a reason as to why results were not submitted. -type noopSubmitter struct { +type NoopSubmitter struct { emitLog bool reason string log *log.Logger } -var _ resultSubmitter = &noopSubmitter{} +func NewNoopSubmitter(emitLog bool, reason string, log *log.Logger) *NoopSubmitter { + return &NoopSubmitter{ + emitLog: emitLog, + reason: reason, + log: log, + } +} -func (s *noopSubmitter) Submit(ctx context.Context) error { +var _ ResultSubmitter = &NoopSubmitter{} + +func (s *NoopSubmitter) Submit(ctx context.Context) error { if s.emitLog { msg := "Results are not being sent for submission." if s.reason != "" { @@ -198,3 +207,30 @@ func (s *noopSubmitter) Submit(ctx context.Context) error { return nil } + +func (s *NoopSubmitter) SetEmitLog(emitLog bool) { + s.emitLog = emitLog +} + +func (s *NoopSubmitter) SetReason(reason string) { + s.reason = reason +} + +func buildConnectURL(projectID string) string { + connectURL := fmt.Sprintf("https://connect.redhat.com/projects/%s", projectID) + + pyxisEnv := viper.GetString("pyxis_env") + if len(pyxisEnv) > 0 && pyxisEnv != "prod" { + connectURL = fmt.Sprintf("https://connect.%s.redhat.com/projects/%s", viper.GetString("pyxis_env"), projectID) + } + + return connectURL +} + +func BuildOverviewURL(projectID string) string { + return fmt.Sprintf("%s/overview", buildConnectURL(projectID)) +} + +func BuildScanResultsURL(projectID string, imageID string) string { + return fmt.Sprintf("%s/images/%s/scan-results", buildConnectURL(projectID), imageID) +}