Repository: redhat-openshift-ecosystem/openshift-preflight
Pull request #931 (2023-03-23 16:19:22)

Use a local viper instance for the CLI
DCI found a bug in #917 that's being caused by Preflight's use of the global viper instance. That PR imports Cobra commands from the Chart Verifier project, which also uses the global viper instance. The end result was that Preflight was reading a `config.{yaml.json}` from an unexpected path (`$HOME`) because Chart Verifier's initialization would add the home directory to the config dir paths array (e.g. where we might find configs).

This PR creates an **internal/viper** package that just loads a package-local instance of Viper.

All places that called spf13/viper are now expected to call internal/viper to use the package-specific instance. 

There are two notable cases (aside from **internal/viper**) that import spf13/viper -

- In tests, there are cases where we're testing behaviors as parameters go through viper, and so the tests instantiate their own instance of viper.Viper. Our local package doesn't have `viper.New()` to get a net-new instance.

- In one case, we do an error evaluation against an error type stored in spf13/viper.

---

You might notice that the **internal/viper** package has the single function `Instance()`. I'm open to changing this, but the reason I chose this over `GetViper()` and `Get()` is because both functions have explicit meanings in the spf13/viper library.

If a developer types `viper.GetViper()`, it's possible that IDEs may automatically import spf13/viper instead of **internal/viper**. The `viper.Get` name has an explicit meaning to get a key from the configuration, so I decided not to use that either.

As a compromise, I decided `viper.Instance()` is unique in that it shouldn't cause an accidental import of spf13/viper.

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

This pull-request has been approved by: *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/931#" title="Author self-approved">komish</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=redhat-openshift-ecosystem%2Fopenshift-preflight).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

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

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

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/931#" title="Author self-approved">komish</a>*, *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/931#pullrequestreview-1355353910" title="Approved">skattoju</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=redhat-openshift-ecosystem%2Fopenshift-preflight).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

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

- ~~[OWNERS](https://github.com/redhat-openshift-ecosystem/openshift-preflight/blob/main/OWNERS)~~ [komish,skattoju]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
dcibot: - SUCCESS https://www.distributed-ci.io/jobs/945504a8-7890-4350-9b60-8ff308808a63/jobStates
 from https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/931
acornett21: /lgtm
openshift-ci[bot]: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/931#pullrequestreview-1355546658" title="Approved">acornett21</a>*, *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/931#" title="Author self-approved">komish</a>*, *<a href="https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/931#pullrequestreview-1355353910" title="Approved">skattoju</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=redhat-openshift-ecosystem%2Fopenshift-preflight).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

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

- ~~[OWNERS](https://github.com/redhat-openshift-ecosystem/openshift-preflight/blob/main/OWNERS)~~ [acornett21,komish,skattoju]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
komish: Thanks @tkrishtop @tonyskapunk. DCI helped us find this!

Patch:
diff --git a/cmd/preflight/cmd/check.go b/cmd/preflight/cmd/check.go
index a6986ad..26b1a92 100644
--- a/cmd/preflight/cmd/check.go
+++ b/cmd/preflight/cmd/check.go
@@ -2,9 +2,9 @@ package cmd
 
 import (
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/cli"
+	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/viper"
 
 	"github.com/spf13/cobra"
-	"github.com/spf13/viper"
 )
 
 func checkCmd() *cobra.Command {
@@ -14,6 +14,7 @@ func checkCmd() *cobra.Command {
 		Long:  "This command will allow you to execute the Red Hat Certification tests for an operator or a container.",
 	}
 
+	viper := viper.Instance()
 	checkCmd.PersistentFlags().StringP("docker-config", "d", "", "Path to docker config.json file. This value is optional for publicly accessible images.\n"+
 		"However, it is strongly encouraged for public Docker Hub images,\n"+
 		"due to the rate limit imposed for unauthenticated requests. (env: PFLT_DOCKERCONFIG)")
diff --git a/cmd/preflight/cmd/check_container.go b/cmd/preflight/cmd/check_container.go
index 899c15e..ddd3bce 100644
--- a/cmd/preflight/cmd/check_container.go
+++ b/cmd/preflight/cmd/check_container.go
@@ -14,12 +14,12 @@ import (
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/formatters"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/lib"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/runtime"
+	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/viper"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/version"
 
 	"github.com/go-logr/logr"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
-	"github.com/spf13/viper"
 )
 
 var submit bool
@@ -43,6 +43,7 @@ func checkContainerCmd(runpreflight runPreflight) *cobra.Command {
 
 	flags := checkContainerCmd.Flags()
 
+	viper := viper.Instance()
 	flags.BoolVarP(&submit, "submit", "s", false, "submit check container results to Red Hat")
 	_ = viper.BindPFlag("submit", flags.Lookup("submit"))
 
@@ -84,7 +85,7 @@ func checkContainerRunE(cmd *cobra.Command, args []string, runpreflight runPrefl
 	containerImage := args[0]
 
 	// Render the Viper configuration as a runtime.Config
-	cfg, err := runtime.NewConfigFrom(*viper.GetViper())
+	cfg, err := runtime.NewConfigFrom(*viper.Instance())
 	if err != nil {
 		return fmt.Errorf("invalid configuration: %w", err)
 	}
@@ -142,6 +143,7 @@ func checkContainerPositionalArgs(cmd *cobra.Command, args []string) error {
 	})
 
 	// --submit was specified
+	viper := viper.Instance()
 	if submit {
 		// If the flag is not marked as changed AND viper hasn't gotten it from environment, it's an error
 		if !cmd.Flag("certification-project-id").Changed && !viper.IsSet("certification_project_id") {
@@ -171,6 +173,7 @@ func checkContainerPositionalArgs(cmd *cobra.Command, args []string) error {
 // validateCertificationProjectID validates that the certification project id is in the proper format
 // and throws an error if the value provided is in a legacy format that is not usable to query pyxis
 func validateCertificationProjectID(cmd *cobra.Command, args []string) error {
+	viper := viper.Instance()
 	certificationProjectID := viper.GetString("certification_project_id")
 	// splitting the certification project id into parts. if there are more than 2 elements in the array,
 	// we know they inputted a legacy project id, which can not be used to query pyxis
diff --git a/cmd/preflight/cmd/check_operator.go b/cmd/preflight/cmd/check_operator.go
index 55dc137..098a855 100644
--- a/cmd/preflight/cmd/check_operator.go
+++ b/cmd/preflight/cmd/check_operator.go
@@ -11,12 +11,12 @@ import (
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/formatters"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/lib"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/runtime"
+	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/viper"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/operator"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/version"
 
 	"github.com/go-logr/logr"
 	"github.com/spf13/cobra"
-	"github.com/spf13/viper"
 )
 
 func checkOperatorCmd(runpreflight runPreflight) *cobra.Command {
@@ -31,6 +31,8 @@ func checkOperatorCmd(runpreflight runPreflight) *cobra.Command {
 			return checkOperatorRunE(cmd, args, runpreflight)
 		},
 	}
+
+	viper := viper.Instance()
 	checkOperatorCmd.Flags().String("namespace", "", "The namespace to use when running OperatorSDK Scorecard. (env: PFLT_NAMESPACE)")
 	_ = viper.BindPFlag("namespace", checkOperatorCmd.Flags().Lookup("namespace"))
 
@@ -65,7 +67,7 @@ func ensureKubeconfigIsSet() error {
 // ensureIndexImageConfigIsSet ensures that the PFLT_INDEXIMAGE environment variable has
 // a value.
 func ensureIndexImageConfigIsSet() error {
-	if catalogImage := viper.GetString("indexImage"); len(catalogImage) == 0 {
+	if catalogImage := viper.Instance().GetString("indexImage"); len(catalogImage) == 0 {
 		return fmt.Errorf("environment variable PFLT_INDEXIMAGE could not be found")
 	}
 
@@ -84,7 +86,7 @@ func checkOperatorRunE(cmd *cobra.Command, args []string, runpreflight runPrefli
 	operatorImage := args[0]
 
 	// Render the Viper configuration as a runtime.Config
-	cfg, err := runtime.NewConfigFrom(*viper.GetViper())
+	cfg, err := runtime.NewConfigFrom(*viper.Instance())
 	if err != nil {
 		return fmt.Errorf("invalid configuration: %w", err)
 	}
diff --git a/cmd/preflight/cmd/root.go b/cmd/preflight/cmd/root.go
index 849c39f..c8fe641 100644
--- a/cmd/preflight/cmd/root.go
+++ b/cmd/preflight/cmd/root.go
@@ -7,13 +7,14 @@ import (
 	"os"
 
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/artifacts"
+	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/viper"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/version"
 
 	"github.com/bombsimon/logrusr/v4"
 	"github.com/go-logr/logr"
 	"github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
-	"github.com/spf13/viper"
+	spfviper "github.com/spf13/viper"
 )
 
 var configFileUsed bool
@@ -32,6 +33,7 @@ func rootCmd() *cobra.Command {
 		PersistentPreRun: preRunConfig,
 	}
 
+	viper := viper.Instance()
 	rootCmd.PersistentFlags().String("logfile", "", "Where the execution logfile will be written. (env: PFLT_LOGFILE)")
 	_ = viper.BindPFlag("logfile", rootCmd.PersistentFlags().Lookup("logfile"))
 
@@ -51,6 +53,7 @@ func Execute() error {
 }
 
 func initConfig() {
+	viper := viper.Instance()
 	// set up ENV var support
 	viper.SetEnvPrefix("pflt")
 	viper.AutomaticEnv()
@@ -62,7 +65,7 @@ func initConfig() {
 
 	configFileUsed = true
 	if err := viper.ReadInConfig(); err != nil {
-		if _, ok := err.(viper.ConfigFileNotFoundError); ok {
+		if _, ok := err.(spfviper.ConfigFileNotFoundError); ok {
 			configFileUsed = false
 		}
 	}
@@ -82,6 +85,7 @@ func initConfig() {
 
 // preRunConfig is used by cobra.PreRun in all non-root commands to load all necessary configurations
 func preRunConfig(cmd *cobra.Command, args []string) {
+	viper := viper.Instance()
 	l := logrus.New()
 	// set up logging
 	logname := viper.GetString("logfile")
diff --git a/internal/lib/types.go b/internal/lib/types.go
index eb2810d..e68225f 100644
--- a/internal/lib/types.go
+++ b/internal/lib/types.go
@@ -12,12 +12,12 @@ import (
 	"time"
 
 	"github.com/go-logr/logr"
-	"github.com/spf13/viper"
 
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/artifacts"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/check"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/log"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/pyxis"
+	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/viper"
 )
 
 // ResultWriter defines methods associated with writing check results.
@@ -232,9 +232,9 @@ func (s *NoopSubmitter) SetReason(reason string) {
 func BuildConnectURL(projectID string) string {
 	connectURL := fmt.Sprintf("https://connect.redhat.com/projects/%s", projectID)
 
-	pyxisEnv := viper.GetString("pyxis_env")
+	pyxisEnv := viper.Instance().GetString("pyxis_env")
 	if len(pyxisEnv) > 0 && pyxisEnv != "prod" {
-		connectURL = fmt.Sprintf("https://connect.%s.redhat.com/projects/%s", viper.GetString("pyxis_env"), projectID)
+		connectURL = fmt.Sprintf("https://connect.%s.redhat.com/projects/%s", viper.Instance().GetString("pyxis_env"), projectID)
 	}
 
 	return connectURL
diff --git a/internal/viper/viper.go b/internal/viper/viper.go
new file mode 100644
index 0000000..9815ab8
--- /dev/null
+++ b/internal/viper/viper.go
@@ -0,0 +1,29 @@
+// Package viper provides a package-specific instance of Viper to avoid
+// the use of Viper's global instance, which can cause conflicts
+package viper
+
+import (
+	"sync"
+
+	spfviper "github.com/spf13/viper"
+)
+
+var (
+	instance *spfviper.Viper
+	mu       = sync.Mutex{}
+)
+
+// Instance provides the instance of Viper, or lazy-loads a new one
+// if one has not been defined.
+func Instance() *spfviper.Viper {
+	if instance != nil {
+		return instance
+	}
+
+	mu.Lock()
+	defer mu.Unlock()
+	if instance == nil {
+		instance = spfviper.New()
+	}
+	return instance
+}


Test patch:
diff --git a/cmd/preflight/cmd/check_container_test.go b/cmd/preflight/cmd/check_container_test.go
index b4b9efe..9d21266 100644
--- a/cmd/preflight/cmd/check_container_test.go
+++ b/cmd/preflight/cmd/check_container_test.go
@@ -11,11 +11,11 @@ import (
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/cli"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/formatters"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/lib"
+	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/viper"
 
 	"github.com/go-logr/logr"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
-	"github.com/spf13/viper"
 )
 
 var _ = Describe("Check Container Command", func() {
@@ -74,8 +74,8 @@ var _ = Describe("Check Container Command", func() {
 
 					err := checkContainerPositionalArgs(checkContainerCmd(mockRunPreflight), []string{"foo"})
 					Expect(err).ToNot(HaveOccurred())
-					Expect(viper.GetString("pyxis_api_token")).To(Equal("tokenid"))
-					Expect(viper.GetString("certification_project_id")).To(Equal("certid"))
+					Expect(viper.Instance().GetString("pyxis_api_token")).To(Equal("tokenid"))
+					Expect(viper.Instance().GetString("certification_project_id")).To(Equal("certid"))
 				})
 			})
 			When("a config file is used", func() {
@@ -86,7 +86,7 @@ certification_project_id: mycertid`
 					Expect(err).ToNot(HaveOccurred())
 					err = os.WriteFile(filepath.Join(tempDir, "config.yaml"), bytes.NewBufferString(config).Bytes(), 0o644)
 					Expect(err).ToNot(HaveOccurred())
-					viper.AddConfigPath(tempDir)
+					viper.Instance().AddConfigPath(tempDir)
 					DeferCleanup(os.RemoveAll, tempDir)
 				})
 				It("should still execute with no error", func() {
@@ -96,8 +96,8 @@ certification_project_id: mycertid`
 
 					err := checkContainerPositionalArgs(checkContainerCmd(mockRunPreflight), []string{"foo"})
 					Expect(err).ToNot(HaveOccurred())
-					Expect(viper.GetString("pyxis_api_token")).To(Equal("mytoken"))
-					Expect(viper.GetString("certification_project_id")).To(Equal("mycertid"))
+					Expect(viper.Instance().GetString("pyxis_api_token")).To(Equal("mytoken"))
+					Expect(viper.Instance().GetString("certification_project_id")).To(Equal("mycertid"))
 				})
 			})
 		})
@@ -106,30 +106,30 @@ certification_project_id: 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")
-				DeferCleanup(viper.Set, "certification_project_id", "")
+				viper.Instance().Set("certification_project_id", "123456789")
+				DeferCleanup(viper.Instance().Set, "certification_project_id", "")
 			})
 			It("should not change the flag value", func() {
 				err := validateCertificationProjectID(checkContainerCmd(mockRunPreflight), []string{"foo"})
 				Expect(err).ToNot(HaveOccurred())
-				Expect(viper.GetString("certification_project_id")).To(Equal("123456789"))
+				Expect(viper.Instance().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")
-				DeferCleanup(viper.Set, "certification_project_id", "")
+				viper.Instance().Set("certification_project_id", "ospid-123456789")
+				DeferCleanup(viper.Instance().Set, "certification_project_id", "")
 			})
 			It("should strip ospid- from the flag value", func() {
 				err := validateCertificationProjectID(checkContainerCmd(mockRunPreflight), []string{"foo"})
 				Expect(err).ToNot(HaveOccurred())
-				Expect(viper.GetString("certification_project_id")).To(Equal("123456789"))
+				Expect(viper.Instance().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")
-				DeferCleanup(viper.Set, "certification_project_id", "")
+				viper.Instance().Set("certification_project_id", "ospid-62423-f26c346-6cc1dc7fae92")
+				DeferCleanup(viper.Instance().Set, "certification_project_id", "")
 			})
 			It("should throw an error", func() {
 				err := validateCertificationProjectID(checkContainerCmd(mockRunPreflight), []string{"foo"})
@@ -138,8 +138,8 @@ certification_project_id: mycertid`
 		})
 		Context("and a legacy format without ospid is provided", func() {
 			BeforeEach(func() {
-				viper.Set("certification_project_id", "62423-f26c346-6cc1dc7fae92")
-				DeferCleanup(viper.Set, "certification_project_id", "")
+				viper.Instance().Set("certification_project_id", "62423-f26c346-6cc1dc7fae92")
+				DeferCleanup(viper.Instance().Set, "certification_project_id", "")
 			})
 			It("should throw an error", func() {
 				err := validateCertificationProjectID(checkContainerCmd(mockRunPreflight), []string{"foo"})
diff --git a/cmd/preflight/cmd/check_operator_test.go b/cmd/preflight/cmd/check_operator_test.go
index fd535ba..58cff17 100644
--- a/cmd/preflight/cmd/check_operator_test.go
+++ b/cmd/preflight/cmd/check_operator_test.go
@@ -7,7 +7,8 @@ import (
 	"github.com/go-logr/logr"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
-	"github.com/spf13/viper"
+
+	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/viper"
 )
 
 var _ = Describe("Check Operator", func() {
@@ -57,8 +58,8 @@ var _ = Describe("Check Operator", func() {
 
 		Context("With all of the required parameters", func() {
 			BeforeEach(func() {
-				DeferCleanup(viper.Set, "indexImage", viper.GetString("indexImage"))
-				viper.Set("indexImage", "foo")
+				DeferCleanup(viper.Instance().Set, "indexImage", viper.Instance().GetString("indexImage"))
+				viper.Instance().Set("indexImage", "foo")
 				if val, isSet := os.LookupEnv("KUBECONFIG"); isSet {
 					DeferCleanup(os.Setenv, "KUBECONFIG", val)
 				} else {
@@ -90,8 +91,8 @@ var _ = Describe("Check Operator", func() {
 
 		Context("With an invalid KUBECONFIG file location", func() {
 			BeforeEach(func() {
-				DeferCleanup(viper.Set, "indexImage", viper.GetString("indexImage"))
-				viper.Set("indexImage", "foo")
+				DeferCleanup(viper.Instance().Set, "indexImage", viper.Instance().GetString("indexImage"))
+				viper.Instance().Set("indexImage", "foo")
 				if val, isSet := os.LookupEnv("KUBECONFIG"); isSet {
 					DeferCleanup(os.Setenv, "KUBECONFIG", val)
 				} else {
@@ -109,8 +110,8 @@ var _ = Describe("Check Operator", func() {
 
 		Context("With a KUBECONFIG file location that is a directory", func() {
 			BeforeEach(func() {
-				DeferCleanup(viper.Set, "indexImage", viper.GetString("indexImage"))
-				viper.Set("indexImage", "foo")
+				DeferCleanup(viper.Instance().Set, "indexImage", viper.Instance().GetString("indexImage"))
+				viper.Instance().Set("indexImage", "foo")
 				if val, isSet := os.LookupEnv("KUBECONFIG"); isSet {
 					DeferCleanup(os.Setenv, "KUBECONFIG", val)
 				} else {
@@ -151,8 +152,8 @@ var _ = Describe("Check Operator", func() {
 
 		Context("specifically, PFLT_INDEXIMAGE", func() {
 			BeforeEach(func() {
-				DeferCleanup(viper.Set, "indexImage", viper.GetString("indexImage"))
-				viper.Set("indexImage", "foo")
+				DeferCleanup(viper.Instance().Set, "indexImage", viper.Instance().GetString("indexImage"))
+				viper.Instance().Set("indexImage", "foo")
 			})
 			It("should not encounter an error if the value is set", func() {
 				err := ensureIndexImageConfigIsSet()
@@ -160,7 +161,7 @@ var _ = Describe("Check Operator", func() {
 			})
 
 			It("should encounter an error if the value is not set", func() {
-				viper.Set("indexImage", "")
+				viper.Instance().Set("indexImage", "")
 				err := ensureIndexImageConfigIsSet()
 				Expect(err).To(HaveOccurred())
 			})
@@ -173,8 +174,8 @@ var _ = Describe("Check Operator", func() {
 		// to prevent trying to run the entire RunE func in previous cases.
 		posArgs := []string{"firstparam"}
 		BeforeEach(func() {
-			DeferCleanup(viper.Set, "indexImage", viper.GetString("indexImage"))
-			viper.Set("indexImage", "foo")
+			DeferCleanup(viper.Instance().Set, "indexImage", viper.Instance().GetString("indexImage"))
+			viper.Instance().Set("indexImage", "foo")
 			if val, isSet := os.LookupEnv("KUBECONIFG"); isSet {
 				DeferCleanup(os.Setenv, "KUBECONFIG", val)
 			} else {
diff --git a/cmd/preflight/cmd/check_test.go b/cmd/preflight/cmd/check_test.go
index 616ae60..1920252 100644
--- a/cmd/preflight/cmd/check_test.go
+++ b/cmd/preflight/cmd/check_test.go
@@ -4,10 +4,10 @@ import (
 	"os"
 
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/lib"
+	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/viper"
 
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
-	"github.com/spf13/viper"
 )
 
 var _ = Describe("cmd package check command", func() {
@@ -19,8 +19,8 @@ var _ = Describe("cmd package check command", func() {
 			imageID   = "my-image-id"
 		)
 		BeforeEach(func() {
-			viper.SetEnvPrefix("pflt")
-			viper.AutomaticEnv()
+			viper.Instance().SetEnvPrefix("pflt")
+			viper.Instance().AutomaticEnv()
 		})
 		AfterEach(func() {
 			os.Unsetenv("PFLT_PYXIS_ENV")
diff --git a/cmd/preflight/cmd/root_test.go b/cmd/preflight/cmd/root_test.go
index 5bacc35..d687610 100644
--- a/cmd/preflight/cmd/root_test.go
+++ b/cmd/preflight/cmd/root_test.go
@@ -8,12 +8,12 @@ import (
 
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/artifacts"
 	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/cli"
+	"github.com/redhat-openshift-ecosystem/openshift-preflight/internal/viper"
 
 	"github.com/go-logr/logr"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 	"github.com/spf13/cobra"
-	"github.com/spf13/viper"
 )
 
 // executeCommand is used for cobra command testing. It is effectively what's seen here:
@@ -75,10 +75,10 @@ var _ = Describe("cmd package utility functions", func() {
 			Context("and no envvars are set", func() {
 				It("should have defaults set correctly", func() {
 					initConfig()
-					Expect(viper.GetString("namespace")).To(Equal(DefaultNamespace))
-					Expect(viper.GetString("artifacts")).To(Equal(artifacts.DefaultArtifactsDir))
-					Expect(viper.GetString("logfile")).To(Equal(DefaultLogFile))
-					Expect(viper.GetString("loglevel")).To(Equal(DefaultLogLevel))
+					Expect(viper.Instance().GetString("namespace")).To(Equal(DefaultNamespace))
+					Expect(viper.Instance().GetString("artifacts")).To(Equal(artifacts.DefaultArtifactsDir))
+					Expect(viper.Instance().GetString("logfile")).To(Equal(DefaultLogFile))
+					Expect(viper.Instance().GetString("loglevel")).To(Equal(DefaultLogLevel))
 				})
 			})
 			Context("and envvars are set", func() {
@@ -88,10 +88,10 @@ var _ = Describe("cmd package utility functions", func() {
 				})
 				It("should have overrides in place", func() {
 					initConfig()
-					Expect(viper.GetString("namespace")).To(Equal(DefaultNamespace))
-					Expect(viper.GetString("artifacts")).To(Equal(artifacts.DefaultArtifactsDir))
-					Expect(viper.GetString("logfile")).To(Equal("/tmp/foo.log"))
-					Expect(viper.GetString("loglevel")).To(Equal("trace"))
+					Expect(viper.Instance().GetString("namespace")).To(Equal(DefaultNamespace))
+					Expect(viper.Instance().GetString("artifacts")).To(Equal(artifacts.DefaultArtifactsDir))
+					Expect(viper.Instance().GetString("logfile")).To(Equal("/tmp/foo.log"))
+					Expect(viper.Instance().GetString("loglevel")).To(Equal("trace"))
 				})
 				AfterEach(func() {
 					os.Unsetenv("PFLT_LOGFILE")
@@ -118,7 +118,7 @@ var _ = Describe("cmd package utility functions", func() {
 				DeferCleanup(os.RemoveAll, tmpDir)
 			})
 			It("should create the logfile", func() {
-				viper.Set("logfile", filepath.Join(tmpDir, "foo.log"))
+				viper.Instance().Set("logfile", filepath.Join(tmpDir, "foo.log"))
 				Expect(cmd.ExecuteContext(context.TODO())).To(Succeed())
 				_, err := os.Stat(filepath.Join(tmpDir, "foo.log"))
 				Expect(err).ToNot(HaveOccurred())
diff --git a/internal/viper/viper_suite_test.go b/internal/viper/viper_suite_test.go
new file mode 100644
index 0000000..b409fd9
--- /dev/null
+++ b/internal/viper/viper_suite_test.go
@@ -0,0 +1,13 @@
+package viper
+
+import (
+	"testing"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+)
+
+func TestViper(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Package-Local Viper")
+}
diff --git a/internal/viper/viper_test.go b/internal/viper/viper_test.go
new file mode 100644
index 0000000..148d1a1
--- /dev/null
+++ b/internal/viper/viper_test.go
@@ -0,0 +1,28 @@
+package viper
+
+import (
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+)
+
+var _ = Describe("Viper tests", func() {
+	Context("Lazy Loading Viper", func() {
+		When("the viper instance hasn't been initialized", func() {
+			instance = nil
+			It("should be initialized when calling for it", func() {
+				_ = Instance() // we don't care about this return value for this test.
+				Expect(instance).ToNot(BeNil())
+			})
+		})
+	})
+
+	Context("Getting the project-specific Viper instance", func() {
+		When("Requesting the viper instance for the project", func() {
+			It("Should return a non-empty viper instance", func() {
+				packageV := Instance()
+				packageV.Set("foo", "bar")
+				Expect(Instance().Get("foo")).To(Equal("bar"))
+			})
+		})
+	})
+})