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)