diff --git a/cmd/config/add_profile_test.go b/cmd/config/add_profile_test.go index 02e4c489..ddb57223 100644 --- a/cmd/config/add_profile_test.go +++ b/cmd/config/add_profile_test.go @@ -51,7 +51,7 @@ func TestConfigAddProfileCmd_DuplicateProfileName(t *testing.T) { // Test config add profile command fails when provided an invalid profile name func TestConfigAddProfileCmd_InvalidProfileName(t *testing.T) { - expectedErrorPattern := `^failed to add profile: invalid profile name: '.*'\. name must be lowercase and contain only alphanumeric characters, underscores, and dashes$` + expectedErrorPattern := `^failed to add profile: invalid profile name: '.*'\. name must contain only alphanumeric characters, underscores, and dashes$` err := testutils_cobra.ExecutePingcli(t, "config", "add-profile", "--name", "pname&*^*&^$&@!", "--description", "test description", @@ -59,16 +59,6 @@ func TestConfigAddProfileCmd_InvalidProfileName(t *testing.T) { testutils.CheckExpectedError(t, err, &expectedErrorPattern) } -// Test Root Command fails when provided an invalid value containing uppercase character for profile name -func TestConfigAddProfileCmd_InvalidUpperCaseProfileName(t *testing.T) { - expectedErrorPattern := `^failed to add profile: invalid profile name: '.*'\. name must be lowercase and contain only alphanumeric characters, underscores, and dashes$` - err := testutils_cobra.ExecutePingcli(t, "config", "add-profile", - "--name", "myProfile", - "--description", "test description", - "--set-active=false") - testutils.CheckExpectedError(t, err, &expectedErrorPattern) -} - // Test config add profile command fails when provided an invalid set-active value func TestConfigAddProfileCmd_InvalidSetActiveValue(t *testing.T) { expectedErrorPattern := `^invalid argument ".*" for "-s, --set-active" flag: strconv\.ParseBool: parsing ".*": invalid syntax$` diff --git a/cmd/config/config_test.go b/cmd/config/config_test.go index 5272e5c6..73bb5214 100644 --- a/cmd/config/config_test.go +++ b/cmd/config/config_test.go @@ -56,7 +56,7 @@ func TestConfigCmd_HelpFlag(t *testing.T) { // // Test Config Command fails when provided an invalid profile name // func TestConfigCmd_InvalidProfileName(t *testing.T) { -// expectedErrorPattern := `^failed to update profile '.*' name to: .*\. invalid profile name: '.*'\. name must be lowercase and contain only alphanumeric characters, underscores, and dashes$` +// expectedErrorPattern := `^failed to update profile '.*' name to: .*\. invalid profile name: '.*'\. name must contain only alphanumeric characters, underscores, and dashes$` // err := testutils_cobra.ExecutePingcli(t, "config", // "--profile", "production", // "--name", "pname&*^*&^$&@!", diff --git a/cmd/config/get.go b/cmd/config/get.go index b63c427f..766b6cdc 100644 --- a/cmd/config/get.go +++ b/cmd/config/get.go @@ -18,7 +18,7 @@ const ( pingcli config get --profile myprofile noColor Read the worker ID used to authenticate to the PingOne service management API. - pingcli config get service.pingone.authentication.worker.environmentID + pingcli config get service.pingOne.authentication.worker.environmentID Read the unmasked value for the request access token. pingcli config get --unmask-values request.accessToken` @@ -35,7 +35,7 @@ func NewConfigGetCommand() *cobra.Command { RunE: configGetRunE, Short: "Read stored configuration settings for the CLI.", Use: "get [flags] key", - ValidArgs: configuration.ViperKeys(), + ValidArgs: configuration.KoanfKeys(), } return cmd diff --git a/cmd/config/get_test.go b/cmd/config/get_test.go index bab8ee18..ca433eb4 100644 --- a/cmd/config/get_test.go +++ b/cmd/config/get_test.go @@ -19,19 +19,19 @@ func TestConfigGetCmd_Execute(t *testing.T) { // Test Config Get Command fails when provided too many arguments func TestConfigGetCmd_TooManyArgs(t *testing.T) { expectedErrorPattern := `^failed to execute 'pingcli config get': command accepts 1 arg\(s\), received 2$` - err := testutils_cobra.ExecutePingcli(t, "config", "get", options.RootColorOption.ViperKey, options.RootOutputFormatOption.ViperKey) + err := testutils_cobra.ExecutePingcli(t, "config", "get", options.RootColorOption.KoanfKey, options.RootOutputFormatOption.KoanfKey) testutils.CheckExpectedError(t, err, &expectedErrorPattern) } // Test Config Get Command Executes when provided a full key func TestConfigGetCmd_FullKey(t *testing.T) { - err := testutils_cobra.ExecutePingcli(t, "config", "get", options.PingOneAuthenticationWorkerClientIDOption.ViperKey) + err := testutils_cobra.ExecutePingcli(t, "config", "get", options.PingOneAuthenticationWorkerClientIDOption.KoanfKey) testutils.CheckExpectedError(t, err, nil) } // Test Config Get Command Executes when provided a partial key func TestConfigGetCmd_PartialKey(t *testing.T) { - err := testutils_cobra.ExecutePingcli(t, "config", "get", "service.pingone") + err := testutils_cobra.ExecutePingcli(t, "config", "get", "service.pingOne") testutils.CheckExpectedError(t, err, nil) } @@ -68,7 +68,7 @@ func TestConfigGetCmd_NoKey(t *testing.T) { // https://pkg.go.dev/testing#hdr-Examples func Example_getEmptyMaskedValue() { t := testing.T{} - _ = testutils_cobra.ExecutePingcli(&t, "config", "get", options.RequestAccessTokenOption.ViperKey) + _ = testutils_cobra.ExecutePingcli(&t, "config", "get", options.RequestAccessTokenOption.KoanfKey) // Output: // Configuration values for profile 'default' and key 'request.accessToken': @@ -79,17 +79,17 @@ func Example_getEmptyMaskedValue() { // https://pkg.go.dev/testing#hdr-Examples func Example_getMaskedValue() { t := testing.T{} - _ = testutils_cobra.ExecutePingcli(&t, "config", "get", options.PingFederateBasicAuthPasswordOption.ViperKey) + _ = testutils_cobra.ExecutePingcli(&t, "config", "get", options.PingFederateBasicAuthPasswordOption.KoanfKey) // Output: - // Configuration values for profile 'default' and key 'service.pingfederate.authentication.basicAuth.password': - // service.pingfederate.authentication.basicAuth.password=******** + // Configuration values for profile 'default' and key 'service.pingFederate.authentication.basicAuth.password': + // service.pingFederate.authentication.basicAuth.password=******** } // https://pkg.go.dev/testing#hdr-Examples func Example_getUnmaskedValue() { t := testing.T{} - _ = testutils_cobra.ExecutePingcli(&t, "config", "get", options.RootColorOption.ViperKey) + _ = testutils_cobra.ExecutePingcli(&t, "config", "get", options.RootColorOption.KoanfKey) // Output: // Configuration values for profile 'default' and key 'noColor': @@ -99,7 +99,7 @@ func Example_getUnmaskedValue() { // https://pkg.go.dev/testing#hdr-Examples func Example_get_UnmaskValuesFlag() { t := testing.T{} - _ = testutils_cobra.ExecutePingcli(&t, "config", "get", "--unmask-values", options.RequestAccessTokenOption.ViperKey) + _ = testutils_cobra.ExecutePingcli(&t, "config", "get", "--unmask-values", options.RequestAccessTokenOption.KoanfKey) // Output: // Configuration values for profile 'default' and key 'request.accessToken': diff --git a/cmd/config/list_keys_test.go b/cmd/config/list_keys_test.go index aecd0635..46ac6dc8 100644 --- a/cmd/config/list_keys_test.go +++ b/cmd/config/list_keys_test.go @@ -37,7 +37,7 @@ func TestConfigListKeysCmd_HelpFlag(t *testing.T) { // Test Config List Keys Command fails when provided too many arguments func TestConfigListKeysCmd_TooManyArgs(t *testing.T) { expectedErrorPattern := `^failed to execute 'pingcli config list-keys': command accepts 0 arg\(s\), received 1$` - err := testutils_cobra.ExecutePingcli(t, "config", "list-keys", options.RootColorOption.ViperKey) + err := testutils_cobra.ExecutePingcli(t, "config", "list-keys", options.RootColorOption.KoanfKey) testutils.CheckExpectedError(t, err, &expectedErrorPattern) } @@ -54,7 +54,7 @@ func Example_listKeysValue() { // - export.format // - export.outputDirectory // - export.overwrite - // - export.pingone.environmentID + // - export.pingOne.environmentID // - export.serviceGroup // - export.services // - noColor @@ -63,22 +63,22 @@ func Example_listKeysValue() { // - request.accessTokenExpiry // - request.fail // - request.service - // - service.pingfederate.adminAPIPath - // - service.pingfederate.authentication.accessTokenAuth.accessToken - // - service.pingfederate.authentication.basicAuth.password - // - service.pingfederate.authentication.basicAuth.username - // - service.pingfederate.authentication.clientCredentialsAuth.clientID - // - service.pingfederate.authentication.clientCredentialsAuth.clientSecret - // - service.pingfederate.authentication.clientCredentialsAuth.scopes - // - service.pingfederate.authentication.clientCredentialsAuth.tokenURL - // - service.pingfederate.authentication.type - // - service.pingfederate.caCertificatePemFiles - // - service.pingfederate.httpsHost - // - service.pingfederate.insecureTrustAllTLS - // - service.pingfederate.xBypassExternalValidationHeader - // - service.pingone.authentication.type - // - service.pingone.authentication.worker.clientID - // - service.pingone.authentication.worker.clientSecret - // - service.pingone.authentication.worker.environmentID - // - service.pingone.regionCode + // - service.pingFederate.adminAPIPath + // - service.pingFederate.authentication.accessTokenAuth.accessToken + // - service.pingFederate.authentication.basicAuth.password + // - service.pingFederate.authentication.basicAuth.username + // - service.pingFederate.authentication.clientCredentialsAuth.clientID + // - service.pingFederate.authentication.clientCredentialsAuth.clientSecret + // - service.pingFederate.authentication.clientCredentialsAuth.scopes + // - service.pingFederate.authentication.clientCredentialsAuth.tokenURL + // - service.pingFederate.authentication.type + // - service.pingFederate.caCertificatePEMFiles + // - service.pingFederate.httpsHost + // - service.pingFederate.insecureTrustAllTLS + // - service.pingFederate.xBypassExternalValidationHeader + // - service.pingOne.authentication.type + // - service.pingOne.authentication.worker.clientID + // - service.pingOne.authentication.worker.clientSecret + // - service.pingOne.authentication.worker.environmentID + // - service.pingOne.regionCode } diff --git a/cmd/config/set.go b/cmd/config/set.go index 611d4bd2..890a97cf 100644 --- a/cmd/config/set.go +++ b/cmd/config/set.go @@ -15,10 +15,10 @@ const ( pingcli config set color=true Set the PingOne tenant region code setting to 'AP' for the profile named 'myprofile'. - pingcli config set --profile myprofile service.pingone.regionCode=AP + pingcli config set --profile myprofile service.pingOne.regionCode=AP Set the PingFederate basic authentication password with unmasked output - pingcli config set --profile myprofile --unmask-values service.pingfederate.authentication.basicAuth.password=1234` + pingcli config set --profile myprofile --unmask-values service.pingFederate.authentication.basicAuth.password=1234` ) func NewConfigSetCommand() *cobra.Command { @@ -32,7 +32,7 @@ func NewConfigSetCommand() *cobra.Command { RunE: configSetRunE, Short: "Set stored configuration settings for the CLI.", Use: "set [flags] key=value", - ValidArgs: configuration.ViperKeys(), + ValidArgs: configuration.KoanfKeys(), } return cmd diff --git a/cmd/config/set_test.go b/cmd/config/set_test.go index 3058f566..e8a6cb94 100644 --- a/cmd/config/set_test.go +++ b/cmd/config/set_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/pingidentity/pingcli/internal/configuration/options" + "github.com/pingidentity/pingcli/internal/customtypes" "github.com/pingidentity/pingcli/internal/profiles" "github.com/pingidentity/pingcli/internal/testing/testutils" "github.com/pingidentity/pingcli/internal/testing/testutils_cobra" @@ -14,7 +15,7 @@ import ( // Test Config Set Command Executes without issue func TestConfigSetCmd_Execute(t *testing.T) { - err := testutils_cobra.ExecutePingcli(t, "config", "set", fmt.Sprintf("%s=false", options.RootColorOption.ViperKey)) + err := testutils_cobra.ExecutePingcli(t, "config", "set", fmt.Sprintf("%s=false", options.RootColorOption.KoanfKey)) testutils.CheckExpectedError(t, err, nil) } @@ -28,7 +29,7 @@ func TestConfigSetCmd_TooFewArgs(t *testing.T) { // Test Config Set Command Fails when provided too many arguments func TestConfigSetCmd_TooManyArgs(t *testing.T) { expectedErrorPattern := `^failed to execute 'pingcli config set': command accepts 1 arg\(s\), received 2$` - err := testutils_cobra.ExecutePingcli(t, "config", "set", fmt.Sprintf("%s=false", options.RootColorOption.ViperKey), fmt.Sprintf("%s=true", options.RootColorOption.ViperKey)) + err := testutils_cobra.ExecutePingcli(t, "config", "set", fmt.Sprintf("%s=false", options.RootColorOption.KoanfKey), fmt.Sprintf("%s=true", options.RootColorOption.KoanfKey)) testutils.CheckExpectedError(t, err, &expectedErrorPattern) } @@ -42,31 +43,31 @@ func TestConfigSetCmd_InvalidKey(t *testing.T) { // Test Config Set Command Fails when an invalid value type is provided func TestConfigSetCmd_InvalidValueType(t *testing.T) { expectedErrorPattern := `^failed to set configuration: value for key '.*' must be a boolean\. Allowed .*: strconv\.ParseBool: parsing ".*": invalid syntax$` - err := testutils_cobra.ExecutePingcli(t, "config", "set", fmt.Sprintf("%s=invalid", options.RootColorOption.ViperKey)) + err := testutils_cobra.ExecutePingcli(t, "config", "set", fmt.Sprintf("%s=invalid", options.RootColorOption.KoanfKey)) testutils.CheckExpectedError(t, err, &expectedErrorPattern) } // Test Config Set Command Fails when no value is provided func TestConfigSetCmd_NoValueProvided(t *testing.T) { expectedErrorPattern := `^failed to set configuration: value for key '.*' is empty\. Use 'pingcli config unset .*' to unset the key$` - err := testutils_cobra.ExecutePingcli(t, "config", "set", fmt.Sprintf("%s=", options.RootColorOption.ViperKey)) + err := testutils_cobra.ExecutePingcli(t, "config", "set", fmt.Sprintf("%s=", options.RootColorOption.KoanfKey)) testutils.CheckExpectedError(t, err, &expectedErrorPattern) } -// Test Config Set Command for key 'pingone.worker.clientId' updates viper configuration -func TestConfigSetCmd_CheckViperConfig(t *testing.T) { - viperKey := options.PingOneAuthenticationWorkerClientIDOption.ViperKey - viperNewUUID := "12345678-1234-1234-1234-123456789012" +// Test Config Set Command for key 'pingone.worker.clientId' updates koanf configuration +func TestConfigSetCmd_CheckKoanfConfig(t *testing.T) { + koanfKey := options.PingOneAuthenticationWorkerClientIDOption.KoanfKey + koanfNewUUID := "12345678-1234-1234-1234-123456789012" - err := testutils_cobra.ExecutePingcli(t, "config", "set", fmt.Sprintf("%s=%s", viperKey, viperNewUUID)) + err := testutils_cobra.ExecutePingcli(t, "config", "set", fmt.Sprintf("%s=%s", koanfKey, koanfNewUUID)) testutils.CheckExpectedError(t, err, nil) - mainViper := profiles.GetMainConfig().ViperInstance() - profileViperKey := "default." + viperKey + koanf := profiles.GetKoanfConfig().KoanfInstance() + profileKoanfKey := "default." + koanfKey - viperNewValue := mainViper.GetString(profileViperKey) - if viperNewValue != viperNewUUID { - t.Errorf("Expected viper configuration value to be updated") + koanfNewValue, ok := koanf.Get(profileKoanfKey).(*customtypes.UUID) + if ok && koanfNewValue.String() != koanfNewUUID { + t.Errorf("Expected koanf configuration value to be updated") } } @@ -89,27 +90,27 @@ func TestConfigSetCmd_InvalidFlag(t *testing.T) { // https://pkg.go.dev/testing#hdr-Examples func Example_setMaskedValue() { t := testing.T{} - _ = testutils_cobra.ExecutePingcli(&t, "config", "set", fmt.Sprintf("%s=%s", options.PingFederateBasicAuthPasswordOption.ViperKey, "1234")) + _ = testutils_cobra.ExecutePingcli(&t, "config", "set", fmt.Sprintf("%s=%s", options.PingFederateBasicAuthPasswordOption.KoanfKey, "1234")) // Output: // SUCCESS: Configuration set successfully: - // service.pingfederate.authentication.basicAuth.password=******** + // service.pingFederate.authentication.basicAuth.password=******** } // https://pkg.go.dev/testing#hdr-Examples func Example_set_UnmaskedValuesFlag() { t := testing.T{} - _ = testutils_cobra.ExecutePingcli(&t, "config", "set", "--unmask-values", fmt.Sprintf("%s=%s", options.PingFederateBasicAuthPasswordOption.ViperKey, "1234")) + _ = testutils_cobra.ExecutePingcli(&t, "config", "set", "--unmask-values", fmt.Sprintf("%s=%s", options.PingFederateBasicAuthPasswordOption.KoanfKey, "1234")) // Output: // SUCCESS: Configuration set successfully: - // service.pingfederate.authentication.basicAuth.password=1234 + // service.pingFederate.authentication.basicAuth.password=1234 } // https://pkg.go.dev/testing#hdr-Examples func Example_setUnmaskedValue() { t := testing.T{} - _ = testutils_cobra.ExecutePingcli(&t, "config", "set", fmt.Sprintf("%s=%s", options.RootColorOption.ViperKey, "true")) + _ = testutils_cobra.ExecutePingcli(&t, "config", "set", fmt.Sprintf("%s=%s", options.RootColorOption.KoanfKey, "true")) // Output: // SUCCESS: Configuration set successfully: diff --git a/cmd/config/unset.go b/cmd/config/unset.go index 12c9c323..40e0f880 100644 --- a/cmd/config/unset.go +++ b/cmd/config/unset.go @@ -15,7 +15,7 @@ const ( pingcli config unset color Unset the PingOne tenant region code setting for the profile named 'myprofile'. - pingcli config unset --profile myprofile service.pingone.regionCode` + pingcli config unset --profile myprofile service.pingOne.regionCode` ) func NewConfigUnsetCommand() *cobra.Command { @@ -29,7 +29,7 @@ func NewConfigUnsetCommand() *cobra.Command { RunE: configUnsetRunE, Short: "Unset stored configuration settings for the CLI.", Use: "unset [flags] key", - ValidArgs: configuration.ViperKeys(), + ValidArgs: configuration.KoanfKeys(), } return cmd diff --git a/cmd/config/unset_test.go b/cmd/config/unset_test.go index 9345c81b..f6d7cc0c 100644 --- a/cmd/config/unset_test.go +++ b/cmd/config/unset_test.go @@ -9,12 +9,12 @@ import ( "github.com/pingidentity/pingcli/internal/profiles" "github.com/pingidentity/pingcli/internal/testing/testutils" "github.com/pingidentity/pingcli/internal/testing/testutils_cobra" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" ) // Test Config Unset Command Executes without issue func TestConfigUnsetCmd_Execute(t *testing.T) { - err := testutils_cobra.ExecutePingcli(t, "config", "unset", options.RootColorOption.ViperKey) + err := testutils_cobra.ExecutePingcli(t, "config", "unset", options.RootColorOption.KoanfKey) testutils.CheckExpectedError(t, err, nil) } @@ -28,7 +28,7 @@ func TestConfigUnsetCmd_TooFewArgs(t *testing.T) { // Test Config Set Command Fails when provided too many arguments func TestConfigUnsetCmd_TooManyArgs(t *testing.T) { expectedErrorPattern := `^failed to execute 'pingcli config unset': command accepts 1 arg\(s\), received 2$` - err := testutils_cobra.ExecutePingcli(t, "config", "unset", options.RootColorOption.ViperKey, options.RootOutputFormatOption.ViperKey) + err := testutils_cobra.ExecutePingcli(t, "config", "unset", options.RootColorOption.KoanfKey, options.RootOutputFormatOption.KoanfKey) testutils.CheckExpectedError(t, err, &expectedErrorPattern) } @@ -39,27 +39,26 @@ func TestConfigUnsetCmd_InvalidKey(t *testing.T) { testutils.CheckExpectedError(t, err, &expectedErrorPattern) } -// Test Config Unset Command for key 'pingone.worker.clientId' updates viper configuration -func TestConfigUnsetCmd_CheckViperConfig(t *testing.T) { - testutils_viper.InitVipers(t) +// Test Config Unset Command for key 'pingone.worker.clientId' updates koanf configuration +func TestConfigUnsetCmd_CheckKoanfConfig(t *testing.T) { + testutils_koanf.InitKoanfs(t) - mainViper := profiles.GetMainConfig().ViperInstance() - viperKey := options.PingOneAuthenticationWorkerClientIDOption.ViperKey - profileViperKey := "default." + viperKey + koanfConfig := profiles.GetKoanfConfig().KoanfInstance() + koanfKey := options.PingOneAuthenticationWorkerClientIDOption.KoanfKey + profileKoanfKey := "default." + koanfKey - viperOldValue := mainViper.GetString(profileViperKey) + koanfOldValue := koanfConfig.String(profileKoanfKey) - err := testutils_cobra.ExecutePingcli(t, "config", "unset", viperKey) + err := testutils_cobra.ExecutePingcli(t, "config", "unset", koanfKey) testutils.CheckExpectedError(t, err, nil) - viperNewValue := mainViper.GetString(profileViperKey) - - if viperOldValue == viperNewValue { - t.Errorf("Expected viper configuration value to be updated. Old: %s, New: %s", viperOldValue, viperNewValue) + koanfNewValue := koanfConfig.String(profileKoanfKey) + if koanfOldValue == koanfNewValue { + t.Errorf("Expected koanf configuration value to be updated. Old: %s, New: %s", koanfOldValue, koanfNewValue) } - if viperNewValue != "" { - t.Errorf("Expected viper configuration value to be empty. Got: %s", viperNewValue) + if koanfNewValue != "" { + t.Errorf("Expected koanf configuration value to be empty. Got: %s", koanfNewValue) } } @@ -82,17 +81,17 @@ func TestConfigUnsetCmd_HelpFlag(t *testing.T) { // https://pkg.go.dev/testing#hdr-Examples func Example_unsetMaskedValue() { t := testing.T{} - _ = testutils_cobra.ExecutePingcli(&t, "config", "unset", options.PingFederateBasicAuthUsernameOption.ViperKey) + _ = testutils_cobra.ExecutePingcli(&t, "config", "unset", options.PingFederateBasicAuthUsernameOption.KoanfKey) // Output: // SUCCESS: Configuration unset successfully: - // service.pingfederate.authentication.basicAuth.username= + // service.pingFederate.authentication.basicAuth.username= } // https://pkg.go.dev/testing#hdr-Examples func Example_unsetUnmaskedValue() { t := testing.T{} - _ = testutils_cobra.ExecutePingcli(&t, "config", "unset", options.RootOutputFormatOption.ViperKey) + _ = testutils_cobra.ExecutePingcli(&t, "config", "unset", options.RootOutputFormatOption.KoanfKey) // Output: // SUCCESS: Configuration unset successfully: diff --git a/cmd/platform/export_test.go b/cmd/platform/export_test.go index 8bfe402f..0cafbb11 100644 --- a/cmd/platform/export_test.go +++ b/cmd/platform/export_test.go @@ -10,7 +10,7 @@ import ( "github.com/pingidentity/pingcli/internal/customtypes" "github.com/pingidentity/pingcli/internal/testing/testutils" "github.com/pingidentity/pingcli/internal/testing/testutils_cobra" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" ) // Test Platform Export Command Executes without issue @@ -25,7 +25,7 @@ func TestPlatformExportCmd_Execute(t *testing.T) { // Test Platform Export Command fails when provided too many arguments func TestPlatformExportCmd_TooManyArgs(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `^failed to execute 'pingcli platform export': command accepts 0 arg\(s\), received 1$` err := testutils_cobra.ExecutePingcli(t, "platform", "export", "extra-arg") diff --git a/cmd/root.go b/cmd/root.go index c441abaa..e549ffb4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,6 +7,8 @@ import ( "os" "path/filepath" + "github.com/knadh/koanf/parsers/yaml" + "github.com/knadh/koanf/providers/file" "github.com/pingidentity/pingcli/cmd/completion" "github.com/pingidentity/pingcli/cmd/config" "github.com/pingidentity/pingcli/cmd/feedback" @@ -19,7 +21,6 @@ import ( "github.com/pingidentity/pingcli/internal/output" "github.com/pingidentity/pingcli/internal/profiles" "github.com/spf13/cobra" - "github.com/spf13/viper" ) func init() { @@ -29,7 +30,7 @@ func init() { configuration.InitAllOptions() l.Debug().Msgf("Initializing Root command...") - cobra.OnInitialize(initViperProfile) + cobra.OnInitialize(initKoanfProfile) } // rootCmd represents the base command when called without any subcommands @@ -84,7 +85,7 @@ func NewRootCommand(version string, commit string) *cobra.Command { return cmd } -func initViperProfile() { +func initKoanfProfile() { l := logger.Get() cfgFile, err := profiles.GetOptionValue(options.RootConfigOption) @@ -99,13 +100,14 @@ func initViperProfile() { l.Debug().Msgf("Validated configuration file location at: %s", cfgFile) - // Configure the main viper instance - initMainViper(cfgFile) + // Configure the koanf instance + initKoanf(cfgFile) userDefinedProfile, err := profiles.GetOptionValue(options.RootProfileOption) if err != nil { output.SystemError(fmt.Sprintf("Failed to get user-defined profile: %v", err), nil) } + configFileActiveProfile, err := profiles.GetOptionValue(options.RootActiveProfileOption) if err != nil { output.SystemError(fmt.Sprintf("Failed to get active profile from configuration file: %v", err), nil) @@ -117,8 +119,8 @@ func initViperProfile() { l.Debug().Msgf("Using configuration profile: %s", configFileActiveProfile) } - // Configure the profile viper instance - if err := profiles.GetMainConfig().ChangeActiveProfile(configFileActiveProfile); err != nil { + // Configure the profile koanf instance + if err := profiles.GetKoanfConfig().ChangeActiveProfile(configFileActiveProfile); err != nil { output.UserFatal(fmt.Sprintf("Failed to set active profile: %v", err), nil) } @@ -154,46 +156,56 @@ func createConfigFile(cfgFile string) { output.SystemError(fmt.Sprintf("Failed to make the directory for the new configuration file '%s': %v", cfgFile, err), nil) } - tempViper := viper.New() - tempViper.Set(options.RootActiveProfileOption.ViperKey, "default") - tempViper.Set(fmt.Sprintf("default.%v", options.ProfileDescriptionOption.ViperKey), "Default profile created by Ping CLI") + tempKoanf := profiles.NewKoanfConfig(cfgFile) + err = tempKoanf.KoanfInstance().Set(options.RootActiveProfileOption.KoanfKey, "default") + if err != nil { + output.SystemError(fmt.Sprintf("Failed to set active profile in new configuration file '%s': %v", cfgFile, err), nil) + } + + err = tempKoanf.KoanfInstance().Set(fmt.Sprintf("default.%v", options.ProfileDescriptionOption.KoanfKey), "Default profile created by Ping CLI") + if err != nil { + output.SystemError(fmt.Sprintf("Failed to set default profile description in new configuration file '%s': %v", cfgFile, err), nil) + } - err = tempViper.WriteConfigAs(cfgFile) + err = tempKoanf.WriteFile() if err != nil { - output.SystemError(fmt.Sprintf("Failed to create configuration file '%s': %v", cfgFile, err), nil) + output.SystemError(fmt.Sprintf("Failed to create new configuration file '%s': %v", cfgFile, err), nil) } } -func initMainViper(cfgFile string) { +func initKoanf(cfgFile string) { l := logger.Get() - loadMainViperConfig(cfgFile) + loadKoanfConfig(cfgFile) // If there are no profiles in the configuration file, seed the default profile - if len(profiles.GetMainConfig().ProfileNames()) == 0 { + if len(profiles.GetKoanfConfig().ProfileNames()) == 0 { l.Debug().Msgf("No profiles found in configuration file. Creating default profile in configuration file '%s'", cfgFile) createConfigFile(cfgFile) - loadMainViperConfig(cfgFile) + loadKoanfConfig(cfgFile) } - err := profiles.GetMainConfig().DefaultMissingViperKeys() + err := profiles.GetKoanfConfig().DefaultMissingKoanfKeys() if err != nil { output.SystemError(err.Error(), nil) } } -func loadMainViperConfig(cfgFile string) { +func loadKoanfConfig(cfgFile string) { l := logger.Get() - mainViper := profiles.GetMainConfig().ViperInstance() - // Use config file from the flag. - mainViper.SetConfigFile(cfgFile) - mainViper.SetConfigType("yaml") + koanfConfig := profiles.GetKoanfConfig() + koanfConfig.SetKoanfConfigFile(cfgFile) - // If a config file is found, read it in. - if err := mainViper.ReadInConfig(); err != nil { - output.SystemError(fmt.Sprintf("Failed to read configuration from file '%s': %v", cfgFile, err), nil) + // Use config file from the flag. + if err := koanfConfig.KoanfInstance().Load(file.Provider(cfgFile), yaml.Parser()); err != nil { + output.SystemError(fmt.Sprintf("Failed to load configuration from file '%s': %v", cfgFile, err), nil) } else { - l.Info().Msgf("Using configuration file: %s", mainViper.ConfigFileUsed()) + l.Info().Msgf("Using configuration file: %s", cfgFile) + } + + _, err := koanfConfig.KoanfInstance().Marshal(yaml.Parser()) + if err != nil { + output.SystemError(fmt.Sprintf("Failed to marshal configuration file '%s': %v", cfgFile, err), nil) } } diff --git a/cmd/root_test.go b/cmd/root_test.go index f53a9d9e..9072a6d5 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -10,7 +10,7 @@ import ( "github.com/pingidentity/pingcli/internal/output" "github.com/pingidentity/pingcli/internal/testing/testutils" "github.com/pingidentity/pingcli/internal/testing/testutils_cobra" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" ) // Test Root Command Executes without issue @@ -139,7 +139,7 @@ func TestRootCmd_DetailedExitCodeFlag(t *testing.T) { // // Test Root Command Detailed Exit Code Flag with output Warn func TestRootCmd_DetailedExitCodeWarnLoggedFunc(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) t.Setenv(options.RootDetailedExitCodeOption.EnvVar, "true") output.Warn("test warning", nil) diff --git a/docs/tool-configuration/configuration-key.md b/docs/tool-configuration/configuration-key.md index 39dc8af5..e217ebe9 100644 --- a/docs/tool-configuration/configuration-key.md +++ b/docs/tool-configuration/configuration-key.md @@ -15,30 +15,30 @@ The following parameters can be configured in Ping CLI's static configuration fi | Config File Property | Type | Equivalent Parameter | Purpose | |---|---|---|---| -| service.pingfederate.adminAPIPath | ENUM_STRING | --pingfederate-admin-api-path | The PingFederate API URL path used to communicate with PingFederate's admin API.

Example: `/pf-admin-api/v1` | -| service.pingfederate.authentication.accessTokenAuth.accessToken | ENUM_STRING | --pingfederate-access-token | The PingFederate access token used to authenticate to the PingFederate admin API when using a custom OAuth 2.0 token method. | -| service.pingfederate.authentication.basicAuth.password | ENUM_STRING | --pingfederate-password | The PingFederate password used to authenticate to the PingFederate admin API when using basic authentication. | -| service.pingfederate.authentication.basicAuth.username | ENUM_STRING | --pingfederate-username | The PingFederate username used to authenticate to the PingFederate admin API when using basic authentication. Example: `administrator` | -| service.pingfederate.authentication.clientCredentialsAuth.clientID | ENUM_STRING | --pingfederate-client-id | The PingFederate OAuth client ID used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. | -| service.pingfederate.authentication.clientCredentialsAuth.clientSecret | ENUM_STRING | --pingfederate-client-secret | The PingFederate OAuth client secret used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. | -| service.pingfederate.authentication.clientCredentialsAuth.scopes | ENUM_STRING_SLICE | --pingfederate-scopes | The PingFederate OAuth scopes used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type.

Accepts a comma-separated string to delimit multiple scopes.

Example: `openid,profile` | -| service.pingfederate.authentication.clientCredentialsAuth.tokenURL | ENUM_STRING | --pingfederate-token-url | The PingFederate OAuth token URL used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. | -| service.pingfederate.authentication.type | ENUM_PINGFEDERATE_AUTH_TYPE | --pingfederate-authentication-type | The authentication type to use when connecting to the PingFederate admin API.

Options are: accessTokenAuth, basicAuth, clientCredentialsAuth.

Example: `basicAuth` | -| service.pingfederate.caCertificatePemFiles | ENUM_STRING_SLICE | --pingfederate-ca-certificate-pem-files | Relative or full paths to PEM-encoded certificate files to be trusted as root CAs when connecting to the PingFederate server over HTTPS.

Accepts a comma-separated string to delimit multiple PEM files. | -| service.pingfederate.httpsHost | ENUM_STRING | --pingfederate-https-host | The PingFederate HTTPS host used to communicate with PingFederate's admin API.

Example: `https://pingfederate-admin.bxretail.org` | -| service.pingfederate.insecureTrustAllTLS | ENUM_BOOL | --pingfederate-insecure-trust-all-tls | Trust any certificate when connecting to the PingFederate server admin API.

This is insecure and shouldn't be enabled outside of testing. | -| service.pingfederate.xBypassExternalValidationHeader | ENUM_BOOL | --pingfederate-x-bypass-external-validation-header | Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when using PingFederate's admin API). | -| service.pingone.authentication.type | ENUM_PINGONE_AUTH_TYPE | --pingone-authentication-type | The authentication type to use to authenticate to the PingOne management API.

Options are: worker.

Example: `worker` | -| service.pingone.authentication.worker.clientID | ENUM_UUID | --pingone-worker-client-id | The worker client ID used to authenticate to the PingOne management API. | -| service.pingone.authentication.worker.clientSecret | ENUM_STRING | --pingone-worker-client-secret | The worker client secret used to authenticate to the PingOne management API. | -| service.pingone.authentication.worker.environmentID | ENUM_UUID | --pingone-worker-environment-id | The ID of the PingOne environment that contains the worker client used to authenticate to the PingOne management API. | -| service.pingone.regionCode | ENUM_PINGONE_REGION_CODE | --pingone-region-code | The region code of the PingOne tenant.

Options are: AP, AU, CA, EU, NA.

Example: `NA` | +| service.pingFederate.adminAPIPath | ENUM_STRING | --pingfederate-admin-api-path | The PingFederate API URL path used to communicate with PingFederate's admin API.

Example: `/pf-admin-api/v1` | +| service.pingFederate.authentication.accessTokenAuth.accessToken | ENUM_STRING | --pingfederate-access-token | The PingFederate access token used to authenticate to the PingFederate admin API when using a custom OAuth 2.0 token method. | +| service.pingFederate.authentication.basicAuth.password | ENUM_STRING | --pingfederate-password | The PingFederate password used to authenticate to the PingFederate admin API when using basic authentication. | +| service.pingFederate.authentication.basicAuth.username | ENUM_STRING | --pingfederate-username | The PingFederate username used to authenticate to the PingFederate admin API when using basic authentication. Example: `administrator` | +| service.pingFederate.authentication.clientCredentialsAuth.clientID | ENUM_STRING | --pingfederate-client-id | The PingFederate OAuth client ID used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. | +| service.pingFederate.authentication.clientCredentialsAuth.clientSecret | ENUM_STRING | --pingfederate-client-secret | The PingFederate OAuth client secret used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. | +| service.pingFederate.authentication.clientCredentialsAuth.scopes | ENUM_STRING_SLICE | --pingfederate-scopes | The PingFederate OAuth scopes used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type.

Accepts a comma-separated string to delimit multiple scopes.

Example: `openid,profile` | +| service.pingFederate.authentication.clientCredentialsAuth.tokenURL | ENUM_STRING | --pingfederate-token-url | The PingFederate OAuth token URL used to authenticate to the PingFederate admin API when using the OAuth 2.0 client credentials grant type. | +| service.pingFederate.authentication.type | ENUM_PINGFEDERATE_AUTH_TYPE | --pingfederate-authentication-type | The authentication type to use when connecting to the PingFederate admin API.

Options are: accessTokenAuth, basicAuth, clientCredentialsAuth.

Example: `basicAuth` | +| service.pingFederate.caCertificatePEMFiles | ENUM_STRING_SLICE | --pingfederate-ca-certificate-pem-files | Relative or full paths to PEM-encoded certificate files to be trusted as root CAs when connecting to the PingFederate server over HTTPS.

Accepts a comma-separated string to delimit multiple PEM files. | +| service.pingFederate.httpsHost | ENUM_STRING | --pingfederate-https-host | The PingFederate HTTPS host used to communicate with PingFederate's admin API.

Example: `https://pingfederate-admin.bxretail.org` | +| service.pingFederate.insecureTrustAllTLS | ENUM_BOOL | --pingfederate-insecure-trust-all-tls | Trust any certificate when connecting to the PingFederate server admin API.

This is insecure and shouldn't be enabled outside of testing. | +| service.pingFederate.xBypassExternalValidationHeader | ENUM_BOOL | --pingfederate-x-bypass-external-validation-header | Bypass connection tests when configuring PingFederate (the X-BypassExternalValidation header when using PingFederate's admin API). | +| service.pingOne.authentication.type | ENUM_PINGONE_AUTH_TYPE | --pingone-authentication-type | The authentication type to use to authenticate to the PingOne management API.

Options are: worker.

Example: `worker` | +| service.pingOne.authentication.worker.clientID | ENUM_UUID | --pingone-worker-client-id | The worker client ID used to authenticate to the PingOne management API. | +| service.pingOne.authentication.worker.clientSecret | ENUM_STRING | --pingone-worker-client-secret | The worker client secret used to authenticate to the PingOne management API. | +| service.pingOne.authentication.worker.environmentID | ENUM_UUID | --pingone-worker-environment-id | The ID of the PingOne environment that contains the worker client used to authenticate to the PingOne management API. | +| service.pingOne.regionCode | ENUM_PINGONE_REGION_CODE | --pingone-region-code | The region code of the PingOne tenant.

Options are: AP, AU, CA, EU, NA.

Example: `NA` | #### Platform Export Properties | Config File Property | Type | Equivalent Parameter | Purpose | |---|---|---|---| -| export.format | ENUM_STRING | --format / -f | Specifies the export format.

Options are: HCL.

Example: `HCL` | +| export.format | ENUM_EXPORT_FORMAT | --format / -f | Specifies the export format.

Options are: HCL.

Example: `HCL` | | export.outputDirectory | ENUM_STRING | --output-directory / -d | Specifies the output directory for export. Example: `$HOME/pingcli-export` | | export.overwrite | ENUM_BOOL | --overwrite / -o | Overwrites the existing generated exports in output directory. | | export.pingone.environmentID | ENUM_UUID | --pingone-export-environment-id | The ID of the PingOne environment to export. Must be a valid PingOne UUID. | @@ -49,4 +49,5 @@ The following parameters can be configured in Ping CLI's static configuration fi | Config File Property | Type | Equivalent Parameter | Purpose | |---|---|---|---| +| request.fail | ENUM_BOOL | --fail / -f | Return non-zero exit code when HTTP custom request returns a failure status code. | | request.service | ENUM_REQUEST_SERVICE | --service / -s | The Ping service (configured in the active profile) to send the custom request to.

Options are: pingone.

Example: `pingone` | \ No newline at end of file diff --git a/docs/tool-configuration/example-configuration.md b/docs/tool-configuration/example-configuration.md index d25d64e2..cbd84787 100644 --- a/docs/tool-configuration/example-configuration.md +++ b/docs/tool-configuration/example-configuration.md @@ -1,7 +1,7 @@ ## Example Ping CLI configuration file ``` -activeprofile: default +activeProfile: default default: noColor: true description: Default profile created by pingcli diff --git a/go.mod b/go.mod index 2b83edcd..93cd0743 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,10 @@ tool ( require ( github.com/fatih/color v1.18.0 github.com/hashicorp/go-uuid v1.0.3 + github.com/knadh/koanf/parsers/yaml v0.1.0 + github.com/knadh/koanf/providers/confmap v0.1.0 + github.com/knadh/koanf/providers/file v1.1.2 + github.com/knadh/koanf/v2 v2.2.0 github.com/manifoldco/promptui v0.9.0 github.com/patrickcping/pingone-go-sdk-v2 v0.12.13 github.com/patrickcping/pingone-go-sdk-v2/authorize v0.8.0 @@ -20,7 +24,6 @@ require ( github.com/rs/zerolog v1.34.0 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 - github.com/spf13/viper v1.20.1 golang.org/x/mod v0.24.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -111,6 +114,7 @@ require ( github.com/kisielk/errcheck v1.8.0 // indirect github.com/kisielk/gotool v1.0.0 // indirect github.com/kkHAIKE/contextcheck v1.1.5 // indirect + github.com/knadh/koanf/maps v0.1.2 // indirect github.com/kulti/thelper v0.6.3 // indirect github.com/kunwardeep/paralleltest v1.0.10 // indirect github.com/lasiar/canonicalheader v1.1.2 // indirect @@ -129,7 +133,9 @@ require ( github.com/mattn/go-runewidth v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mgechev/revive v1.6.1 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moricho/tparallel v0.3.2 // indirect github.com/nakabonne/nestif v0.3.1 // indirect github.com/nishanths/exhaustive v0.12.0 // indirect @@ -170,6 +176,7 @@ require ( github.com/sourcegraph/go-diff v0.7.0 // indirect github.com/spf13/afero v1.12.0 // indirect github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/viper v1.20.1 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect github.com/stretchr/objx v0.5.2 // indirect diff --git a/go.sum b/go.sum index 74998115..99d4555b 100644 --- a/go.sum +++ b/go.sum @@ -337,6 +337,16 @@ github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= +github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo= +github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w= +github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY= +github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= +github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU= +github.com/knadh/koanf/providers/file v1.1.2 h1:aCC36YGOgV5lTtAFz2qkgtWdeQsgfxUkxDOe+2nQY3w= +github.com/knadh/koanf/providers/file v1.1.2/go.mod h1:/faSBcv2mxPVjFrXck95qeoyoZ5myJ6uxN8OOVNJJCI= +github.com/knadh/koanf/v2 v2.2.0 h1:FZFwd9bUjpb8DyCWARUBy5ovuhDs1lI87dOEn2K8UVU= +github.com/knadh/koanf/v2 v2.2.0/go.mod h1:PSFru3ufQgTsI7IF+95rf9s8XA1+aHxKuO/W+dPoHEY= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -391,8 +401,12 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgechev/revive v1.6.1 h1:ncK0ZCMWtb8GXwVAmk+IeWF2ULIDsvRxSRfg5sTwQ2w= github.com/mgechev/revive v1.6.1/go.mod h1:/2tfHWVO8UQi/hqJsIYNEKELi+DJy/e+PQpLgTB1v88= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 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/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= diff --git a/internal/autocompletion/config_args.go b/internal/autocompletion/config_args.go index 44dd2eba..237a9bd5 100644 --- a/internal/autocompletion/config_args.go +++ b/internal/autocompletion/config_args.go @@ -12,11 +12,11 @@ import ( ) func ConfigViewProfileFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return profiles.GetMainConfig().ProfileNames(), cobra.ShellCompDirectiveNoFileComp + return profiles.GetKoanfConfig().ProfileNames(), cobra.ShellCompDirectiveNoFileComp } func ConfigReturnNonActiveProfilesFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - profileNames := profiles.GetMainConfig().ProfileNames() + profileNames := profiles.GetKoanfConfig().ProfileNames() if len(args) != 0 { return nil, cobra.ShellCompDirectiveNoFileComp } diff --git a/internal/autocompletion/root_flags.go b/internal/autocompletion/root_flags.go index fc929f08..bea293f2 100644 --- a/internal/autocompletion/root_flags.go +++ b/internal/autocompletion/root_flags.go @@ -9,7 +9,7 @@ import ( ) func RootProfileFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { - return profiles.GetMainConfig().ProfileNames(), cobra.ShellCompDirectiveNoFileComp + return profiles.GetKoanfConfig().ProfileNames(), cobra.ShellCompDirectiveNoFileComp } func RootOutputFormatFunc(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { diff --git a/internal/commands/config/add_profile_internal.go b/internal/commands/config/add_profile_internal.go index 268d2792..0d9403bc 100644 --- a/internal/commands/config/add_profile_internal.go +++ b/internal/commands/config/add_profile_internal.go @@ -7,11 +7,11 @@ import ( "io" "strconv" + "github.com/knadh/koanf/v2" "github.com/pingidentity/pingcli/internal/configuration/options" "github.com/pingidentity/pingcli/internal/input" "github.com/pingidentity/pingcli/internal/output" "github.com/pingidentity/pingcli/internal/profiles" - "github.com/spf13/viper" ) func RunInternalConfigAddProfile(rc io.ReadCloser) (err error) { @@ -20,31 +20,34 @@ func RunInternalConfigAddProfile(rc io.ReadCloser) (err error) { return fmt.Errorf("failed to add profile: %w", err) } - err = profiles.GetMainConfig().ValidateNewProfileName(newProfileName) + err = profiles.GetKoanfConfig().ValidateNewProfileName(newProfileName) if err != nil { return fmt.Errorf("failed to add profile: %w", err) } output.Message(fmt.Sprintf("Adding new profile '%s'...", newProfileName), nil) - subViper := viper.New() - subViper.Set(options.ProfileDescriptionOption.ViperKey, newDescription) + subKoanf := koanf.New(".") + err = subKoanf.Set(options.ProfileDescriptionOption.KoanfKey, newDescription) + if err != nil { + return fmt.Errorf("failed to add profile: %w", err) + } - if err = profiles.GetMainConfig().SaveProfile(newProfileName, subViper); err != nil { + if err = profiles.GetKoanfConfig().SaveProfile(newProfileName, subKoanf); err != nil { return fmt.Errorf("failed to add profile: %w", err) } - output.Success(fmt.Sprintf("Profile created. Update additional profile attributes via 'pingcli config set' or directly within the config file at '%s'", profiles.GetMainConfig().ViperInstance().ConfigFileUsed()), nil) + output.Success(fmt.Sprintf("Profile created. Update additional profile attributes via 'pingcli config set' or directly within the config file at '%s'", profiles.GetKoanfConfig().GetKoanfConfigFile()), nil) if setActive { - if err = profiles.GetMainConfig().ChangeActiveProfile(newProfileName); err != nil { + if err = profiles.GetKoanfConfig().ChangeActiveProfile(newProfileName); err != nil { return fmt.Errorf("failed to set active profile: %w", err) } output.Success(fmt.Sprintf("Profile '%s' set as active.", newProfileName), nil) } - err = profiles.GetMainConfig().DefaultMissingViperKeys() + err = profiles.GetKoanfConfig().DefaultMissingKoanfKeys() if err != nil { return fmt.Errorf("failed to add profile: %w", err) } @@ -70,7 +73,7 @@ func readConfigAddProfileOptions(rc io.ReadCloser) (newProfileName, newDescripti func readConfigAddProfileNameOption(rc io.ReadCloser) (newProfileName string, err error) { if !options.ConfigAddProfileNameOption.Flag.Changed { - newProfileName, err = input.RunPrompt("New profile name", profiles.GetMainConfig().ValidateNewProfileName, rc) + newProfileName, err = input.RunPrompt("New profile name", profiles.GetKoanfConfig().ValidateNewProfileName, rc) if err != nil { return newProfileName, err } diff --git a/internal/commands/config/add_profile_internal_test.go b/internal/commands/config/add_profile_internal_test.go index 18520a18..0c792186 100644 --- a/internal/commands/config/add_profile_internal_test.go +++ b/internal/commands/config/add_profile_internal_test.go @@ -9,12 +9,12 @@ import ( "github.com/pingidentity/pingcli/internal/configuration/options" "github.com/pingidentity/pingcli/internal/customtypes" "github.com/pingidentity/pingcli/internal/testing/testutils" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" ) // Test RunInternalConfigAddProfile function func Test_RunInternalConfigAddProfile(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) var ( profileName = customtypes.String("test-profile") @@ -39,7 +39,7 @@ func Test_RunInternalConfigAddProfile(t *testing.T) { // Test RunInternalConfigAddProfile function fails when existing profile name is provided func Test_RunInternalConfigAddProfile_ExistingProfileName(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) var ( profileName = customtypes.String("default") @@ -63,8 +63,7 @@ func Test_RunInternalConfigAddProfile_ExistingProfileName(t *testing.T) { // Test RunInternalConfigAddProfile function fails when profile name is not provided func Test_RunInternalConfigAddProfile_NoProfileName(t *testing.T) { - testutils_viper.InitVipers(t) - + testutils_koanf.InitKoanfs(t) var ( profileName = customtypes.String("") description = customtypes.String("test-description") @@ -87,10 +86,9 @@ func Test_RunInternalConfigAddProfile_NoProfileName(t *testing.T) { // Test RunInternalConfigAddProfile function succeeds with set active flag set to true func Test_RunInternalConfigAddProfile_SetActive(t *testing.T) { - testutils_viper.InitVipers(t) - + testutils_koanf.InitKoanfs(t) var ( - profileName = customtypes.String("test-profile") + profileName = customtypes.String("test-profile-active") description = customtypes.String("test-description") setActive = customtypes.Bool(true) ) @@ -112,8 +110,7 @@ func Test_RunInternalConfigAddProfile_SetActive(t *testing.T) { // Test RunInternalConfigAddProfile function fails with invalid set active flag func Test_RunInternalConfigAddProfile_InvalidSetActive(t *testing.T) { - testutils_viper.InitVipers(t) - + testutils_koanf.InitKoanfs(t) var ( profileName = customtypes.String("test-profile") description = customtypes.String("test-description") diff --git a/internal/commands/config/delete_profile_internal.go b/internal/commands/config/delete_profile_internal.go index cdc8565d..0b69422f 100644 --- a/internal/commands/config/delete_profile_internal.go +++ b/internal/commands/config/delete_profile_internal.go @@ -23,7 +23,7 @@ func RunInternalConfigDeleteProfile(args []string, rc io.ReadCloser) (err error) } } - if err = profiles.GetMainConfig().ValidateExistingProfileName(pName); err != nil { + if err = profiles.GetKoanfConfig().ValidateExistingProfileName(pName); err != nil { return fmt.Errorf("failed to delete profile: %w", err) } @@ -47,7 +47,7 @@ func RunInternalConfigDeleteProfile(args []string, rc io.ReadCloser) (err error) } func promptUserToDeleteProfile(rc io.ReadCloser) (pName string, err error) { - pName, err = input.RunPromptSelect("Select profile to delete", profiles.GetMainConfig().ProfileNames(), rc) + pName, err = input.RunPromptSelect("Select profile to delete", profiles.GetKoanfConfig().ProfileNames(), rc) if err != nil { return pName, err @@ -75,7 +75,7 @@ func promptUserToConfirmDelete(pName string, rc io.ReadCloser) (confirmed bool, func deleteProfile(pName string) (err error) { output.Message(fmt.Sprintf("Deleting profile '%s'...", pName), nil) - if err = profiles.GetMainConfig().DeleteProfile(pName); err != nil { + if err = profiles.GetKoanfConfig().DeleteProfile(pName); err != nil { return err } diff --git a/internal/commands/config/delete_profile_internal_test.go b/internal/commands/config/delete_profile_internal_test.go index 7a3a4a97..14d08e83 100644 --- a/internal/commands/config/delete_profile_internal_test.go +++ b/internal/commands/config/delete_profile_internal_test.go @@ -6,12 +6,12 @@ import ( "testing" "github.com/pingidentity/pingcli/internal/testing/testutils" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" ) // Test deleteProfile function func Test_deleteProfile(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := deleteProfile("production") testutils.CheckExpectedError(t, err, nil) @@ -19,7 +19,7 @@ func Test_deleteProfile(t *testing.T) { // Test deleteProfile function fails with active profile func Test_deleteProfile_ActiveProfile(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `^'.*' is the active profile and cannot be deleted$` err := deleteProfile("default") @@ -28,7 +28,7 @@ func Test_deleteProfile_ActiveProfile(t *testing.T) { // Test deleteProfile function fails with invalid profile name func Test_deleteProfile_InvalidProfileName(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `^invalid profile name: '.*' profile does not exist$` err := deleteProfile("(*#&)") @@ -37,7 +37,7 @@ func Test_deleteProfile_InvalidProfileName(t *testing.T) { // Test deleteProfile function fails with empty profile name func Test_deleteProfile_EmptyProfileName(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `^invalid profile name: profile name cannot be empty$` err := deleteProfile("") @@ -46,7 +46,7 @@ func Test_deleteProfile_EmptyProfileName(t *testing.T) { // Test deleteProfile function fails with non-existent profile name func Test_deleteProfile_NonExistentProfileName(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `^invalid profile name: '.*' profile does not exist$` err := deleteProfile("non-existent") diff --git a/internal/commands/config/get_internal.go b/internal/commands/config/get_internal.go index 2aca0583..a481b394 100644 --- a/internal/commands/config/get_internal.go +++ b/internal/commands/config/get_internal.go @@ -12,8 +12,8 @@ import ( "github.com/pingidentity/pingcli/internal/profiles" ) -func RunInternalConfigGet(viperKey string) (err error) { - if err = configuration.ValidateParentViperKey(viperKey); err != nil { +func RunInternalConfigGet(koanfKey string) (err error) { + if err = configuration.ValidateParentKoanfKey(koanfKey); err != nil { return fmt.Errorf("failed to get configuration: %w", err) } @@ -22,14 +22,14 @@ func RunInternalConfigGet(viperKey string) (err error) { return fmt.Errorf("failed to get configuration: %w", err) } - msgStr := fmt.Sprintf("Configuration values for profile '%s' and key '%s':\n", strings.ToLower(pName), viperKey) + msgStr := fmt.Sprintf("Configuration values for profile '%s' and key '%s':\n", pName, koanfKey) for _, opt := range options.Options() { - if opt.ViperKey == "" || !strings.Contains(opt.ViperKey, viperKey) { + if opt.KoanfKey == "" || !strings.Contains(opt.KoanfKey, koanfKey) { continue } - vVal, _, err := profiles.ViperValueFromOption(opt) + vVal, _, err := profiles.KoanfValueFromOption(opt, pName) if err != nil { return fmt.Errorf("failed to get configuration: %w", err) } @@ -40,9 +40,9 @@ func RunInternalConfigGet(viperKey string) (err error) { } if opt.Sensitive && strings.EqualFold(unmaskOptionVal, "false") { - msgStr += fmt.Sprintf("%s=%s\n", opt.ViperKey, profiles.MaskValue(vVal)) + msgStr += fmt.Sprintf("%s=%s\n", opt.KoanfKey, profiles.MaskValue(vVal)) } else { - msgStr += fmt.Sprintf("%s=%s\n", opt.ViperKey, vVal) + msgStr += fmt.Sprintf("%s=%s\n", opt.KoanfKey, vVal) } } diff --git a/internal/commands/config/get_internal_test.go b/internal/commands/config/get_internal_test.go index 4d8379ed..4df92f2a 100644 --- a/internal/commands/config/get_internal_test.go +++ b/internal/commands/config/get_internal_test.go @@ -8,12 +8,12 @@ import ( "github.com/pingidentity/pingcli/internal/configuration/options" "github.com/pingidentity/pingcli/internal/customtypes" "github.com/pingidentity/pingcli/internal/testing/testutils" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" ) // Test RunInternalConfigGet function func Test_RunInternalConfigGet(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := RunInternalConfigGet("service") if err != nil { @@ -23,7 +23,7 @@ func Test_RunInternalConfigGet(t *testing.T) { // Test RunInternalConfigGet function fails with invalid key func Test_RunInternalConfigGet_InvalidKey(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `(?s)^failed to get configuration: key '.*' is not recognized as a valid configuration key\.\s*Use 'pingcli config list-keys' to view all available keys` err := RunInternalConfigGet("invalid-key") @@ -32,7 +32,7 @@ func Test_RunInternalConfigGet_InvalidKey(t *testing.T) { // Test RunInternalConfigGet function with different profile func Test_RunInternalConfigGet_DifferentProfile(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) var ( profileName = customtypes.String("production") @@ -49,7 +49,7 @@ func Test_RunInternalConfigGet_DifferentProfile(t *testing.T) { // Test RunInternalConfigGet function fails with invalid profile name func Test_RunInternalConfigGet_InvalidProfileName(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) var ( profileName = customtypes.String("invalid") diff --git a/internal/commands/config/list_keys_internal.go b/internal/commands/config/list_keys_internal.go index 4e248a06..f0499630 100644 --- a/internal/commands/config/list_keys_internal.go +++ b/internal/commands/config/list_keys_internal.go @@ -15,19 +15,19 @@ import ( func returnKeysYamlString() (string, error) { var err error - viperKeys := configuration.ViperKeys() + koanfKeys := configuration.KoanfKeys() - if len(viperKeys) == 0 { + if len(koanfKeys) == 0 { return "", fmt.Errorf("unable to retrieve valid keys") } // Split the input string into individual keys keyMap := make(map[string]interface{}) - // Iterate over each viper key - for _, viperKey := range viperKeys { + // Iterate over each koanf key + for _, koanfKey := range koanfKeys { // Skip the "activeProfile" key - if viperKey == "activeProfile" { + if koanfKey == "activeProfile" { continue } @@ -36,7 +36,7 @@ func returnKeysYamlString() (string, error) { currentMap = keyMap currentMapOk bool ) - yamlKeys := strings.Split(viperKey, ".") + yamlKeys := strings.Split(koanfKey, ".") for i, k := range yamlKeys { // If it's the last yaml key, set an empty map if i == len(yamlKeys)-1 { @@ -48,7 +48,7 @@ func returnKeysYamlString() (string, error) { } currentMap, currentMapOk = currentMap[k].(map[string]interface{}) if !currentMapOk { - return "", fmt.Errorf("failed to get configuration keys list: error creating nested map for key %s", viperKey) + return "", fmt.Errorf("failed to get configuration keys list: error creating nested map for key %s", koanfKey) } } } @@ -65,7 +65,7 @@ func returnKeysYamlString() (string, error) { func returnKeysString() (string, error) { // var err error - validKeys := configuration.ViperKeys() + validKeys := configuration.KoanfKeys() if len(validKeys) == 0 { return "", fmt.Errorf("unable to retrieve valid keys") diff --git a/internal/commands/config/list_keys_internal_test.go b/internal/commands/config/list_keys_internal_test.go index 24404125..b4150377 100644 --- a/internal/commands/config/list_keys_internal_test.go +++ b/internal/commands/config/list_keys_internal_test.go @@ -6,12 +6,12 @@ import ( "testing" "github.com/pingidentity/pingcli/internal/testing/testutils" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" ) // Test RunInternalConfigListKeys function func Test_RunInternalConfigListKeys(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := RunInternalConfigListKeys() testutils.CheckExpectedError(t, err, nil) diff --git a/internal/commands/config/list_profiles_internal.go b/internal/commands/config/list_profiles_internal.go index 99fed27f..3faa053f 100644 --- a/internal/commands/config/list_profiles_internal.go +++ b/internal/commands/config/list_profiles_internal.go @@ -3,19 +3,14 @@ package config_internal import ( - "strings" - "github.com/fatih/color" "github.com/pingidentity/pingcli/internal/configuration/options" - "github.com/pingidentity/pingcli/internal/logger" "github.com/pingidentity/pingcli/internal/output" "github.com/pingidentity/pingcli/internal/profiles" ) func RunInternalConfigListProfiles() (err error) { - l := logger.Get() - - profileNames := profiles.GetMainConfig().ProfileNames() + profileNames := profiles.GetKoanfConfig().ProfileNames() activeProfileName, err := profiles.GetOptionValue(options.RootActiveProfileOption) if err != nil { return err @@ -27,23 +22,21 @@ func RunInternalConfigListProfiles() (err error) { output.SetColorize() activeFmt := color.New(color.Bold, color.FgGreen).SprintFunc() - for _, profileName := range profileNames { - if strings.EqualFold(profileName, activeProfileName) { + for i, profileName := range profileNames { + if profileName == activeProfileName { listStr += "- " + profileName + activeFmt(" (active)") + " \n" } else { listStr += "- " + profileName + "\n" } - description, err := profiles.GetMainConfig().ProfileViperValue(profileName, "description") - if err != nil { - l.Warn().Msgf("Cannot retrieve profile description for profile %s: %v", profileName, err) - - continue - } - + description := profiles.GetKoanfConfig().KoanfInstance().String(profileName + "." + "description") if description != "" { listStr += " " + description } + + if i < len(profileNames)-1 { + listStr += "\n" + } } output.Message(listStr, nil) diff --git a/internal/commands/config/list_profiles_internal_test.go b/internal/commands/config/list_profiles_internal_test.go index 31a3825d..cd55163f 100644 --- a/internal/commands/config/list_profiles_internal_test.go +++ b/internal/commands/config/list_profiles_internal_test.go @@ -6,12 +6,12 @@ import ( "testing" "github.com/pingidentity/pingcli/internal/testing/testutils" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" ) // Test RunInternalConfigListProfiles function func Test_RunInternalConfigListProfiles(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := RunInternalConfigListProfiles() testutils.CheckExpectedError(t, err, nil) diff --git a/internal/commands/config/set_active_profile_internal.go b/internal/commands/config/set_active_profile_internal.go index 96b4ebd3..436bbecb 100644 --- a/internal/commands/config/set_active_profile_internal.go +++ b/internal/commands/config/set_active_profile_internal.go @@ -24,7 +24,7 @@ func RunInternalConfigSetActiveProfile(args []string, rc io.ReadCloser) (err err output.Message(fmt.Sprintf("Setting active profile to '%s'...", pName), nil) - if err = profiles.GetMainConfig().ChangeActiveProfile(pName); err != nil { + if err = profiles.GetKoanfConfig().ChangeActiveProfile(pName); err != nil { return fmt.Errorf("failed to set active profile: %w", err) } @@ -34,7 +34,7 @@ func RunInternalConfigSetActiveProfile(args []string, rc io.ReadCloser) (err err } func promptUserToSelectActiveProfile(rc io.ReadCloser) (pName string, err error) { - pName, err = input.RunPromptSelect("Select profile to set as active: ", profiles.GetMainConfig().ProfileNames(), rc) + pName, err = input.RunPromptSelect("Select profile to set as active: ", profiles.GetKoanfConfig().ProfileNames(), rc) if err != nil { return pName, err diff --git a/internal/commands/config/set_active_profile_internal_test.go b/internal/commands/config/set_active_profile_internal_test.go index 9ed0d879..b27128c8 100644 --- a/internal/commands/config/set_active_profile_internal_test.go +++ b/internal/commands/config/set_active_profile_internal_test.go @@ -7,12 +7,12 @@ import ( "testing" "github.com/pingidentity/pingcli/internal/testing/testutils" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" ) // Test RunInternalConfigSetActiveProfile function func Test_RunInternalConfigSetActiveProfile(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := RunInternalConfigSetActiveProfile([]string{"production"}, os.Stdin) testutils.CheckExpectedError(t, err, nil) @@ -20,7 +20,7 @@ func Test_RunInternalConfigSetActiveProfile(t *testing.T) { // Test RunInternalConfigSetActiveProfile function fails with invalid profile name func Test_RunInternalConfigSetActiveProfile_InvalidProfileName(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `^failed to set active profile: invalid profile name: '.*' profile does not exist$` err := RunInternalConfigSetActiveProfile([]string{"(*#&)"}, os.Stdin) @@ -29,7 +29,7 @@ func Test_RunInternalConfigSetActiveProfile_InvalidProfileName(t *testing.T) { // Test RunInternalConfigSetActiveProfile function fails with non-existent profile func Test_RunInternalConfigSetActiveProfile_NonExistentProfile(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `^failed to set active profile: invalid profile name: '.*' profile does not exist$` err := RunInternalConfigSetActiveProfile([]string{"non-existent"}, os.Stdin) diff --git a/internal/commands/config/set_internal.go b/internal/commands/config/set_internal.go index cb3d63f4..53e7034a 100644 --- a/internal/commands/config/set_internal.go +++ b/internal/commands/config/set_internal.go @@ -6,12 +6,12 @@ import ( "fmt" "strings" + "github.com/knadh/koanf/v2" "github.com/pingidentity/pingcli/internal/configuration" "github.com/pingidentity/pingcli/internal/configuration/options" "github.com/pingidentity/pingcli/internal/customtypes" "github.com/pingidentity/pingcli/internal/output" "github.com/pingidentity/pingcli/internal/profiles" - "github.com/spf13/viper" ) func RunInternalConfigSet(kvPair string) (err error) { @@ -20,7 +20,7 @@ func RunInternalConfigSet(kvPair string) (err error) { return fmt.Errorf("failed to set configuration: %w", err) } - if err = configuration.ValidateViperKey(vKey); err != nil { + if err = configuration.ValidateKoanfKey(vKey); err != nil { return fmt.Errorf("failed to set configuration: %w", err) } @@ -29,27 +29,27 @@ func RunInternalConfigSet(kvPair string) (err error) { return fmt.Errorf("failed to set configuration: value for key '%s' is empty. Use 'pingcli config unset %s' to unset the key", vKey, vKey) } - subViper, err := profiles.GetMainConfig().GetProfileViper(pName) + subKoanf, err := profiles.GetKoanfConfig().GetProfileKoanf(pName) if err != nil { return fmt.Errorf("failed to set configuration: %w", err) } - opt, err := configuration.OptionFromViperKey(vKey) + opt, err := configuration.OptionFromKoanfKey(vKey) if err != nil { return fmt.Errorf("failed to set configuration: %w", err) } - if err = setValue(subViper, vKey, vValue, opt.Type); err != nil { + if err = setValue(subKoanf, vKey, vValue, opt.Type); err != nil { return fmt.Errorf("failed to set configuration: %w", err) } - if err = profiles.GetMainConfig().SaveProfile(pName, subViper); err != nil { + if err = profiles.GetKoanfConfig().SaveProfile(pName, subKoanf); err != nil { return fmt.Errorf("failed to set configuration: %w", err) } msgStr := "Configuration set successfully:\n" - vVal, _, err := profiles.ViperValueFromOption(opt) + vVal, _, err := profiles.KoanfValueFromOption(opt, pName) if err != nil { return fmt.Errorf("failed to set configuration: %w", err) } @@ -109,92 +109,134 @@ func parseKeyValuePair(kvPair string) (string, string, error) { return parsedInput[0], parsedInput[1], nil } -func setValue(profileViper *viper.Viper, vKey, vValue string, valueType options.OptionType) (err error) { +func setValue(profileKoanf *koanf.Koanf, vKey, vValue string, valueType options.OptionType) (err error) { switch valueType { case options.ENUM_BOOL: b := new(customtypes.Bool) if err = b.Set(vValue); err != nil { return fmt.Errorf("value for key '%s' must be a boolean. Allowed [true, false]: %w", vKey, err) } - profileViper.Set(vKey, b) + err = profileKoanf.Set(vKey, b) + if err != nil { + return fmt.Errorf("unable to set key '%w' in koanf profile: ", err) + } case options.ENUM_EXPORT_FORMAT: exportFormat := new(customtypes.ExportFormat) if err = exportFormat.Set(vValue); err != nil { return fmt.Errorf("value for key '%s' must be a valid export format. Allowed [%s]: %w", vKey, strings.Join(customtypes.ExportFormatValidValues(), ", "), err) } - profileViper.Set(vKey, exportFormat) + err = profileKoanf.Set(vKey, exportFormat) + if err != nil { + return fmt.Errorf("unable to set key '%w' in koanf profile: ", err) + } case options.ENUM_EXPORT_SERVICE_GROUP: exportServiceGroup := new(customtypes.ExportServiceGroup) if err = exportServiceGroup.Set(vValue); err != nil { return fmt.Errorf("value for key '%s' must be valid export service group. Allowed [%s]: %w", vKey, strings.Join(customtypes.ExportServiceGroupValidValues(), ", "), err) } - profileViper.Set(vKey, exportServiceGroup) + err = profileKoanf.Set(vKey, exportServiceGroup) + if err != nil { + return fmt.Errorf("unable to set key '%w' in koanf profile: ", err) + } case options.ENUM_EXPORT_SERVICES: exportServices := new(customtypes.ExportServices) if err = exportServices.Set(vValue); err != nil { return fmt.Errorf("value for key '%s' must be valid export service(s). Allowed [%s]: %w", vKey, strings.Join(customtypes.ExportServicesValidValues(), ", "), err) } - profileViper.Set(vKey, exportServices) + err = profileKoanf.Set(vKey, exportServices) + if err != nil { + return fmt.Errorf("unable to set key '%w' in koanf profile: ", err) + } case options.ENUM_OUTPUT_FORMAT: outputFormat := new(customtypes.OutputFormat) if err = outputFormat.Set(vValue); err != nil { return fmt.Errorf("value for key '%s' must be a valid output format. Allowed [%s]: %w", vKey, strings.Join(customtypes.OutputFormatValidValues(), ", "), err) } - profileViper.Set(vKey, outputFormat) + err = profileKoanf.Set(vKey, outputFormat) + if err != nil { + return fmt.Errorf("unable to set key '%w' in koanf profile: ", err) + } case options.ENUM_PINGONE_REGION_CODE: region := new(customtypes.PingOneRegionCode) if err = region.Set(vValue); err != nil { return fmt.Errorf("value for key '%s' must be a valid PingOne Region Code. Allowed [%s]: %w", vKey, strings.Join(customtypes.PingOneRegionCodeValidValues(), ", "), err) } - profileViper.Set(vKey, region) + err = profileKoanf.Set(vKey, region) + if err != nil { + return fmt.Errorf("unable to set key '%w' in koanf profile: ", err) + } case options.ENUM_STRING: str := new(customtypes.String) if err = str.Set(vValue); err != nil { return fmt.Errorf("value for key '%s' must be a string: %w", vKey, err) } - profileViper.Set(vKey, str) + err = profileKoanf.Set(vKey, str) + if err != nil { + return fmt.Errorf("unable to set key '%w' in koanf profile: ", err) + } case options.ENUM_STRING_SLICE: strSlice := new(customtypes.StringSlice) if err = strSlice.Set(vValue); err != nil { return fmt.Errorf("value for key '%s' must be a string slice: %w", vKey, err) } - profileViper.Set(vKey, strSlice) + err = profileKoanf.Set(vKey, strSlice) + if err != nil { + return fmt.Errorf("unable to set key '%w' in koanf profile: ", err) + } case options.ENUM_UUID: uuid := new(customtypes.UUID) if err = uuid.Set(vValue); err != nil { return fmt.Errorf("value for key '%s' must be a valid UUID: %w", vKey, err) } - profileViper.Set(vKey, uuid) + err = profileKoanf.Set(vKey, uuid) + if err != nil { + return fmt.Errorf("unable to set key '%w' in koanf profile: ", err) + } case options.ENUM_PINGONE_AUTH_TYPE: authType := new(customtypes.PingOneAuthenticationType) if err = authType.Set(vValue); err != nil { return fmt.Errorf("value for key '%s' must be a valid PingOne Authentication Type. Allowed [%s]: %w", vKey, strings.Join(customtypes.PingOneAuthenticationTypeValidValues(), ", "), err) } - profileViper.Set(vKey, authType) + err = profileKoanf.Set(vKey, authType) + if err != nil { + return fmt.Errorf("unable to set key '%w' in koanf profile: ", err) + } case options.ENUM_PINGFEDERATE_AUTH_TYPE: authType := new(customtypes.PingFederateAuthenticationType) if err = authType.Set(vValue); err != nil { return fmt.Errorf("value for key '%s' must be a valid PingFederate Authentication Type. Allowed [%s]: %w", vKey, strings.Join(customtypes.PingFederateAuthenticationTypeValidValues(), ", "), err) } - profileViper.Set(vKey, authType) + err = profileKoanf.Set(vKey, authType) + if err != nil { + return fmt.Errorf("unable to set key '%w' in koanf profile: ", err) + } case options.ENUM_INT: intValue := new(customtypes.Int) if err = intValue.Set(vValue); err != nil { return fmt.Errorf("value for key '%s' must be an integer: %w", vKey, err) } - profileViper.Set(vKey, intValue) + err = profileKoanf.Set(vKey, intValue) + if err != nil { + return fmt.Errorf("unable to set key '%w' in koanf profile: ", err) + } case options.ENUM_REQUEST_HTTP_METHOD: httpMethod := new(customtypes.HTTPMethod) if err = httpMethod.Set(vValue); err != nil { return fmt.Errorf("value for key '%s' must be a valid HTTP method. Allowed [%s]: %w", vKey, strings.Join(customtypes.HTTPMethodValidValues(), ", "), err) } - profileViper.Set(vKey, httpMethod) + err = profileKoanf.Set(vKey, httpMethod) + if err != nil { + return fmt.Errorf("unable to set key '%w' in koanf profile: ", err) + } case options.ENUM_REQUEST_SERVICE: service := new(customtypes.RequestService) if err = service.Set(vValue); err != nil { return fmt.Errorf("value for key '%s' must be a valid request service. Allowed [%s]: %w", vKey, strings.Join(customtypes.RequestServiceValidValues(), ", "), err) } - profileViper.Set(vKey, service) + err = profileKoanf.Set(vKey, service) + if err != nil { + return fmt.Errorf("unable to set key '%w' in koanf profile: ", err) + } default: return fmt.Errorf("failed to set configuration: variable type for key '%s' is not recognized", vKey) } diff --git a/internal/commands/config/set_internal_test.go b/internal/commands/config/set_internal_test.go index 2b8c8833..f4b55ac7 100644 --- a/internal/commands/config/set_internal_test.go +++ b/internal/commands/config/set_internal_test.go @@ -8,12 +8,12 @@ import ( "github.com/pingidentity/pingcli/internal/configuration/options" "github.com/pingidentity/pingcli/internal/customtypes" "github.com/pingidentity/pingcli/internal/testing/testutils" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" ) // Test RunInternalConfigSet function func Test_RunInternalConfigSet(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := RunInternalConfigSet("noColor=true") if err != nil { @@ -23,7 +23,7 @@ func Test_RunInternalConfigSet(t *testing.T) { // Test RunInternalConfigSet function fails with invalid key func Test_RunInternalConfigSet_InvalidKey(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `^failed to set configuration: key '.*' is not recognized as a valid configuration key.\s*Use 'pingcli config list-keys' to view all available keys` err := RunInternalConfigSet("invalid-key=false") @@ -32,7 +32,7 @@ func Test_RunInternalConfigSet_InvalidKey(t *testing.T) { // Test RunInternalConfigSet function fails with invalid value func Test_RunInternalConfigSet_InvalidValue(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `^failed to set configuration: value for key '.*' must be a boolean. Allowed .*: strconv.ParseBool: parsing ".*": invalid syntax$` err := RunInternalConfigSet("noColor=invalid") @@ -41,7 +41,7 @@ func Test_RunInternalConfigSet_InvalidValue(t *testing.T) { // Test RunInternalConfigSet function fails with non-existent profile name func Test_RunInternalConfigSet_NonExistentProfileName(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) var ( profileName = customtypes.String("non-existent") @@ -57,7 +57,7 @@ func Test_RunInternalConfigSet_NonExistentProfileName(t *testing.T) { // Test RunInternalConfigSet function with different profile func Test_RunInternalConfigSet_DifferentProfile(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) var ( profileName = customtypes.String("production") @@ -74,7 +74,7 @@ func Test_RunInternalConfigSet_DifferentProfile(t *testing.T) { // Test RunInternalConfigSet function fails with invalid profile name func Test_RunInternalConfigSet_InvalidProfileName(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) var ( profileName = customtypes.String("*&%*&") @@ -90,7 +90,7 @@ func Test_RunInternalConfigSet_InvalidProfileName(t *testing.T) { // Test RunInternalConfigSet function fails with no value provided func Test_RunInternalConfigSet_NoValue(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `^failed to set configuration: value for key '.*' is empty. Use 'pingcli config unset .*' to unset the key$` err := RunInternalConfigSet("noColor=") @@ -99,7 +99,7 @@ func Test_RunInternalConfigSet_NoValue(t *testing.T) { // Test RunInternalConfigSet function fails with no keyValue provided func Test_RunInternalConfigSet_NoKeyValue(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `^failed to set configuration: invalid assignment format ''\. Expect 'key=value' format$` err := RunInternalConfigSet("") diff --git a/internal/commands/config/unset_internal.go b/internal/commands/config/unset_internal.go index 63a651d1..374f4d0b 100644 --- a/internal/commands/config/unset_internal.go +++ b/internal/commands/config/unset_internal.go @@ -12,8 +12,8 @@ import ( "github.com/pingidentity/pingcli/internal/profiles" ) -func RunInternalConfigUnset(viperKey string) (err error) { - if err = configuration.ValidateViperKey(viperKey); err != nil { +func RunInternalConfigUnset(koanfKey string) (err error) { + if err = configuration.ValidateKoanfKey(koanfKey); err != nil { return fmt.Errorf("failed to unset configuration: %w", err) } @@ -22,25 +22,28 @@ func RunInternalConfigUnset(viperKey string) (err error) { return fmt.Errorf("failed to unset configuration: %w", err) } - subViper, err := profiles.GetMainConfig().GetProfileViper(pName) + subKoanf, err := profiles.GetKoanfConfig().GetProfileKoanf(pName) if err != nil { return fmt.Errorf("failed to unset configuration: %w", err) } - opt, err := configuration.OptionFromViperKey(viperKey) + opt, err := configuration.OptionFromKoanfKey(koanfKey) if err != nil { return fmt.Errorf("failed to unset configuration: %w", err) } - subViper.Set(viperKey, opt.DefaultValue) + err = subKoanf.Set(koanfKey, opt.DefaultValue) + if err != nil { + return fmt.Errorf("failed to unset configuration: %w", err) + } - if err = profiles.GetMainConfig().SaveProfile(pName, subViper); err != nil { + if err = profiles.GetKoanfConfig().SaveProfile(pName, subKoanf); err != nil { return fmt.Errorf("failed to unset configuration: %w", err) } msgStr := "Configuration unset successfully:\n" - vVal, _, err := profiles.ViperValueFromOption(opt) + vVal, _, err := profiles.KoanfValueFromOption(opt, pName) if err != nil { return fmt.Errorf("failed to unset configuration: %w", err) } @@ -51,9 +54,9 @@ func RunInternalConfigUnset(viperKey string) (err error) { } if opt.Sensitive && strings.EqualFold(unmaskOptionVal, "false") { - msgStr += fmt.Sprintf("%s=%s", viperKey, profiles.MaskValue(vVal)) + msgStr += fmt.Sprintf("%s=%s", koanfKey, profiles.MaskValue(vVal)) } else { - msgStr += fmt.Sprintf("%s=%s", viperKey, vVal) + msgStr += fmt.Sprintf("%s=%s", koanfKey, vVal) } output.Success(msgStr, nil) diff --git a/internal/commands/config/unset_internal_test.go b/internal/commands/config/unset_internal_test.go index ca2994c7..1a793788 100644 --- a/internal/commands/config/unset_internal_test.go +++ b/internal/commands/config/unset_internal_test.go @@ -8,12 +8,12 @@ import ( "github.com/pingidentity/pingcli/internal/configuration/options" "github.com/pingidentity/pingcli/internal/customtypes" "github.com/pingidentity/pingcli/internal/testing/testutils" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" ) // Test RunInternalConfigUnset function func Test_RunInternalConfigUnset(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := RunInternalConfigUnset("noColor") if err != nil { @@ -23,7 +23,7 @@ func Test_RunInternalConfigUnset(t *testing.T) { // Test RunInternalConfigUnset function fails with invalid key func Test_RunInternalConfigUnset_InvalidKey(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `^failed to unset configuration: key '.*' is not recognized as a valid configuration key.\s*Use 'pingcli config list-keys' to view all available keys` err := RunInternalConfigUnset("invalid-key") @@ -32,7 +32,7 @@ func Test_RunInternalConfigUnset_InvalidKey(t *testing.T) { // Test RunInternalConfigUnset function with different profile func Test_RunInternalConfigUnset_DifferentProfile(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) var ( profileName = customtypes.String("production") @@ -49,7 +49,7 @@ func Test_RunInternalConfigUnset_DifferentProfile(t *testing.T) { // Test RunInternalConfigUnset function fails with invalid profile name func Test_RunInternalConfigUnset_InvalidProfileName(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) var ( profileName = customtypes.String("invalid") diff --git a/internal/commands/config/view_profile_internal.go b/internal/commands/config/view_profile_internal.go index 5197d405..54cea55f 100644 --- a/internal/commands/config/view_profile_internal.go +++ b/internal/commands/config/view_profile_internal.go @@ -12,7 +12,7 @@ import ( ) func RunInternalConfigViewProfile(args []string) (err error) { - var pName string + var msgStr, pName string if len(args) == 1 { pName = args[0] } else { @@ -23,21 +23,30 @@ func RunInternalConfigViewProfile(args []string) (err error) { } // Validate the profile name - err = profiles.GetMainConfig().ValidateExistingProfileName(pName) + err = profiles.GetKoanfConfig().ValidateExistingProfileName(pName) if err != nil { return fmt.Errorf("failed to view profile: %w", err) } - msgStr := fmt.Sprintf("Configuration for profile '%s':\n", pName) + // Get the Koanf configuration for the specified profile + koanfProfile, err := profiles.GetKoanfConfig().GetProfileKoanf(pName) + if err != nil { + return fmt.Errorf("failed to get config from profile: %w", err) + } + // Iterate over the options in profile and print them for _, opt := range options.Options() { - if opt.ViperKey == "" { + if !koanfProfile.Exists(opt.KoanfKey) { + continue + } + + vVal, ok, err := profiles.KoanfValueFromOption(opt, pName) + if !ok { continue } - vVal, _, err := profiles.ViperValueFromOption(opt) if err != nil { - return fmt.Errorf("failed to view profile: %w", err) + return fmt.Errorf("failed to get koanf value from option: %w", err) } unmaskOptionVal, err := profiles.GetOptionValue(options.ConfigUnmaskSecretValueOption) @@ -46,13 +55,13 @@ func RunInternalConfigViewProfile(args []string) (err error) { } if opt.Sensitive && strings.EqualFold(unmaskOptionVal, "false") { - msgStr += fmt.Sprintf("%s=%s\n", opt.ViperKey, profiles.MaskValue(vVal)) + msgStr += fmt.Sprintf("%s=%s\n", opt.KoanfKey, profiles.MaskValue(vVal)) } else { - msgStr += fmt.Sprintf("%s=%s\n", opt.ViperKey, vVal) + msgStr += fmt.Sprintf("%s=%s\n", opt.KoanfKey, vVal) } } - output.Message(msgStr, nil) + output.Message(fmt.Sprintf("Configuration for profile '%s':\n", pName)+msgStr, nil) return nil } diff --git a/internal/commands/config/view_profile_internal_test.go b/internal/commands/config/view_profile_internal_test.go index cb836c76..23224ca7 100644 --- a/internal/commands/config/view_profile_internal_test.go +++ b/internal/commands/config/view_profile_internal_test.go @@ -6,12 +6,12 @@ import ( "testing" "github.com/pingidentity/pingcli/internal/testing/testutils" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" ) // Test RunInternalConfigViewProfile function func Test_RunInternalConfigViewProfile(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := RunInternalConfigViewProfile([]string{}) testutils.CheckExpectedError(t, err, nil) @@ -19,7 +19,7 @@ func Test_RunInternalConfigViewProfile(t *testing.T) { // Test RunInternalConfigViewProfile function fails with invalid profile name func Test_RunInternalConfigViewProfile_InvalidProfileName(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `^failed to view profile: invalid profile name: '.*' profile does not exist$` err := RunInternalConfigViewProfile([]string{"invalid"}) @@ -28,7 +28,7 @@ func Test_RunInternalConfigViewProfile_InvalidProfileName(t *testing.T) { // Test RunInternalConfigViewProfile function with different profile func Test_RunInternalConfigViewProfile_DifferentProfile(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := RunInternalConfigViewProfile([]string{"production"}) testutils.CheckExpectedError(t, err, nil) diff --git a/internal/commands/platform/export_internal.go b/internal/commands/platform/export_internal.go index 47c1db65..8f25cf70 100644 --- a/internal/commands/platform/export_internal.go +++ b/internal/commands/platform/export_internal.go @@ -380,7 +380,7 @@ func createOrValidateOutputDir(outputDir string, overwriteExport bool) (resolved "via the '--%s' flag, '%s' environment variable, or key '%s' in the configuration file", options.PlatformExportOutputDirectoryOption.CobraParamName, options.PlatformExportOutputDirectoryOption.EnvVar, - options.PlatformExportOutputDirectoryOption.ViperKey) + options.PlatformExportOutputDirectoryOption.KoanfKey) } // Check if path is absolute. If not, make it absolute using the present working directory diff --git a/internal/commands/platform/export_internal_test.go b/internal/commands/platform/export_internal_test.go index 8fb009b0..5d98e224 100644 --- a/internal/commands/platform/export_internal_test.go +++ b/internal/commands/platform/export_internal_test.go @@ -14,13 +14,13 @@ import ( "github.com/pingidentity/pingcli/internal/customtypes" "github.com/pingidentity/pingcli/internal/profiles" "github.com/pingidentity/pingcli/internal/testing/testutils" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" pingfederateGoClient "github.com/pingidentity/pingfederate-go-client/v1220/configurationapi" ) // Test RunInternalExport function func TestRunInternalExport(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := RunInternalExport(t.Context(), "v1.2.3") testutils.CheckExpectedError(t, err, nil) @@ -63,7 +63,7 @@ func TestRunInternalExportNilContext(t *testing.T) { // Test initPingFederateServices function func TestInitPingFederateServices(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := initPingFederateServices(t.Context(), "v1.2.3") testutils.CheckExpectedError(t, err, nil) @@ -88,7 +88,7 @@ func TestInitPingFederateServicesNilContext(t *testing.T) { // Test initPingOneServices function func TestInitPingOneServices(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := initPingOneServices(t.Context(), "v1.2.3") testutils.CheckExpectedError(t, err, nil) @@ -101,7 +101,7 @@ func TestInitPingOneServices(t *testing.T) { // Test initPingFederateApiClient function func TestInitPingFederateApiClient(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) tr := &http.Transport{ TLSClientConfig: &tls.Config{ @@ -127,7 +127,7 @@ func TestInitPingFederateApiClientNilTransport(t *testing.T) { // Test initPingOneApiClient function func TestInitPingOneApiClient(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := initPingOneApiClient(t.Context(), "v1.2.3") testutils.CheckExpectedError(t, err, nil) @@ -147,7 +147,7 @@ func TestInitPingOneApiClientNilContext(t *testing.T) { // Test createOrValidateOutputDir function with non-existent directory func TestCreateOrValidateOutputDir(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) outputDir := os.TempDir() + "/nonexistantdir" @@ -157,7 +157,7 @@ func TestCreateOrValidateOutputDir(t *testing.T) { // Test createOrValidateOutputDir function with existent directory func TestCreateOrValidateOutputDirExistentDir(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) outputDir := t.TempDir() @@ -168,7 +168,7 @@ func TestCreateOrValidateOutputDirExistentDir(t *testing.T) { // Test createOrValidateOutputDir function with existent directory and overwrite flag // when there is a file in the directory func TestCreateOrValidateOutputDirExistentDirWithFile(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) outputDir := t.TempDir() @@ -188,7 +188,7 @@ func TestCreateOrValidateOutputDirExistentDirWithFile(t *testing.T) { // Test createOrValidateOutputDir function fails with existent directory and no overwrite flag // when there is a file in the directory func TestCreateOrValidateOutputDirExistentDirWithFileNoOverwrite(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) outputDir := t.TempDir() @@ -208,7 +208,7 @@ func TestCreateOrValidateOutputDirExistentDirWithFileNoOverwrite(t *testing.T) { // Test getPingOneExportEnvID function func TestGetPingOneExportEnvID(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) if err := getPingOneExportEnvID(); err != nil { t.Errorf("getPingOneExportEnvID() error = %v, want nil", err) @@ -222,7 +222,7 @@ func TestGetPingOneExportEnvID(t *testing.T) { // Test validatePingOneExportEnvID function func TestValidatePingOneExportEnvID(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) if err := initPingOneApiClient(t.Context(), "v1.2.3"); err != nil { t.Errorf("initPingOneApiClient() error = %v, want nil", err) @@ -245,7 +245,7 @@ func TestValidatePingOneExportEnvIDNilContext(t *testing.T) { // Test getExportableConnectors function func TestGetExportableConnectors(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) es := new(customtypes.ExportServices) err := es.Set(customtypes.ENUM_EXPORT_SERVICE_PINGONE_PROTECT) @@ -277,7 +277,7 @@ func TestGetExportableConnectorsNilMultiService(t *testing.T) { // Test exportConnectors function func TestExportConnectors(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := initPingOneServices(t.Context(), "v1.2.3") if err != nil { @@ -314,7 +314,7 @@ func TestExportConnectorsEmptyExportableConnectors(t *testing.T) { // Test exportConnectors function with invalid export format func TestExportConnectorsInvalidExportFormat(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := initPingOneServices(t.Context(), "v1.2.3") if err != nil { @@ -337,7 +337,7 @@ func TestExportConnectorsInvalidExportFormat(t *testing.T) { // Test exportConnectors function with invalid output directory func TestExportConnectorsInvalidOutputDir(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := initPingOneServices(t.Context(), "v1.2.3") if err != nil { diff --git a/internal/commands/request/request_internal.go b/internal/commands/request/request_internal.go index 733025b4..019d61c2 100644 --- a/internal/commands/request/request_internal.go +++ b/internal/commands/request/request_internal.go @@ -308,14 +308,20 @@ func pingoneAuth() (accessToken string, err error) { } } - subViper, err := profiles.GetMainConfig().GetProfileViper(pName) + subKoanf, err := profiles.GetKoanfConfig().GetProfileKoanf(pName) if err != nil { return "", err } - subViper.Set(options.RequestAccessTokenOption.ViperKey, pingoneAuthResponse.AccessToken) - subViper.Set(options.RequestAccessTokenExpiryOption.ViperKey, tokenExpiry) - err = profiles.GetMainConfig().SaveProfile(pName, subViper) + err = subKoanf.Set(options.RequestAccessTokenOption.KoanfKey, pingoneAuthResponse.AccessToken) + if err != nil { + return "", err + } + err = subKoanf.Set(options.RequestAccessTokenExpiryOption.KoanfKey, tokenExpiry) + if err != nil { + return "", err + } + err = profiles.GetKoanfConfig().SaveProfile(pName, subKoanf) if err != nil { return "", err } diff --git a/internal/commands/request/request_internal_test.go b/internal/commands/request/request_internal_test.go index f4e633c5..b11dd629 100644 --- a/internal/commands/request/request_internal_test.go +++ b/internal/commands/request/request_internal_test.go @@ -12,12 +12,12 @@ import ( "github.com/pingidentity/pingcli/internal/configuration/options" "github.com/pingidentity/pingcli/internal/customtypes" "github.com/pingidentity/pingcli/internal/testing/testutils" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" ) // Test RunInternalRequest function func Test_RunInternalRequest(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) t.Setenv(options.RequestServiceOption.EnvVar, "pingone") @@ -28,7 +28,7 @@ func Test_RunInternalRequest(t *testing.T) { // Test RunInternalRequest function with fail func Test_RunInternalRequestWithFail(t *testing.T) { if os.Getenv("RUN_INTERNAL_FAIL_TEST") == "true" { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) t.Setenv(options.RequestServiceOption.EnvVar, "pingone") options.RequestFailOption.Flag.Changed = true err := options.RequestFailOption.Flag.Value.Set("true") @@ -56,7 +56,7 @@ func Test_RunInternalRequestWithFail(t *testing.T) { // Test RunInternalRequest function with empty service func Test_RunInternalRequest_EmptyService(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := os.Unsetenv(options.RequestServiceOption.EnvVar) if err != nil { @@ -70,7 +70,7 @@ func Test_RunInternalRequest_EmptyService(t *testing.T) { // Test RunInternalRequest function with unrecognized service func Test_RunInternalRequest_UnrecognizedService(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) t.Setenv(options.RequestServiceOption.EnvVar, "invalid-service") @@ -82,7 +82,7 @@ func Test_RunInternalRequest_UnrecognizedService(t *testing.T) { // Test RunInternalRequest function with valid service but invalid URI // This should not error, but rather print a failure message with Body and status of response func Test_RunInternalRequest_ValidService_InvalidURI(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) t.Setenv(options.RequestServiceOption.EnvVar, "pingone") @@ -92,7 +92,7 @@ func Test_RunInternalRequest_ValidService_InvalidURI(t *testing.T) { // Test runInternalPingOneRequest function func Test_runInternalPingOneRequest(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := runInternalPingOneRequest("environments") testutils.CheckExpectedError(t, err, nil) @@ -101,7 +101,7 @@ func Test_runInternalPingOneRequest(t *testing.T) { // Test runInternalPingOneRequest function with invalid URI // This should not error, but rather print a failure message with Body and status of response func Test_runInternalPingOneRequest_InvalidURI(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := runInternalPingOneRequest("invalid-uri") testutils.CheckExpectedError(t, err, nil) @@ -109,7 +109,7 @@ func Test_runInternalPingOneRequest_InvalidURI(t *testing.T) { // Test getTopLevelDomain function func Test_getTopLevelDomain(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) t.Setenv(options.PingOneRegionCodeOption.EnvVar, customtypes.ENUM_PINGONE_REGION_CODE_CA) @@ -124,7 +124,7 @@ func Test_getTopLevelDomain(t *testing.T) { // Test getTopLevelDomain function with invalid region code func Test_getTopLevelDomain_InvalidRegionCode(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) t.Setenv(options.PingOneRegionCodeOption.EnvVar, "invalid-region") @@ -135,7 +135,7 @@ func Test_getTopLevelDomain_InvalidRegionCode(t *testing.T) { // Test pingoneAccessToken function func Test_pingoneAccessToken(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) firstToken, err := pingoneAccessToken() testutils.CheckExpectedError(t, err, nil) @@ -151,7 +151,7 @@ func Test_pingoneAccessToken(t *testing.T) { // Test pingoneAuth function func Test_pingoneAuth(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) firstToken, err := pingoneAuth() testutils.CheckExpectedError(t, err, nil) @@ -167,7 +167,7 @@ func Test_pingoneAuth(t *testing.T) { // Test pingoneAuth function with invalid credentials func Test_pingoneAuth_InvalidCredentials(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) t.Setenv(options.PingOneAuthenticationWorkerClientIDOption.EnvVar, "invalid") @@ -178,7 +178,7 @@ func Test_pingoneAuth_InvalidCredentials(t *testing.T) { // Test getData function func Test_getDataRaw(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) expectedData := "{data: 'json'}" t.Setenv(options.RequestDataRawOption.EnvVar, expectedData) @@ -193,7 +193,7 @@ func Test_getDataRaw(t *testing.T) { // Test getData function with empty data func Test_getDataRaw_EmptyData(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) t.Setenv(options.RequestDataRawOption.EnvVar, "") @@ -207,7 +207,7 @@ func Test_getDataRaw_EmptyData(t *testing.T) { // Test getData function with file input func Test_getDataFile_FileInput(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) expectedData := "{data: 'json from file'}" testDir := t.TempDir() @@ -229,7 +229,7 @@ func Test_getDataFile_FileInput(t *testing.T) { // Test getData function with non-existent file input func Test_getDataFile_NonExistentFileInput(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) t.Setenv(options.RequestDataOption.EnvVar, "non_existent_file.json") diff --git a/internal/configuration/config/add_profile.go b/internal/configuration/config/add_profile.go index f75e3c1c..0e25318e 100644 --- a/internal/configuration/config/add_profile.go +++ b/internal/configuration/config/add_profile.go @@ -32,7 +32,7 @@ func initAddProfileDescriptionOption() { }, Sensitive: false, Type: options.ENUM_STRING, - ViperKey: "", // No viper key + KoanfKey: "", // No koanf key } } @@ -54,7 +54,7 @@ func initAddProfileNameOption() { }, Sensitive: false, Type: options.ENUM_STRING, - ViperKey: "", // No viper key + KoanfKey: "", // No koanf key } } @@ -78,6 +78,6 @@ func initAddProfileSetActiveOption() { }, Sensitive: false, Type: options.ENUM_BOOL, - ViperKey: "", // No viper key + KoanfKey: "", // No koanf key } } diff --git a/internal/configuration/config/delete_profile.go b/internal/configuration/config/delete_profile.go index 6e844653..ec659aee 100644 --- a/internal/configuration/config/delete_profile.go +++ b/internal/configuration/config/delete_profile.go @@ -32,6 +32,6 @@ func initDeleteAutoAcceptOption() { }, Sensitive: false, Type: options.ENUM_STRING, - ViperKey: "", // No viper key + KoanfKey: "", // No koanf key } } diff --git a/internal/configuration/config/list_keys_yaml.go b/internal/configuration/config/list_keys_yaml.go index 2d770669..4260d39b 100644 --- a/internal/configuration/config/list_keys_yaml.go +++ b/internal/configuration/config/list_keys_yaml.go @@ -32,6 +32,6 @@ func initConfigListKeysYAMLOption() { }, Sensitive: false, Type: options.ENUM_BOOL, - ViperKey: "", // No viper key + KoanfKey: "", // No koanf key } } diff --git a/internal/configuration/configuration.go b/internal/configuration/configuration.go index c47db193..50dc0004 100644 --- a/internal/configuration/configuration.go +++ b/internal/configuration/configuration.go @@ -16,10 +16,10 @@ import ( configuration_services "github.com/pingidentity/pingcli/internal/configuration/services" ) -func ViperKeys() (keys []string) { +func KoanfKeys() (keys []string) { for _, opt := range options.Options() { - if opt.ViperKey != "" { - keys = append(keys, opt.ViperKey) + if opt.KoanfKey != "" { + keys = append(keys, opt.KoanfKey) } } @@ -28,29 +28,29 @@ func ViperKeys() (keys []string) { return keys } -func ValidateViperKey(viperKey string) error { - validKeys := ViperKeys() +func ValidateKoanfKey(koanfKey string) error { + validKeys := KoanfKeys() for _, vKey := range validKeys { - if strings.EqualFold(vKey, viperKey) { + if vKey == koanfKey { return nil } } - return fmt.Errorf("key '%s' is not recognized as a valid configuration key.\nUse 'pingcli config list-keys' to view all available keys", viperKey) + return fmt.Errorf("key '%s' is not recognized as a valid configuration key.\nUse 'pingcli config list-keys' to view all available keys", koanfKey) } -// Return a list of all viper keys from Options +// Return a list of all koanf keys from Options // Including all substrings of parent keys. // For example, the option key export.environmentID adds the keys // 'export' and 'export.environmentID' to the list. -func ExpandedViperKeys() (keys []string) { - leafKeys := ViperKeys() +func ExpandedKoanfKeys() (keys []string) { + leafKeys := KoanfKeys() for _, key := range leafKeys { keySplit := strings.Split(key, ".") for i := range keySplit { curKey := strings.Join(keySplit[:i+1], ".") if !slices.ContainsFunc(keys, func(v string) bool { - return strings.EqualFold(v, curKey) + return v == curKey }) { keys = append(keys, curKey) } @@ -62,25 +62,25 @@ func ExpandedViperKeys() (keys []string) { return keys } -func ValidateParentViperKey(viperKey string) error { - validKeys := ExpandedViperKeys() +func ValidateParentKoanfKey(koanfKey string) error { + validKeys := ExpandedKoanfKeys() for _, vKey := range validKeys { - if strings.EqualFold(vKey, viperKey) { + if vKey == koanfKey { return nil } } - return fmt.Errorf("key '%s' is not recognized as a valid configuration key.\nUse 'pingcli config list-keys' to view all available keys", viperKey) + return fmt.Errorf("key '%s' is not recognized as a valid configuration key.\nUse 'pingcli config list-keys' to view all available keys", koanfKey) } -func OptionFromViperKey(viperKey string) (opt options.Option, err error) { +func OptionFromKoanfKey(koanfKey string) (opt options.Option, err error) { for _, opt := range options.Options() { - if strings.EqualFold(opt.ViperKey, viperKey) { + if opt.KoanfKey == koanfKey { return opt, nil } } - return opt, fmt.Errorf("failed to get option: no option found for viper key: %s", viperKey) + return opt, fmt.Errorf("failed to get option: no option found for koanf key: %s", koanfKey) } func InitAllOptions() { diff --git a/internal/configuration/configuration_test.go b/internal/configuration/configuration_test.go index 7c3a6c93..7f79820d 100644 --- a/internal/configuration/configuration_test.go +++ b/internal/configuration/configuration_test.go @@ -7,75 +7,75 @@ import ( "github.com/pingidentity/pingcli/internal/configuration" "github.com/pingidentity/pingcli/internal/testing/testutils" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" ) -// Test ValidateViperKey function -func Test_ValidateViperKey(t *testing.T) { - testutils_viper.InitVipers(t) +// Test ValidateKoanfKey function +func Test_ValidateKoanfKey(t *testing.T) { + testutils_koanf.InitKoanfs(t) - err := configuration.ValidateViperKey("noColor") + err := configuration.ValidateKoanfKey("noColor") if err != nil { - t.Errorf("ValidateViperKey returned error: %v", err) + t.Errorf("ValidateKoanfKey returned error: %v", err) } } -// Test ValidateViperKey function fails with invalid key -func Test_ValidateViperKey_InvalidKey(t *testing.T) { - testutils_viper.InitVipers(t) +// Test ValidateKoanfKey function fails with invalid key +func Test_ValidateKoanfKey_InvalidKey(t *testing.T) { + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `^key '.*' is not recognized as a valid configuration key.\s*Use 'pingcli config list-keys' to view all available keys` - err := configuration.ValidateViperKey("invalid-key") + err := configuration.ValidateKoanfKey("invalid-key") testutils.CheckExpectedError(t, err, &expectedErrorPattern) } -// Test ValidateViperKey function fails with empty key -func Test_ValidateViperKey_EmptyKey(t *testing.T) { - testutils_viper.InitVipers(t) +// Test ValidateKoanfKey function fails with empty key +func Test_ValidateKoanfKey_EmptyKey(t *testing.T) { + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `^key '' is not recognized as a valid configuration key.\s*Use 'pingcli config list-keys' to view all available keys` - err := configuration.ValidateViperKey("") + err := configuration.ValidateKoanfKey("") testutils.CheckExpectedError(t, err, &expectedErrorPattern) } -// Test ValidateParentViperKey function -func Test_ValidateParentViperKey(t *testing.T) { - testutils_viper.InitVipers(t) +// Test ValidateParentKoanfKey function +func Test_ValidateParentKoanfKey(t *testing.T) { + testutils_koanf.InitKoanfs(t) - err := configuration.ValidateParentViperKey("service") + err := configuration.ValidateParentKoanfKey("service") if err != nil { - t.Errorf("ValidateParentViperKey returned error: %v", err) + t.Errorf("ValidateParentKoanfKey returned error: %v", err) } } -// Test ValidateParentViperKey function fails with invalid key -func Test_ValidateParentViperKey_InvalidKey(t *testing.T) { - testutils_viper.InitVipers(t) +// Test ValidateParentKoanfKey function fails with invalid key +func Test_ValidateParentKoanfKey_InvalidKey(t *testing.T) { + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `^key '.*' is not recognized as a valid configuration key.\s*Use 'pingcli config list-keys' to view all available keys` - err := configuration.ValidateParentViperKey("invalid-key") + err := configuration.ValidateParentKoanfKey("invalid-key") testutils.CheckExpectedError(t, err, &expectedErrorPattern) } -// Test ValidateParentViperKey function fails with empty key -func Test_ValidateParentViperKey_EmptyKey(t *testing.T) { - testutils_viper.InitVipers(t) +// Test ValidateParentKoanfKey function fails with empty key +func Test_ValidateParentKoanfKey_EmptyKey(t *testing.T) { + testutils_koanf.InitKoanfs(t) expectedErrorPattern := `^key '' is not recognized as a valid configuration key.\s*Use 'pingcli config list-keys' to view all available keys` - err := configuration.ValidateParentViperKey("") + err := configuration.ValidateParentKoanfKey("") testutils.CheckExpectedError(t, err, &expectedErrorPattern) } -// Test OptionFromViperKey function -func Test_OptionFromViperKey(t *testing.T) { - testutils_viper.InitVipers(t) +// Test OptionFromKoanfKey function +func Test_OptionFromKoanfKey(t *testing.T) { + testutils_koanf.InitKoanfs(t) - opt, err := configuration.OptionFromViperKey("noColor") + opt, err := configuration.OptionFromKoanfKey("noColor") if err != nil { - t.Errorf("OptionFromViperKey returned error: %v", err) + t.Errorf("OptionFromKoanfKey returned error: %v", err) } - if opt.ViperKey != "noColor" { - t.Errorf("OptionFromViperKey returned invalid option: %v", opt) + if opt.KoanfKey != "noColor" { + t.Errorf("OptionFromKoanfKey returned invalid option: %v", opt) } } diff --git a/internal/configuration/options/options.go b/internal/configuration/options/options.go index 0c994d9b..3f7c1c8b 100644 --- a/internal/configuration/options/options.go +++ b/internal/configuration/options/options.go @@ -38,7 +38,7 @@ type Option struct { Flag *pflag.Flag Sensitive bool Type OptionType - ViperKey string + KoanfKey string } func Options() []Option { @@ -96,9 +96,9 @@ func Options() []Option { RequestFailOption, } - // Sort the options list by viper key + // Sort the options list by koanf key slices.SortFunc(optList, func(opt1, opt2 Option) int { - return strings.Compare(opt1.ViperKey, opt2.ViperKey) + return strings.Compare(opt1.KoanfKey, opt2.KoanfKey) }) return optList @@ -153,7 +153,7 @@ var ( PlatformExportPingOneEnvironmentIDOption Option ) -// Generic viper profile options +// Generic koanf profile options var ( ProfileDescriptionOption Option ) diff --git a/internal/configuration/options/options_test.go b/internal/configuration/options/options_test.go index f6ca0d06..bd6e1c16 100644 --- a/internal/configuration/options/options_test.go +++ b/internal/configuration/options/options_test.go @@ -9,19 +9,19 @@ import ( "testing" "github.com/pingidentity/pingcli/internal/configuration/options" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" ) func Test_outputOptionsMDInfo(t *testing.T) { // Skip this test. Use only to generate markdown table for documentation t.SkipNow() - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) propertyCategoryInformation := make(map[string][]string) for _, option := range options.Options() { - if option.ViperKey == "" || option.Flag == nil { + if option.KoanfKey == "" || option.Flag == nil { continue } @@ -36,11 +36,11 @@ func Test_outputOptionsMDInfo(t *testing.T) { // Replace newlines with '

' usageString = strings.ReplaceAll(usageString, "\n", "

") - if !strings.Contains(option.ViperKey, ".") { - propertyCategoryInformation["general"] = append(propertyCategoryInformation["general"], fmt.Sprintf("| %s | %s | %s | %s |", option.ViperKey, option.Type, flagInfo, usageString)) + if !strings.Contains(option.KoanfKey, ".") { + propertyCategoryInformation["general"] = append(propertyCategoryInformation["general"], fmt.Sprintf("| %s | %s | %s | %s |", option.KoanfKey, option.Type, flagInfo, usageString)) } else { - rootKey := strings.Split(option.ViperKey, ".")[0] - propertyCategoryInformation[rootKey] = append(propertyCategoryInformation[rootKey], fmt.Sprintf("| %s | %s | %s | %s |", option.ViperKey, option.Type, flagInfo, usageString)) + rootKey := strings.Split(option.KoanfKey, ".")[0] + propertyCategoryInformation[rootKey] = append(propertyCategoryInformation[rootKey], fmt.Sprintf("| %s | %s | %s | %s |", option.KoanfKey, option.Type, flagInfo, usageString)) } } diff --git a/internal/configuration/platform/export.go b/internal/configuration/platform/export.go index 886654af..8e27b108 100644 --- a/internal/configuration/platform/export.go +++ b/internal/configuration/platform/export.go @@ -43,8 +43,8 @@ func initFormatOption() { Value: cobraValue, }, Sensitive: false, - Type: options.ENUM_STRING, - ViperKey: "export.format", + Type: options.ENUM_EXPORT_FORMAT, + KoanfKey: "export.format", } } @@ -71,8 +71,8 @@ func initServiceGroupOption() { Value: cobraValue, }, Sensitive: false, + KoanfKey: "export.serviceGroup", Type: options.ENUM_EXPORT_SERVICE_GROUP, - ViperKey: "export.serviceGroup", } } @@ -102,7 +102,7 @@ func initServicesOption() { }, Sensitive: false, Type: options.ENUM_EXPORT_SERVICES, - ViperKey: "export.services", + KoanfKey: "export.services", } } @@ -128,7 +128,7 @@ func initOutputDirectoryOption() { }, Sensitive: false, Type: options.ENUM_STRING, - ViperKey: "export.outputDirectory", + KoanfKey: "export.outputDirectory", } } @@ -151,8 +151,8 @@ func initOverwriteOption() { NoOptDefVal: "true", // Make this flag a boolean flag }, Sensitive: false, + KoanfKey: "export.overwrite", Type: options.ENUM_BOOL, - ViperKey: "export.overwrite", } } @@ -172,8 +172,7 @@ func initPingOneEnvironmentIDOption() { Usage: "The ID of the PingOne environment to export. Must be a valid PingOne UUID.", Value: cobraValue, }, - Sensitive: false, - Type: options.ENUM_UUID, - ViperKey: "export.pingone.environmentID", + KoanfKey: "export.pingOne.environmentID", + Type: options.ENUM_UUID, } } diff --git a/internal/configuration/profiles/profiles.go b/internal/configuration/profiles/profiles.go index f3e33331..24bedb11 100644 --- a/internal/configuration/profiles/profiles.go +++ b/internal/configuration/profiles/profiles.go @@ -20,6 +20,6 @@ func initDescriptionOption() { Flag: nil, // No flag Sensitive: false, Type: options.ENUM_STRING, - ViperKey: "description", + KoanfKey: "description", } } diff --git a/internal/configuration/request/request.go b/internal/configuration/request/request.go index c549733b..70bf779d 100644 --- a/internal/configuration/request/request.go +++ b/internal/configuration/request/request.go @@ -41,7 +41,7 @@ func initDataOption() { }, Sensitive: false, Type: options.ENUM_STRING, - ViperKey: "", // No viper key + KoanfKey: "", // No koanf key } } @@ -64,7 +64,7 @@ func initDataRawOption() { }, Sensitive: false, Type: options.ENUM_STRING, - ViperKey: "", // No viper key + KoanfKey: "", // No koanf key } } @@ -89,7 +89,7 @@ func initHeaderOption() { }, Sensitive: false, Type: options.ENUM_HEADER, - ViperKey: "", // No viper key + KoanfKey: "", // No koanf key } } @@ -118,7 +118,7 @@ func initHTTPMethodOption() { }, Sensitive: false, Type: options.ENUM_REQUEST_HTTP_METHOD, - ViperKey: "", // No viper key + KoanfKey: "", // No koanf key } } @@ -147,7 +147,7 @@ func initServiceOption() { }, Sensitive: false, Type: options.ENUM_REQUEST_SERVICE, - ViperKey: "request.service", + KoanfKey: "request.service", } } @@ -162,7 +162,7 @@ func initAccessTokenOption() { Flag: nil, Sensitive: true, Type: options.ENUM_STRING, - ViperKey: "request.accessToken", + KoanfKey: "request.accessToken", } } @@ -177,7 +177,7 @@ func initAccessTokenExpiryOption() { Flag: nil, // No flag Sensitive: false, Type: options.ENUM_INT, - ViperKey: "request.accessTokenExpiry", + KoanfKey: "request.accessTokenExpiry", } } @@ -199,6 +199,6 @@ func initFailOption() { }, Sensitive: false, Type: options.ENUM_BOOL, - ViperKey: "request.fail", + KoanfKey: "request.fail", } } diff --git a/internal/configuration/root/root.go b/internal/configuration/root/root.go index f59f9287..057f6b5a 100644 --- a/internal/configuration/root/root.go +++ b/internal/configuration/root/root.go @@ -34,7 +34,7 @@ func initActiveProfileOption() { Flag: nil, // No flag Sensitive: false, Type: options.ENUM_STRING, - ViperKey: "activeProfile", + KoanfKey: "activeProfile", } } @@ -56,7 +56,7 @@ func initProfileOption() { }, Sensitive: false, Type: options.ENUM_STRING, - ViperKey: "", // No viper key + KoanfKey: "", // No koanf key } } @@ -78,7 +78,7 @@ func initColorOption() { }, Sensitive: false, Type: options.ENUM_BOOL, - ViperKey: "noColor", + KoanfKey: "noColor", } } @@ -101,7 +101,7 @@ func initConfigOption() { }, Sensitive: false, Type: options.ENUM_STRING, - ViperKey: "", // No viper key + KoanfKey: "", // No koanf key } } @@ -127,7 +127,7 @@ func initDetailedExitCodeOption() { }, Sensitive: false, Type: options.ENUM_BOOL, - ViperKey: "detailedExitCode", + KoanfKey: "detailedExitCode", } } @@ -155,7 +155,7 @@ func initOutputFormatOption() { }, Sensitive: false, Type: options.ENUM_OUTPUT_FORMAT, - ViperKey: "outputFormat", + KoanfKey: "outputFormat", } } @@ -178,7 +178,7 @@ func initUnmaskSecretValuesOption() { }, Sensitive: false, Type: options.ENUM_BOOL, - ViperKey: "", // No ViperKey + KoanfKey: "", // No KoanfKey } } diff --git a/internal/configuration/services/pingfederate.go b/internal/configuration/services/pingfederate.go index 53da7c23..59b96a1f 100644 --- a/internal/configuration/services/pingfederate.go +++ b/internal/configuration/services/pingfederate.go @@ -46,7 +46,7 @@ func initHTTPSHostOption() { }, Sensitive: false, Type: options.ENUM_STRING, - ViperKey: "service.pingfederate.httpsHost", + KoanfKey: "service.pingFederate.httpsHost", } } @@ -69,7 +69,7 @@ func initAdminAPIPathOption() { }, Sensitive: false, Type: options.ENUM_STRING, - ViperKey: "service.pingfederate.adminAPIPath", + KoanfKey: "service.pingFederate.adminAPIPath", } } @@ -94,7 +94,7 @@ func initXBypassExternalValidationHeaderOption() { }, Sensitive: false, Type: options.ENUM_BOOL, - ViperKey: "service.pingfederate.xBypassExternalValidationHeader", + KoanfKey: "service.pingFederate.xBypassExternalValidationHeader", } } @@ -119,7 +119,7 @@ func initCACertificatePemFilesOption() { }, Sensitive: false, Type: options.ENUM_STRING_SLICE, - ViperKey: "service.pingfederate.caCertificatePemFiles", + KoanfKey: "service.pingFederate.caCertificatePEMFiles", } } @@ -144,7 +144,7 @@ func initInsecureTrustAllTLSOption() { }, Sensitive: false, Type: options.ENUM_BOOL, - ViperKey: "service.pingfederate.insecureTrustAllTLS", + KoanfKey: "service.pingFederate.insecureTrustAllTLS", } } @@ -168,7 +168,7 @@ func initUsernameOption() { }, Sensitive: false, Type: options.ENUM_STRING, - ViperKey: "service.pingfederate.authentication.basicAuth.username", + KoanfKey: "service.pingFederate.authentication.basicAuth.username", } } @@ -191,7 +191,7 @@ func initPasswordOption() { }, Sensitive: true, Type: options.ENUM_STRING, - ViperKey: "service.pingfederate.authentication.basicAuth.password", + KoanfKey: "service.pingFederate.authentication.basicAuth.password", } } @@ -214,7 +214,7 @@ func initAccessTokenOption() { }, Sensitive: true, Type: options.ENUM_STRING, - ViperKey: "service.pingfederate.authentication.accessTokenAuth.accessToken", + KoanfKey: "service.pingFederate.authentication.accessTokenAuth.accessToken", } } @@ -237,7 +237,7 @@ func initClientIDOption() { }, Sensitive: false, Type: options.ENUM_STRING, - ViperKey: "service.pingfederate.authentication.clientCredentialsAuth.clientID", + KoanfKey: "service.pingFederate.authentication.clientCredentialsAuth.clientID", } } @@ -260,7 +260,7 @@ func initClientSecretOption() { }, Sensitive: true, Type: options.ENUM_STRING, - ViperKey: "service.pingfederate.authentication.clientCredentialsAuth.clientSecret", + KoanfKey: "service.pingFederate.authentication.clientCredentialsAuth.clientSecret", } } @@ -283,7 +283,7 @@ func initTokenURLOption() { }, Sensitive: false, Type: options.ENUM_STRING, - ViperKey: "service.pingfederate.authentication.clientCredentialsAuth.tokenURL", + KoanfKey: "service.pingFederate.authentication.clientCredentialsAuth.tokenURL", } } @@ -309,7 +309,7 @@ func initScopesOption() { }, Sensitive: false, Type: options.ENUM_STRING_SLICE, - ViperKey: "service.pingfederate.authentication.clientCredentialsAuth.scopes", + KoanfKey: "service.pingFederate.authentication.clientCredentialsAuth.scopes", } } @@ -337,6 +337,6 @@ func initPingFederateAuthenticationTypeOption() { }, Sensitive: false, Type: options.ENUM_PINGFEDERATE_AUTH_TYPE, - ViperKey: "service.pingfederate.authentication.type", + KoanfKey: "service.pingFederate.authentication.type", } } diff --git a/internal/configuration/services/pingone.go b/internal/configuration/services/pingone.go index 18c46e88..b0ee1713 100644 --- a/internal/configuration/services/pingone.go +++ b/internal/configuration/services/pingone.go @@ -37,7 +37,7 @@ func initAuthenticationWorkerClientIDOption() { }, Sensitive: false, Type: options.ENUM_UUID, - ViperKey: "service.pingone.authentication.worker.clientID", + KoanfKey: "service.pingOne.authentication.worker.clientID", } } @@ -59,7 +59,7 @@ func initAuthenticationWorkerClientSecretOption() { }, Sensitive: true, Type: options.ENUM_STRING, - ViperKey: "service.pingone.authentication.worker.clientSecret", + KoanfKey: "service.pingOne.authentication.worker.clientSecret", } } @@ -82,7 +82,7 @@ func initAuthenticationWorkerEnvironmentIDOption() { }, Sensitive: false, Type: options.ENUM_UUID, - ViperKey: "service.pingone.authentication.worker.environmentID", + KoanfKey: "service.pingOne.authentication.worker.environmentID", } } @@ -109,7 +109,7 @@ func initPingOneAuthenticationTypeOption() { }, Sensitive: false, Type: options.ENUM_PINGONE_AUTH_TYPE, - ViperKey: "service.pingone.authentication.type", + KoanfKey: "service.pingOne.authentication.type", } } @@ -137,6 +137,6 @@ func initRegionCodeOption() { }, Sensitive: false, Type: options.ENUM_PINGONE_REGION_CODE, - ViperKey: "service.pingone.regionCode", + KoanfKey: "service.pingOne.regionCode", } } diff --git a/internal/connector/pingone/platform/pingone_platform_connector_test.go b/internal/connector/pingone/platform/pingone_platform_connector_test.go index 6ac20f18..29903b8e 100644 --- a/internal/connector/pingone/platform/pingone_platform_connector_test.go +++ b/internal/connector/pingone/platform/pingone_platform_connector_test.go @@ -82,11 +82,12 @@ func TestPlatformTerraformPlan(t *testing.T) { testableResource: pingone_platform_testable_resources.Environment(t, clientInfo), ignoredErrors: nil, }, - { - name: "Form", - testableResource: pingone_platform_testable_resources.Form(t, clientInfo), - ignoredErrors: nil, - }, + // TODO: Remove after completion of TRIAGE-26607 + // { + // name: "Form", + // testableResource: pingone_platform_testable_resources.Form(t, clientInfo), + // ignoredErrors: nil, + // }, { name: "FormsRecaptchaV2", testableResource: pingone_platform_testable_resources.FormsRecaptchaV2(t, clientInfo), diff --git a/internal/connector/pingone/platform/resources/form_test.go b/internal/connector/pingone/platform/resources/form_test.go index 547dc76d..3933e6a1 100644 --- a/internal/connector/pingone/platform/resources/form_test.go +++ b/internal/connector/pingone/platform/resources/form_test.go @@ -13,7 +13,9 @@ import ( "github.com/pingidentity/pingcli/internal/testing/testutils_resource/pingone_platform_testable_resources" ) +// TODO: Remove after completion of TRIAGE-26607 func Test_Form(t *testing.T) { + t.SkipNow() clientInfo := testutils.GetClientInfo(t) tr := pingone_platform_testable_resources.Form(t, clientInfo) diff --git a/internal/logger/logger.go b/internal/logger/logger.go index a8ae755b..b4846550 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -21,7 +21,7 @@ var ( // Create a get function for a standardized zerolog logger func Get() zerolog.Logger { once.Do(func() { - // Viper config is not initialized yet, so read environment variables directly + // Koanf config is not initialized yet, so read environment variables directly logLevelEnv := os.Getenv("PINGCLI_LOG_LEVEL") logPathEnv := os.Getenv("PINGCLI_LOG_PATH") diff --git a/internal/profiles/koanf.go b/internal/profiles/koanf.go new file mode 100644 index 00000000..91a28282 --- /dev/null +++ b/internal/profiles/koanf.go @@ -0,0 +1,346 @@ +package profiles + +import ( + "fmt" + "os" + "regexp" + "slices" + "strings" + + "github.com/knadh/koanf/parsers/yaml" + "github.com/knadh/koanf/providers/confmap" + "github.com/knadh/koanf/v2" + "github.com/pingidentity/pingcli/internal/configuration/options" +) + +var ( + k *KoanfConfig = NewKoanfConfig("") +) + +type KoanfConfig struct { + koanfInstance *koanf.Koanf + configFilePath *string +} + +func NewKoanfConfig(cnfFilePath string) *KoanfConfig { + return &KoanfConfig{ + koanfInstance: koanf.New("."), + configFilePath: &cnfFilePath, + } +} + +func GetKoanfConfig() *KoanfConfig { + return k +} + +func (k KoanfConfig) GetKoanfConfigFile() string { + return *k.configFilePath +} + +func (k *KoanfConfig) SetKoanfConfigFile(cnfFilePath string) { + k.configFilePath = &cnfFilePath +} + +func (k *KoanfConfig) KoanfInstance() *koanf.Koanf { + return k.koanfInstance +} + +func cobraParamValueFromOption(opt options.Option) (value string, ok bool) { + if opt.CobraParamValue != nil && opt.Flag.Changed { + return opt.CobraParamValue.String(), true + } + + return "", false +} + +func GetActiveProfileName(k *koanf.Koanf) string { + if k.Exists(options.RootActiveProfileOption.CobraParamName) { + return k.String(options.RootActiveProfileOption.CobraParamName) + } + + return "" +} + +func KoanfValueFromOption(opt options.Option, pName string) (value string, ok bool, err error) { + if opt.KoanfKey != "" { + var ( + kValue any + mainKoanfInstance = GetKoanfConfig() + ) + + // Case 1: Koanf Key is the ActiveProfile Key, get value from main koanf instance + if opt.KoanfKey != "" && opt.KoanfKey == options.RootActiveProfileOption.KoanfKey && mainKoanfInstance != nil { + kValue = mainKoanfInstance.KoanfInstance().Get(opt.KoanfKey) + } else { + // // Case 2: --profile flag has been set, get value from set profile koanf instance + // // Case 3: no --profile flag set, get value from active profile koanf instance defined in main koanf instance + // // This recursive call is safe, as options.RootProfileOption.KoanfKey is not set + if pName == "" { + pName, err = GetOptionValue(options.RootProfileOption) + if err != nil { + return "", false, err + } + if pName == "" { + pName, err = GetOptionValue(options.RootActiveProfileOption) + if err != nil { + return "", false, err + } + } + } + + // Get the sub koanf instance for the profile + subKoanf, err := mainKoanfInstance.GetProfileKoanf(pName) + if err != nil { + return "", false, err + } + + kValue = subKoanf.Get(opt.KoanfKey) + } + + switch typedValue := kValue.(type) { + case nil: + return "", false, nil + case string: + return typedValue, true, nil + case []string: + return strings.Join(typedValue, ","), true, nil + case []any: + strSlice := []string{} + for _, v := range typedValue { + strSlice = append(strSlice, fmt.Sprintf("%v", v)) + } + + return strings.Join(strSlice, ","), true, nil + default: + return fmt.Sprintf("%v", typedValue), true, nil + } + } + + return "", false, nil +} + +// Get all profile names from config.yaml configuration file +// Returns a sorted slice of profile names +func (k KoanfConfig) ProfileNames() (profileNames []string) { + keySet := make(map[string]struct{}) + mainKoanfKeys := k.KoanfInstance().All() + for key := range mainKoanfKeys { + // Do not add Active profile koanf key to profileNames + if key == options.RootActiveProfileOption.KoanfKey { + continue + } + + pName := strings.Split(key, ".")[0] + if _, ok := keySet[pName]; !ok { + keySet[pName] = struct{}{} + profileNames = append(profileNames, pName) + } + } + + // Sort the profile names + slices.Sort(profileNames) + + return profileNames +} + +// The profile name must contain only alphanumeric characters, underscores, and dashes +// The profile name cannot be empty +func (k KoanfConfig) ValidateProfileNameFormat(pName string) (err error) { + if pName == "" { + return fmt.Errorf("invalid profile name: profile name cannot be empty") + } + + re := regexp.MustCompile(`^[a-zA-Z0-9\_\-]+$`) + if !re.MatchString(pName) { + return fmt.Errorf("invalid profile name: '%s'. name must contain only alphanumeric characters, underscores, and dashes", pName) + } + + return nil +} + +func (k KoanfConfig) ChangeActiveProfile(pName string) (err error) { + if err = k.ValidateExistingProfileName(pName); err != nil { + return err + } + + err = k.KoanfInstance().Set(options.RootActiveProfileOption.KoanfKey, pName) + if err != nil { + return fmt.Errorf("error setting active profile: %w", err) + } + + if err = k.WriteFile(); err != nil { + return fmt.Errorf("failed to write config file for set active profile: %w", err) + } + + return nil +} + +// The profile name must exist +func (k KoanfConfig) ValidateExistingProfileName(pName string) (err error) { + if pName == "" { + return fmt.Errorf("invalid profile name: profile name cannot be empty") + } + + pNames := k.ProfileNames() + + if !slices.ContainsFunc(pNames, func(n string) bool { + return n == pName + }) { + return fmt.Errorf("invalid profile name: '%s' profile does not exist", pName) + } + + return nil +} + +// The profile name format must be valid +// The new profile name must be unique +func (k KoanfConfig) ValidateNewProfileName(pName string) (err error) { + if err = k.ValidateProfileNameFormat(pName); err != nil { + return err + } + + pNames := k.ProfileNames() + + if slices.ContainsFunc(pNames, func(n string) bool { + return n == pName + }) { + return fmt.Errorf("invalid profile name: '%s'. profile already exists", pName) + } + + return nil +} + +func (k KoanfConfig) GetProfileKoanf(pName string) (subKoanf *koanf.Koanf, err error) { + if err = k.ValidateExistingProfileName(pName); err != nil { + return nil, err + } + + // Create a new koanf instance for the profile + subKoanf = koanf.New(".") + err = subKoanf.Load(confmap.Provider(k.KoanfInstance().Cut(pName).All(), "."), nil) + if err != nil { + return nil, fmt.Errorf("error marshalling koanf: %w", err) + } + + return subKoanf, nil +} + +func (k KoanfConfig) WriteFile() (err error) { + encodedConfig, err := k.KoanfInstance().Marshal(yaml.Parser()) + if err != nil { + return fmt.Errorf("error marshalling koanf: %w", err) + } + + err = os.WriteFile(k.GetKoanfConfigFile(), encodedConfig, 0600) + if err != nil { + return fmt.Errorf("error opening file (%s): %w", k.GetKoanfConfigFile(), err) + } + + return nil +} + +func (k KoanfConfig) SaveProfile(pName string, subKoanf *koanf.Koanf) (err error) { + err = k.KoanfInstance().MergeAt(subKoanf, pName) + if err != nil { + return fmt.Errorf("error merging koanf: %w", err) + } + + err = k.WriteFile() + if err != nil { + return fmt.Errorf("failed to save profile '%s': %w", pName, err) + } + + return nil +} + +func (k KoanfConfig) DeleteProfile(pName string) (err error) { + if err = k.ValidateExistingProfileName(pName); err != nil { + return err + } + + activeProfileName, err := GetOptionValue(options.RootActiveProfileOption) + if err != nil { + return err + } + + if activeProfileName == pName { + return fmt.Errorf("'%s' is the active profile and cannot be deleted", pName) + } + + // Delete the profile from the main koanf + k.KoanfInstance().Delete(pName) + + err = k.WriteFile() + if err != nil { + return fmt.Errorf("failed to delete profile '%s': %w", pName, err) + } + + return nil +} + +func (k KoanfConfig) DefaultMissingKoanfKeys() (err error) { + // For each profile, if a koanf key from an option doesn't exist, set it to the default value + for _, pName := range k.ProfileNames() { + subKoanf, err := k.GetProfileKoanf(pName) + if err != nil { + return err + } + + for _, opt := range options.Options() { + if opt.KoanfKey == "" || opt.KoanfKey == options.RootActiveProfileOption.KoanfKey { + continue + } + + if !subKoanf.Exists(opt.KoanfKey) { + err = subKoanf.Set(opt.KoanfKey, opt.DefaultValue) + if err != nil { + return fmt.Errorf("error setting default value for koanf key %s: %w", opt.KoanfKey, err) + } + } + } + err = k.SaveProfile(pName, subKoanf) + if err != nil { + return fmt.Errorf("failed to save profile '%s': %w", pName, err) + } + } + + return nil +} + +func GetOptionValue(opt options.Option) (string, error) { + // 1st priority: cobra param flag value + if cobraParamValue, ok := cobraParamValueFromOption(opt); ok { + return cobraParamValue, nil + } + + // 2nd priority: environment variable value + if pFlagValue := os.Getenv(opt.EnvVar); pFlagValue != "" { + return pFlagValue, nil + } + + // 3rd priority: koanf value + koanfValue, ok, _ := KoanfValueFromOption(opt, "") + if ok { + return koanfValue, nil + } + + // 4th priority: default value + if opt.DefaultValue != nil { + return opt.DefaultValue.String(), nil + } + + // This is a error, as it means the option is not configured internally to contain one of the 4 values above. + // This should never happen, as all options should at least have a default value. + return "", fmt.Errorf("failed to get option value: no value found: %v", opt) +} + +func MaskValue(value any) string { + stringValue, ok := value.(string) + if ok && stringValue == "" { + return stringValue + } + + // Mask all values to the same asterisk length + // providing no additional information about the value when logged. + return strings.Repeat("*", 8) +} diff --git a/internal/profiles/validate.go b/internal/profiles/validate.go index 7366c31f..a404ff8a 100644 --- a/internal/profiles/validate.go +++ b/internal/profiles/validate.go @@ -7,20 +7,15 @@ import ( "slices" "strings" + "github.com/knadh/koanf/v2" "github.com/pingidentity/pingcli/internal/configuration" "github.com/pingidentity/pingcli/internal/configuration/options" "github.com/pingidentity/pingcli/internal/customtypes" - "github.com/spf13/viper" ) func Validate() (err error) { // Get a slice of all profile names configured in the config.yaml file - profileNames := GetMainConfig().ProfileNames() - - // Iterate over the profileNames and convert each to lowercase - for i := range profileNames { - profileNames[i] = strings.ToLower(profileNames[i]) - } + profileNames := GetKoanfConfig().ProfileNames() // Validate profile names if err = validateProfileNames(profileNames); err != nil { @@ -29,27 +24,27 @@ func Validate() (err error) { // Make sure selected active profile is in the configuration file activeProfileName, err := GetOptionValue(options.RootActiveProfileOption) - activeProfileName = strings.ToLower(activeProfileName) if err != nil { return fmt.Errorf("failed to validate Ping CLI configuration: %w", err) } + if !slices.Contains(profileNames, activeProfileName) { return fmt.Errorf("failed to validate Ping CLI configuration: active profile '%s' not found in configuration "+ - "file %s", activeProfileName, GetMainConfig().ViperInstance().ConfigFileUsed()) + "file %s", activeProfileName, GetKoanfConfig().GetKoanfConfigFile()) } - // for each profile key, validate the profile viper + // for each profile key, validate the profile koanf for _, pName := range profileNames { - subViper, err := GetMainConfig().GetProfileViper(pName) + subKoanf, err := GetKoanfConfig().GetProfileKoanf(pName) if err != nil { return fmt.Errorf("failed to validate Ping CLI configuration: %w", err) } - if err := validateProfileKeys(pName, subViper); err != nil { + if err := validateProfileKeys(pName, subKoanf); err != nil { return fmt.Errorf("failed to validate Ping CLI configuration: %w", err) } - if err := validateProfileValues(pName, subViper); err != nil { + if err := validateProfileValues(pName, subKoanf); err != nil { return fmt.Errorf("failed to validate Ping CLI configuration: %w", err) } } @@ -59,7 +54,7 @@ func Validate() (err error) { func validateProfileNames(profileNames []string) error { for _, profileName := range profileNames { - if err := GetMainConfig().ValidateProfileNameFormat(profileName); err != nil { + if err := GetKoanfConfig().ValidateProfileNameFormat(profileName); err != nil { return err } } @@ -67,16 +62,16 @@ func validateProfileNames(profileNames []string) error { return nil } -func validateProfileKeys(profileName string, profileViper *viper.Viper) error { - validProfileKeys := configuration.ViperKeys() +func validateProfileKeys(profileName string, profileKoanf *koanf.Koanf) error { + validProfileKeys := configuration.KoanfKeys() - // Get all keys viper has loaded from config file. - // If a key found in the config file is not in the viperKeys list, + // Get all keys koanf has loaded from config file. + // If a key found in the config file is not in the koanfKeys list, // it is an invalid key. var invalidKeys []string - for _, key := range profileViper.AllKeys() { + for key := range profileKoanf.All() { if !slices.ContainsFunc(validProfileKeys, func(v string) bool { - return strings.EqualFold(v, key) + return v == key }) { invalidKeys = append(invalidKeys, key) } @@ -92,14 +87,14 @@ func validateProfileKeys(profileName string, profileViper *viper.Viper) error { return nil } -func validateProfileValues(pName string, profileViper *viper.Viper) (err error) { - for _, key := range profileViper.AllKeys() { - opt, err := configuration.OptionFromViperKey(key) +func validateProfileValues(pName string, profileKoanf *koanf.Koanf) (err error) { + for key := range profileKoanf.All() { + opt, err := configuration.OptionFromKoanfKey(key) if err != nil { return err } - vValue := profileViper.Get(key) + vValue := profileKoanf.Get(key) switch opt.Type { case options.ENUM_BOOL: diff --git a/internal/profiles/validate_test.go b/internal/profiles/validate_test.go index 0e9a564b..54fc324a 100644 --- a/internal/profiles/validate_test.go +++ b/internal/profiles/validate_test.go @@ -6,12 +6,12 @@ import ( "testing" "github.com/pingidentity/pingcli/internal/profiles" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" ) // Test Validate function func TestValidate(t *testing.T) { - testutils_viper.InitVipers(t) + testutils_koanf.InitKoanfs(t) err := profiles.Validate() if err != nil { @@ -24,11 +24,11 @@ func TestValidateInvalidProfile(t *testing.T) { fileContents := `activeProfile: default default: description: "default description" - pingone: + pingOne: export: - environmentid: "invalid"` + environmentID: "invalid"` - testutils_viper.InitVipersCustomFile(t, fileContents) + testutils_koanf.InitKoanfsCustomFile(t, fileContents) err := profiles.Validate() if err == nil { @@ -41,10 +41,10 @@ func TestValidateInvalidRegion(t *testing.T) { fileContents := `activeProfile: default default: description: "default description" - pingone: + pingOne: region: "invalid"` - testutils_viper.InitVipersCustomFile(t, fileContents) + testutils_koanf.InitKoanfsCustomFile(t, fileContents) err := profiles.Validate() if err == nil { @@ -60,7 +60,7 @@ default: pingcli: noColor: invalid` - testutils_viper.InitVipersCustomFile(t, fileContents) + testutils_koanf.InitKoanfsCustomFile(t, fileContents) err := profiles.Validate() if err == nil { @@ -76,7 +76,7 @@ default: pingcli: outputFormat: invalid` - testutils_viper.InitVipersCustomFile(t, fileContents) + testutils_koanf.InitKoanfsCustomFile(t, fileContents) err := profiles.Validate() if err == nil { @@ -92,7 +92,7 @@ default: invalid(&*^&*^&*^**$): description: "default description"` - testutils_viper.InitVipersCustomFile(t, fileContents) + testutils_koanf.InitKoanfsCustomFile(t, fileContents) err := profiles.Validate() if err == nil { diff --git a/internal/profiles/viper.go b/internal/profiles/viper.go deleted file mode 100644 index 1f99cbac..00000000 --- a/internal/profiles/viper.go +++ /dev/null @@ -1,458 +0,0 @@ -// Copyright © 2025 Ping Identity Corporation - -package profiles - -/* The main viper instance should ONLY interact with the configuration file -on disk. No viper overrides, environment variable bindings, or pflag -bindings should be used with this viper instance. This keeps the config -file as the ONLY source of truth for the main viper instance, and prevents -profile drift, as well as active profile drift and other niche bugs. As a -result, much of the logic in this file avoids the use of mainViper.Set(), and -goes out of the way to modify the config file.*/ - -import ( - "bytes" - "encoding/json" - "fmt" - "os" - "regexp" - "slices" - "strings" - - "github.com/pingidentity/pingcli/internal/configuration/options" - "github.com/spf13/viper" - "gopkg.in/yaml.v3" -) - -type MainConfig struct { - viperInstance *viper.Viper -} - -var ( - mainViper *MainConfig = NewMainConfig() -) - -// Returns a new MainViper instance -func NewMainConfig() (newMainViper *MainConfig) { - newMainViper = &MainConfig{ - viperInstance: viper.New(), - } - - return newMainViper -} - -// Returns the MainViper struct -func GetMainConfig() *MainConfig { - return mainViper -} - -func (m MainConfig) ViperInstance() *viper.Viper { - return m.viperInstance -} - -func (m MainConfig) ChangeActiveProfile(pName string) (err error) { - if err = m.ValidateExistingProfileName(pName); err != nil { - return err - } - - tempViper := viper.New() - tempViper.SetConfigFile(m.ViperInstance().ConfigFileUsed()) - if err := tempViper.ReadInConfig(); err != nil { - return err - } - - tempViper.Set(options.RootActiveProfileOption.ViperKey, pName) - - if err = tempViper.WriteConfig(); err != nil { - return err - } - - if err = m.ViperInstance().ReadInConfig(); err != nil { - return err - } - - return nil -} - -func (m MainConfig) ChangeProfileName(oldPName, newPName string) (err error) { - if oldPName == newPName { - return nil - } - - err = m.ValidateExistingProfileName(oldPName) - if err != nil { - return err - } - - err = m.ValidateNewProfileName(newPName) - if err != nil { - return err - } - - subViper, err := m.GetProfileViper(oldPName) - if err != nil { - return err - } - - if err = m.DeleteProfile(oldPName); err != nil { - return err - } - - if err = m.SaveProfile(newPName, subViper); err != nil { - return err - } - - return nil -} - -func (m MainConfig) ChangeProfileDescription(pName, description string) (err error) { - if err = m.ValidateExistingProfileName(pName); err != nil { - return err - } - - subViper, err := m.GetProfileViper(pName) - if err != nil { - return err - } - - subViper.Set(options.ProfileDescriptionOption.ViperKey, description) - - if err = m.SaveProfile(pName, subViper); err != nil { - return err - } - - return nil -} - -func (m MainConfig) GetProfileViper(pName string) (subViper *viper.Viper, err error) { - if err = m.ValidateExistingProfileName(pName); err != nil { - return nil, err - } - - subViper = m.ViperInstance().Sub(pName) - if subViper == nil { - return nil, fmt.Errorf("failed to get profile viper: profile '%s' does not exist", pName) - } - - return subViper, nil -} - -// Viper gives no built-in delete or unset method for keys -// Using this "workaround" described here: https://github.com/spf13/viper/issues/632 -func (m MainConfig) DeleteProfile(pName string) (err error) { - if err = m.ValidateExistingProfileName(pName); err != nil { - return err - } - - activeProfileName, err := GetOptionValue(options.RootActiveProfileOption) - if err != nil { - return err - } - - if strings.EqualFold(activeProfileName, pName) { - return fmt.Errorf("'%s' is the active profile and cannot be deleted", pName) - } - - mainViperConfigMap := m.ViperInstance().AllSettings() - delete(mainViperConfigMap, pName) - - encodedConfig, err := json.MarshalIndent(mainViperConfigMap, "", " ") - if err != nil { - return err - } - - err = m.ViperInstance().ReadConfig(bytes.NewReader(encodedConfig)) - if err != nil { - return err - } - - err = m.ViperInstance().WriteConfig() - if err != nil { - return err - } - - return nil -} - -// Get all profile names from config.yaml configuration file -// Returns a sorted slice of profile names -func (m MainConfig) ProfileNames() (profileNames []string) { - keySet := make(map[string]struct{}) - mainViperKeys := m.ViperInstance().AllKeys() - for _, key := range mainViperKeys { - // Do not add Active profile viper key to profileNames - if strings.EqualFold(key, options.RootActiveProfileOption.ViperKey) { - continue - } - - pName := strings.Split(key, ".")[0] - if _, ok := keySet[pName]; !ok { - keySet[pName] = struct{}{} - profileNames = append(profileNames, pName) - } - } - - slices.Sort(profileNames) - - return profileNames -} - -func (m MainConfig) SaveProfile(pName string, subViper *viper.Viper) (err error) { - mainViperConfigMap := m.ViperInstance().AllSettings() - subViperConfigMap := subViper.AllSettings() - - mainViperConfigMap[pName] = subViperConfigMap - - encodedConfig, err := json.MarshalIndent(mainViperConfigMap, "", " ") - if err != nil { - return err - } - - err = m.ViperInstance().ReadConfig(bytes.NewReader(encodedConfig)) - if err != nil { - return err - } - - err = m.ViperInstance().WriteConfig() - if err != nil { - return err - } - - return nil -} - -// The profile name must exist -func (m MainConfig) ValidateExistingProfileName(pName string) (err error) { - if pName == "" { - return fmt.Errorf("invalid profile name: profile name cannot be empty") - } - - pNames := m.ProfileNames() - - if !slices.ContainsFunc(pNames, func(n string) bool { - return strings.EqualFold(n, pName) - }) { - return fmt.Errorf("invalid profile name: '%s' profile does not exist", pName) - } - - return nil -} - -// The profile name format must be valid -// The new profile name must be unique -func (m MainConfig) ValidateNewProfileName(pName string) (err error) { - if err = m.ValidateProfileNameFormat(pName); err != nil { - return err - } - - pNames := m.ProfileNames() - if slices.ContainsFunc(pNames, func(n string) bool { - return strings.EqualFold(n, pName) - }) { - return fmt.Errorf("invalid profile name: '%s'. profile already exists", pName) - } - - return nil -} - -// The profile name must contain only alphanumeric characters, underscores, and dashes -// The profile name cannot be empty -func (m MainConfig) ValidateProfileNameFormat(pName string) (err error) { - if pName == "" { - return fmt.Errorf("invalid profile name: profile name cannot be empty") - } - - re := regexp.MustCompile(`^[a-z0-9\_\-]+$`) - if !re.MatchString(pName) { - return fmt.Errorf("invalid profile name: '%s'. name must be lowercase and contain only alphanumeric characters, underscores, and dashes", pName) - } - - return nil -} - -// If the new profile name is the same as the existing profile name, that is valid -// Otherwise treat newPName as a new profile name and validate it -func (m MainConfig) ValidateUpdateExistingProfileName(ePName, newPName string) (err error) { - if ePName == newPName { - return nil - } - - if err = m.ValidateNewProfileName(newPName); err != nil { - return err - } - - return nil -} - -func (m MainConfig) ProfileToString(pName string) (yamlStr string, err error) { - if err = m.ValidateExistingProfileName(pName); err != nil { - return "", err - } - - subViper, err := m.GetProfileViper(pName) - if err != nil { - return "", err - } - - yaml, err := yaml.Marshal(subViper.AllSettings()) - if err != nil { - return "", fmt.Errorf("failed to yaml marshal active profile: %w", err) - } - - return string(yaml), nil -} - -func (m MainConfig) ProfileViperValue(pName, viperKey string) (yamlStr string, err error) { - if err = m.ValidateExistingProfileName(pName); err != nil { - return "", err - } - - subViper, err := m.GetProfileViper(pName) - if err != nil { - return "", err - } - - if !subViper.IsSet(viperKey) { - return "", fmt.Errorf("configuration key '%s' is not set in profile '%s'", viperKey, pName) - } - - yaml, err := yaml.Marshal(subViper.Get(viperKey)) - if err != nil { - return "", fmt.Errorf("failed to yaml marshal configuration value from key '%s': %w", viperKey, err) - } - - return string(yaml), nil -} - -func (m MainConfig) DefaultMissingViperKeys() (err error) { - // For each profile, if a viper key from an option doesn't exist, set it to the default value - for _, pName := range m.ProfileNames() { - subViper, err := m.GetProfileViper(pName) - if err != nil { - return err - } - - for _, opt := range options.Options() { - if opt.ViperKey == "" || opt.ViperKey == options.RootActiveProfileOption.ViperKey { - continue - } - if !subViper.IsSet(opt.ViperKey) { - subViper.Set(opt.ViperKey, opt.DefaultValue) - } - } - err = m.SaveProfile(pName, subViper) - if err != nil { - return fmt.Errorf("failed to save profile '%s': %w", pName, err) - } - } - - return nil -} - -func GetOptionValue(opt options.Option) (pFlagValue string, err error) { - // 1st priority: cobra param flag value - cobraParamValue, ok := cobraParamValueFromOption(opt) - if ok { - return cobraParamValue, nil - } - - // 2nd priority: environment variable value - pFlagValue = os.Getenv(opt.EnvVar) - if pFlagValue != "" { - return pFlagValue, nil - } - - // 3rd priority: viper value - viperValue, ok, err := ViperValueFromOption(opt) - if err != nil { - return "", err - } - if ok { - return viperValue, nil - } - - // 4th priority: default value - if opt.DefaultValue != nil { - pFlagValue = opt.DefaultValue.String() - - return pFlagValue, nil - } - - // This is a error, as it means the option is not configured internally to contain one of the 4 values above. - // This should never happen, as all options should at least have a default value. - return "", fmt.Errorf("failed to get option value: no value found: %v", opt) -} - -func MaskValue(value string) string { - if value == "" { - return "" - } - - // Mask all values to the same asterisk length - // providing no additional information about the value when logged. - return strings.Repeat("*", 8) -} - -func cobraParamValueFromOption(opt options.Option) (value string, ok bool) { - if opt.CobraParamValue != nil && opt.Flag.Changed { - return opt.CobraParamValue.String(), true - } - - return "", false -} - -func ViperValueFromOption(opt options.Option) (value string, ok bool, err error) { - mainConfig := GetMainConfig() - if opt.ViperKey != "" && mainConfig != nil { - var ( - vValue any - mainViperInstance = mainConfig.ViperInstance() - ) - - // Case 1: Viper Key is the ActiveProfile Key, get value from main viper instance - if opt.ViperKey == options.RootActiveProfileOption.ViperKey && mainViperInstance != nil { - vValue = mainViperInstance.Get(opt.ViperKey) - } else { - // Case 2: --profile flag has been set, get value from set profile viper instance - // Case 3: no --profile flag set, get value from active profile viper instance defined in main viper instance - // This recursive call is safe, as options.RootProfileOption.ViperKey is not set - pName, err := GetOptionValue(options.RootProfileOption) - if err != nil { - return "", false, err - } - if pName == "" { - pName, err = GetOptionValue(options.RootActiveProfileOption) - if err != nil { - return "", false, err - } - } - - subViper, err := mainConfig.GetProfileViper(pName) - if err != nil { - return "", false, err - } - - vValue = subViper.Get(opt.ViperKey) - } - - switch typedValue := vValue.(type) { - case nil: - return "", false, nil - case string: - return typedValue, true, nil - case []string: - return strings.Join(typedValue, ","), true, nil - case []any: - strSlice := []string{} - for _, v := range typedValue { - strSlice = append(strSlice, fmt.Sprintf("%v", v)) - } - - return strings.Join(strSlice, ","), true, nil - default: - return fmt.Sprintf("%v", typedValue), true, nil - } - } - - return "", false, nil -} diff --git a/internal/profiles/viper_test.go b/internal/profiles/viper_test.go deleted file mode 100644 index 11f54766..00000000 --- a/internal/profiles/viper_test.go +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright © 2025 Ping Identity Corporation - -package profiles_test - -import ( - "slices" - "testing" - - "github.com/pingidentity/pingcli/internal/configuration/options" - "github.com/pingidentity/pingcli/internal/profiles" - "github.com/pingidentity/pingcli/internal/testing/testutils" - "github.com/pingidentity/pingcli/internal/testing/testutils_viper" - "github.com/spf13/viper" -) - -// Test ChangeActiveProfile function -func Test_ChangeActiveProfile(t *testing.T) { - testutils_viper.InitVipers(t) - - mainConfig := profiles.GetMainConfig() - - err := mainConfig.ChangeActiveProfile("production") - if err != nil { - t.Errorf("ChangeActiveProfile returned error: %v", err) - } - - activeProfile, err := profiles.GetOptionValue(options.RootActiveProfileOption) - if err != nil { - t.Errorf("GetOptionValue returned error: %v", err) - } - - if activeProfile != "production" { - t.Errorf("activeProfile is %s, expected %s", activeProfile, "production") - } -} - -// Test ChangeActiveProfile function with invalid profile -func Test_ChangeActiveProfile_InvalidProfile(t *testing.T) { - testutils_viper.InitVipers(t) - - mainConfig := profiles.GetMainConfig() - - expectedErrorPattern := `^invalid profile name: '.*' profile does not exist$` - err := mainConfig.ChangeActiveProfile("invalid") - testutils.CheckExpectedError(t, err, &expectedErrorPattern) -} - -// Test ChangeProfileName function -func Test_ChangeProfileName(t *testing.T) { - testutils_viper.InitVipers(t) - - mainConfig := profiles.GetMainConfig() - - err := mainConfig.ChangeProfileName("production", "new") - if err != nil { - t.Errorf("ChangeProfileName returned error: %v", err) - } - - profiles := mainConfig.ProfileNames() - if len(profiles) != 2 { - t.Errorf("profiles length is %d, expected %d", len(profiles), 2) - } - - if slices.Contains(profiles, "production") { - t.Errorf("profiles contains production, expected it to be removed") - } - - if !slices.Contains(profiles, "new") { - t.Errorf("profiles does not contain new, expected it to be added") - } -} - -// Test ChangeProfileName function with same profile name -func Test_ChangeProfileName_SameProfileName(t *testing.T) { - testutils_viper.InitVipers(t) - - mainConfig := profiles.GetMainConfig() - - err := mainConfig.ChangeProfileName("production", "production") - if err != nil { - t.Errorf("ChangeProfileName returned error: %v", err) - } -} - -// Test ChangeProfileName function with invalid old profile name -func Test_ChangeProfileName_InvalidOldProfileName(t *testing.T) { - testutils_viper.InitVipers(t) - - mainConfig := profiles.GetMainConfig() - - expectedErrorPattern := `^invalid profile name: '.*' profile does not exist$` - err := mainConfig.ChangeProfileName("invalid", "new") - testutils.CheckExpectedError(t, err, &expectedErrorPattern) -} - -// Test ChangeProfileName function with invalid new profile name -func Test_ChangeProfileName_InvalidNewProfileName(t *testing.T) { - testutils_viper.InitVipers(t) - - mainConfig := profiles.GetMainConfig() - - expectedErrorPattern := `^invalid profile name: '.*'. profile already exists$` - err := mainConfig.ChangeProfileName("production", "default") - testutils.CheckExpectedError(t, err, &expectedErrorPattern) -} - -// Test DeleteProfile function -func Test_DeleteProfile(t *testing.T) { - testutils_viper.InitVipers(t) - - mainConfig := profiles.GetMainConfig() - - err := mainConfig.DeleteProfile("production") - if err != nil { - t.Errorf("DeleteProfile returned error: %v", err) - } - - profiles := mainConfig.ProfileNames() - if len(profiles) != 1 { - t.Errorf("profiles length is %d, expected %d", len(profiles), 0) - } - - if slices.Contains(profiles, "production") { - t.Errorf("profiles contains production, expected it to be removed") - } -} - -// Test DeleteProfile function with invalid profile name -func Test_DeleteProfile_InvalidProfileName(t *testing.T) { - testutils_viper.InitVipers(t) - - mainConfig := profiles.GetMainConfig() - - expectedErrorPattern := `^invalid profile name: '.*' profile does not exist$` - err := mainConfig.DeleteProfile("invalid") - testutils.CheckExpectedError(t, err, &expectedErrorPattern) -} - -// Test DeleteProfile function with active profile -func Test_DeleteProfile_ActiveProfile(t *testing.T) { - testutils_viper.InitVipers(t) - - mainConfig := profiles.GetMainConfig() - - expectedErrorPattern := `^'.*' is the active profile and cannot be deleted$` - err := mainConfig.DeleteProfile("default") - testutils.CheckExpectedError(t, err, &expectedErrorPattern) -} - -// Test SaveProfile function -func Test_SaveProfile(t *testing.T) { - testutils_viper.InitVipers(t) - - mainConfig := profiles.GetMainConfig() - - subViper := viper.New() - subViper.Set("description", "new description") - - err := mainConfig.SaveProfile("new", subViper) - if err != nil { - t.Errorf("SaveProfile returned error: %v", err) - } - - profiles := mainConfig.ProfileNames() - if len(profiles) != 3 { - t.Errorf("profiles length is %d, expected %d", len(profiles), 3) - } - - if !slices.Contains(profiles, "new") { - t.Errorf("profiles does not contain new, expected it to be added") - } -} - -// Test SaveProfile function with existing profile name -func Test_SaveProfile_ExistingProfileName(t *testing.T) { - testutils_viper.InitVipers(t) - - mainConfig := profiles.GetMainConfig() - - subViper := viper.New() - subViper.Set("description", "new description") - - err := mainConfig.SaveProfile("production", subViper) - testutils.CheckExpectedError(t, err, nil) - - profiles := mainConfig.ProfileNames() - if len(profiles) != 2 { - t.Errorf("profiles length is %d, expected %d", len(profiles), 2) - } - - if !slices.Contains(profiles, "production") { - t.Errorf("profiles does not contain production, expected it to be added") - } - - actual := mainConfig.ViperInstance().Get("production.description") - expected := "new description" - - if actual != expected { - t.Errorf("description is %s, expected %s", actual, expected) - } -} diff --git a/internal/testing/testutils_cobra/cobra_utils.go b/internal/testing/testutils_cobra/cobra_utils.go index 76ee5de2..c9aea826 100644 --- a/internal/testing/testutils_cobra/cobra_utils.go +++ b/internal/testing/testutils_cobra/cobra_utils.go @@ -8,7 +8,7 @@ import ( "github.com/pingidentity/pingcli/cmd" "github.com/pingidentity/pingcli/internal/configuration" - testutils_viper "github.com/pingidentity/pingcli/internal/testing/testutils_viper" + testutils_koanf "github.com/pingidentity/pingcli/internal/testing/testutils_koanf" ) // ExecutePingcli executes the pingcli command with the provided arguments @@ -22,7 +22,7 @@ func ExecutePingcli(t *testing.T, args ...string) (err error) { root := cmd.NewRootCommand("test-version", "test-commit") // Add config location to the root command - configFilepath := testutils_viper.CreateConfigFile(t) + configFilepath := testutils_koanf.CreateConfigFile(t) args = append([]string{"--config", configFilepath}, args...) root.SetArgs(args) @@ -44,7 +44,7 @@ func ExecutePingcliCaptureCobraOutput(t *testing.T, args ...string) (output stri root := cmd.NewRootCommand("test-version", "test-commit") // Add config location to the root command - configFilepath := testutils_viper.CreateConfigFile(t) + configFilepath := testutils_koanf.CreateConfigFile(t) args = append([]string{"--config", configFilepath}, args...) root.SetArgs(args) diff --git a/internal/testing/testutils_viper/viper_utils.go b/internal/testing/testutils_koanf/koanf_utils.go similarity index 60% rename from internal/testing/testutils_viper/viper_utils.go rename to internal/testing/testutils_koanf/koanf_utils.go index fe9f9aae..f6249233 100644 --- a/internal/testing/testutils_viper/viper_utils.go +++ b/internal/testing/testutils_koanf/koanf_utils.go @@ -1,6 +1,6 @@ // Copyright © 2025 Ping Identity Corporation -package testutils_viper +package testutils_koanf import ( "fmt" @@ -8,8 +8,9 @@ import ( "strings" "testing" + "github.com/knadh/koanf/parsers/yaml" + "github.com/knadh/koanf/providers/file" "github.com/pingidentity/pingcli/internal/configuration" - "github.com/pingidentity/pingcli/internal/configuration/options" "github.com/pingidentity/pingcli/internal/customtypes" "github.com/pingidentity/pingcli/internal/profiles" ) @@ -20,6 +21,7 @@ const ( var ( configFileContents string + configFilePath string defaultConfigFileContentsPattern string = `activeProfile: default default: description: "default description" @@ -30,22 +32,22 @@ default: serviceGroup: %s services: ["%s"] service: - pingone: + pingOne: regionCode: %s authentication: type: worker worker: - clientid: %s - clientsecret: %s - environmentid: %s - pingfederate: - adminapipath: /pf-admin-api/v1 + clientID: %s + clientSecret: %s + environmentID: %s + pingFederate: + adminAPIPath: /pf-admin-api/v1 authentication: - type: basicauth - basicauth: + type: basicAuth + basicAuth: username: Administrator password: 2FederateM0re - httpshost: https://localhost:9999 + httpsHost: https://localhost:9999 insecureTrustAllTLS: true xBypassExternalValidationHeader: true production: @@ -53,7 +55,7 @@ production: noColor: true outputFormat: text service: - pingfederate: + pingFederate: insecureTrustAllTLS: false xBypassExternalValidationHeader: false` ) @@ -65,49 +67,41 @@ func CreateConfigFile(t *testing.T) string { configFileContents = strings.Replace(getDefaultConfigFileContents(), outputDirectoryReplacement, t.TempDir(), 1) } - configFilepath := t.TempDir() + "/config.yaml" - if err := os.WriteFile(configFilepath, []byte(configFileContents), 0600); err != nil { + configFilePath := t.TempDir() + "/config.yaml" + if err := os.WriteFile(configFilePath, []byte(configFileContents), 0600); err != nil { t.Fatalf("Failed to create config file: %s", err) } - return configFilepath + return configFilePath } -func configureMainViper(t *testing.T) { +func configureMainKoanf(t *testing.T) { t.Helper() - // Create and write to a temporary config file - configFilepath := CreateConfigFile(t) - // Give main viper instance a file location to write to - mainViper := profiles.GetMainConfig().ViperInstance() - mainViper.SetConfigFile(configFilepath) - mainViper.SetConfigType("yaml") - if err := mainViper.ReadInConfig(); err != nil { - t.Fatal(err) - } - - activePName := profiles.GetMainConfig().ViperInstance().GetString(options.RootActiveProfileOption.ViperKey) + configFilePath = CreateConfigFile(t) + mainKoanf := profiles.GetKoanfConfig() + mainKoanf.SetKoanfConfigFile(configFilePath) - if err := profiles.GetMainConfig().ChangeActiveProfile(activePName); err != nil { - t.Fatal(err) + if err := mainKoanf.KoanfInstance().Load(file.Provider(configFilePath), yaml.Parser()); err != nil { + t.Fatalf("Failed to load configurationhere from file '%s': %v", configFilePath, err) } } -func InitVipers(t *testing.T) { +func InitKoanfs(t *testing.T) { t.Helper() configuration.InitAllOptions() - configFileContents = strings.Replace(getDefaultConfigFileContents(), outputDirectoryReplacement, t.TempDir(), 1) + configFileContents = strings.Replace(getDefaultConfigFileContents(), outputDirectoryReplacement, t.TempDir()+"/config.yaml", 1) - configureMainViper(t) + configureMainKoanf(t) } -func InitVipersCustomFile(t *testing.T, fileContents string) { +func InitKoanfsCustomFile(t *testing.T, fileContents string) { t.Helper() configFileContents = fileContents - configureMainViper(t) + configureMainKoanf(t) } func getDefaultConfigFileContents() string { diff --git a/main.go b/main.go index 023540b5..6df22652 100644 --- a/main.go +++ b/main.go @@ -34,7 +34,6 @@ func main() { } rootCmd := cmd.NewRootCommand(version, commit) - err := rootCmd.Execute() if err != nil { output.UserError(fmt.Sprintf("Failed to execute pingcli: %v", err), nil)