Repository: sonatype-nexus-community/nancy
Pull request #224 (2021-03-15 20:44:31)

Config via env
Flip the switches required to make Viper read environment variables for configuration values.

cc @bhamail / @DarthHater



Patch:
diff --git a/README.md b/README.md
index 3966835..8c196f5 100644
--- a/README.md
+++ b/README.md
@@ -221,6 +221,15 @@ You can also set the user and token via the command line like so:
 
 This can be handy for testing your account out, or if you want to override your set config with a different user.
 
+As of Nancy v1.0.17, you can also specify configuration values using environment variables:
+
+```shell
+export OSSI_USERNAME=auser@anemailaddress.com
+export OSSI_TOKEN=A4@k3@p1T0k3n
+go list -json -m all | ./nancy sleuth
+...
+```
+
 #### Loud mode
 
 By default, `nancy` runs in a "quiet" mode, only displaying a list of vulnerable components.
@@ -494,6 +503,18 @@ To set your Nexus IQ Server config run:
 
 Choose `iq` as an option and run through the rest of the config. Once you are done, Nancy should use this config for communicating with Nexus IQ, simplifying your use of the tool.
 
+As of Nancy v1.0.17, you can also specify configuration values using environment variables:
+
+```shell
+export OSSI_USERNAME=auser@anemailaddress.com
+export OSSI_TOKEN=A4@k3@p1T0k3n
+export IQ_USERNAME=nondefaultuser
+export IQ_TOKEN=yourtoken
+export IQ_SERVER=http://adifferentserverurl:port
+go list -json -m all | ./nancy iq --iq-application public-application-id
+...
+```
+
 ### Usage in CI
 
 You can see an example of using `nancy` in Travis-CI at [this intentionally vulnerable repo we made](https://github.com/sonatype-nexus-community/intentionally-vulnerable-golang-project).
diff --git a/internal/cmd/root.go b/internal/cmd/root.go
index 9d6b7c5..96120d1 100644
--- a/internal/cmd/root.go
+++ b/internal/cmd/root.go
@@ -100,6 +100,15 @@ var (
 	stdInInvalid                                   = fmt.Errorf("StdIn is invalid or empty. Did you forget to pipe 'go list' to nancy?")
 )
 
+//Substitute the _ to .
+var viperKeyReplacer = strings.NewReplacer(".", "_")
+
+func setupViperAutomaticEnv() {
+	viper.AutomaticEnv()
+	//Substitute the _ to .
+	viper.SetEnvKeyReplacer(viperKeyReplacer)
+}
+
 var rootCmd = &cobra.Command{
 	Version: buildversion.BuildVersion,
 	Use:     "nancy",
@@ -116,6 +125,7 @@ var rootCmd = &cobra.Command{
 powered by the 'Sonatype OSS Index', and as well, works with Nexus IQ Server, allowing you
 a smooth experience as a Golang developer, using the best tools in the market!`,
 	PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
+		setupViperAutomaticEnv()
 		logLady = logger.GetLogger("", configOssi.LogLevel)
 		return checkForUpdates("")
 	},


Test patch:
diff --git a/internal/cmd/root_test.go b/internal/cmd/root_test.go
index 0c3f5dd..047f4e1 100644
--- a/internal/cmd/root_test.go
+++ b/internal/cmd/root_test.go
@@ -525,3 +525,8 @@ func TestCleanUserName(t *testing.T) {
 	assert.Equal(t, "1***hidden***1", cleanUserName("1"))
 	assert.Equal(t, "1***hidden***2", cleanUserName("12"))
 }
+
+func TestViperKeyNameReplacer(t *testing.T) {
+	envVarName := viperKeyReplacer.Replace(configuration.ViperKeyUsername)
+	assert.Equal(t, "ossi_Username", envVarName)
+}
diff --git a/internal/cmd/sleuth_test.go b/internal/cmd/sleuth_test.go
index 0c43121..6ebc57b 100644
--- a/internal/cmd/sleuth_test.go
+++ b/internal/cmd/sleuth_test.go
@@ -18,6 +18,9 @@ package cmd
 
 import (
 	"fmt"
+	"github.com/sirupsen/logrus/hooks/test"
+	"github.com/sonatype-nexus-community/go-sona-types/configuration"
+	"github.com/sonatype-nexus-community/go-sona-types/ossindex"
 	"github.com/sonatype-nexus-community/nancy/internal/audit"
 	"github.com/sonatype-nexus-community/nancy/internal/customerrors"
 	"github.com/sonatype-nexus-community/nancy/types"
@@ -172,3 +175,33 @@ func TestConfigOssi_output_of_bad_value(t *testing.T) {
 	validateConfigOssi(t, types.Configuration{Formatter: defaultAuditLogFormatter},
 		[]string{sleuthCmd.Use, "--output=aintgonnadoit"}...)
 }
+
+const testEnvVarValue = "myUnlikelyTestEnvVarValue"
+
+func createServerViaEnv(t *testing.T, viperKey, expectedEnvVarName string) *ossindex.Server {
+	envVarName := strings.ToUpper(viperKeyReplacer.Replace(viperKey))
+	assert.Equal(t, expectedEnvVarName, envVarName)
+
+	origEnvVarValue := os.Getenv(envVarName)
+	defer func() {
+		if origEnvVarValue == "" {
+			assert.Nil(t, os.Unsetenv(envVarName))
+		} else {
+			assert.Nil(t, os.Setenv(envVarName, origEnvVarValue))
+		}
+	}()
+
+	assert.Nil(t, os.Setenv(envVarName, testEnvVarValue))
+
+	logLady, _ = test.NewNullLogger()
+	// force call to enable automatic environment variable feature in viper
+	setupViperAutomaticEnv()
+	ossIndex := ossiCreator.create()
+	server := ossIndex.(*ossindex.Server)
+	return server
+}
+
+func TestConfigOssiUserViaEnv(t *testing.T) {
+	assert.Equal(t, testEnvVarValue, createServerViaEnv(t, configuration.ViperKeyUsername, "OSSI_USERNAME").Options.Username)
+	assert.Equal(t, testEnvVarValue, createServerViaEnv(t, configuration.ViperKeyToken, "OSSI_TOKEN").Options.Token)
+}