From 929fb63d8bb2ab77e2ed1e3e1d6b382ba8721d01 Mon Sep 17 00:00:00 2001 From: Brandon Palm Date: Fri, 30 Sep 2022 09:23:48 -0500 Subject: [PATCH] Add lib to preflight Testing the lib Switch back to old repo name Dep updates Operator Framework update lock otel more dep updates Dep update update deps Add NewManualConfig Add NewManualOperatorConfig Rename back --- certification/runtime/config.go | 22 ++ cmd/check.go | 20 -- cmd/check_container.go | 106 +------- cmd/check_container_cmd_test.go | 163 +++++++++++++ cmd/check_operator.go | 50 +--- cmd/check_operator_test.go | 19 +- cmd/check_test.go | 26 +- cmd/preflight_check.go | 76 ------ go.mod | 14 +- go.sum | 33 ++- {cmd => lib}/check_container_test.go | 230 ++++-------------- cmd/fakes_test.go => lib/fakes.go | 38 +-- lib/lib.go | 229 +++++++++++++++++ lib/lib_test.go | 2 + .../preflight_check.go | 26 +- lib/preflight_check_test.go | 1 + {cmd => lib}/types.go | 88 +++++-- 17 files changed, 616 insertions(+), 527 deletions(-) create mode 100644 cmd/check_container_cmd_test.go delete mode 100644 cmd/preflight_check.go rename {cmd => lib}/check_container_test.go (59%) rename cmd/fakes_test.go => lib/fakes.go (87%) create mode 100644 lib/lib.go create mode 100644 lib/lib_test.go rename cmd/preflight_check_test.go => lib/preflight_check.go (87%) create mode 100644 lib/preflight_check_test.go rename {cmd => lib}/types.go (72%) 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) +}