Skip to content

Commit

Permalink
PDI-1926: Combine profile command into config (#134)
Browse files Browse the repository at this point in the history
* PDI-1926: Combine profile command into config

- Remove profile subcommand and move old profile commands into
the config subcommand group.
- Refactor profiles "Options" to their own package "configuration".
This allows the env vars and cobra flag parameters to be decoupled
from viper's configuration management, avoiding complicated and
confusing interactions with the module.
- As part of the configuration package, move all cobra param names,
param value variables, env var names, default values, pflag Flags,
and viper keys into the new configuration "Options" for easy reference
and development.
- Use profiles.GetOptionValue() to get configuration in a priority
order: cobra flag params > env vars > viper config values > default
values.
- Add additional custom types that implement pflag.Value interface
for configuration options
- Remove need for all viper configuration to be set in config file
for it to be valid.
  • Loading branch information
erikostien-pingidentity authored Sep 11, 2024
1 parent 9ab1367 commit a3560e2
Show file tree
Hide file tree
Showing 112 changed files with 5,031 additions and 4,298 deletions.
42 changes: 42 additions & 0 deletions cmd/config/add_profile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package config

import (
"os"

"github.com/pingidentity/pingctl/cmd/common"
config_internal "github.com/pingidentity/pingctl/internal/commands/config"
"github.com/pingidentity/pingctl/internal/configuration/options"
"github.com/pingidentity/pingctl/internal/logger"
"github.com/spf13/cobra"
)

func NewConfigAddProfileCommand() *cobra.Command {
cmd := &cobra.Command{
Args: common.ExactArgs(0),
DisableFlagsInUseLine: true, // We write our own flags in @Use attribute
Example: `pingctl config add-profile
pingctl config add-profile --name myprofile --description "My Profile desc"
pingctl config add-profile --set-active=true`,
Long: `Add a new configuration profile to pingctl.`,
RunE: configAddProfileRunE,
Short: "Add a new configuration profile to pingctl.",
Use: "add-profile [flags]",
}

cmd.Flags().AddFlag(options.ConfigAddProfileNameOption.Flag)
cmd.Flags().AddFlag(options.ConfigAddProfileDescriptionOption.Flag)
cmd.Flags().AddFlag(options.ConfigAddProfileSetActiveOption.Flag)

return cmd
}

func configAddProfileRunE(cmd *cobra.Command, args []string) error {
l := logger.Get()
l.Debug().Msgf("Config add-profile Subcommand Called.")

if err := config_internal.RunInternalConfigAddProfile(os.Stdin); err != nil {
return err
}

return nil
}
68 changes: 68 additions & 0 deletions cmd/config/add_profile_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package config_test

import (
"testing"

"github.com/pingidentity/pingctl/internal/testing/testutils"
"github.com/pingidentity/pingctl/internal/testing/testutils_cobra"
)

// Test config add profile command executes without issue
func TestConfigAddProfileCmd_Execute(t *testing.T) {
err := testutils_cobra.ExecutePingctl(t, "config", "add-profile",
"--name", "test-profile",
"--description", "test description",
"--set-active=false")
testutils.CheckExpectedError(t, err, nil)
}

// Test config add profile command fails when provided too many arguments
func TestConfigAddProfileCmd_TooManyArgs(t *testing.T) {
expectedErrorPattern := `^failed to execute 'pingctl config add-profile': command accepts 0 arg\(s\), received 1$`
err := testutils_cobra.ExecutePingctl(t, "config", "add-profile", "extra-arg")
testutils.CheckExpectedError(t, err, &expectedErrorPattern)
}

// Test config add profile command fails when provided an invalid flag
func TestConfigAddProfileCmd_InvalidFlag(t *testing.T) {
expectedErrorPattern := `^unknown flag: --invalid-flag$`
err := testutils_cobra.ExecutePingctl(t, "config", "add-profile", "--invalid-flag")
testutils.CheckExpectedError(t, err, &expectedErrorPattern)
}

// Test config add profile command fails when provided an invalid value for a flag
func TestConfigAddProfileCmd_InvalidFlagValue(t *testing.T) {
expectedErrorPattern := `^invalid argument ".*" for ".*" flag: strconv\.ParseBool: parsing ".*": invalid syntax$`
err := testutils_cobra.ExecutePingctl(t, "config", "add-profile", "--set-active", "invalid-value")
testutils.CheckExpectedError(t, err, &expectedErrorPattern)
}

// Test config add profile command fails when provided a duplicate profile name
func TestConfigAddProfileCmd_DuplicateProfileName(t *testing.T) {
expectedErrorPattern := `^failed to add profile: invalid profile name: '.*'\. profile already exists$`
err := testutils_cobra.ExecutePingctl(t, "config", "add-profile",
"--name", "default",
"--description", "test description",
"--set-active=false")
testutils.CheckExpectedError(t, err, &expectedErrorPattern)
}

// 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 contain only alphanumeric characters, underscores, and dashes$`
err := testutils_cobra.ExecutePingctl(t, "config", "add-profile",
"--name", "pname&*^*&^$&@!",
"--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$`
err := testutils_cobra.ExecutePingctl(t, "config", "add-profile",
"--name", "test-profile",
"--description", "test description",
"--set-active", "invalid-value")
testutils.CheckExpectedError(t, err, &expectedErrorPattern)
}
49 changes: 41 additions & 8 deletions cmd/config/config.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,54 @@
package config

import (
"github.com/pingidentity/pingctl/cmd/config/profile"
"os"

"github.com/pingidentity/pingctl/cmd/common"
config_internal "github.com/pingidentity/pingctl/internal/commands/config"
"github.com/pingidentity/pingctl/internal/configuration/options"
"github.com/pingidentity/pingctl/internal/logger"
"github.com/spf13/cobra"
)

func NewConfigCommand() *cobra.Command {
cmd := &cobra.Command{
Long: `Command to get, set, and unset pingctl configuration settings.`,
Short: "Command to get, set, and unset pingctl configuration settings.",
Use: "config",
Args: common.ExactArgs(0),
DisableFlagsInUseLine: true, // We write our own flags in @Use attribute
Example: `pingctl config
pingctl config --profile myprofile
pingctl config --name myprofile --description "My Profile"`,
Long: `Update an existing configuration profile's name and description. See subcommands for more profile configuration management options.`,
RunE: configRunE,
Short: "Update an existing configuration profile's name and description. See subcommands for more profile configuration management options.",
Use: "config [flags]",
}

cmd.AddCommand(NewConfigGetCommand())
cmd.AddCommand(NewConfigSetCommand())
cmd.AddCommand(NewConfigUnsetCommand())
cmd.AddCommand(profile.NewConfigProfileCommand())
// Add subcommands
cmd.AddCommand(
NewConfigAddProfileCommand(),
NewConfigDeleteProfileCommand(),
NewConfigViewProfileCommand(),
NewConfigListProfilesCommand(),
NewConfigSetActiveProfileCommand(),
NewConfigGetCommand(),
NewConfigSetCommand(),
NewConfigUnsetCommand(),
)

cmd.Flags().AddFlag(options.ConfigProfileOption.Flag)
cmd.Flags().AddFlag(options.ConfigNameOption.Flag)
cmd.Flags().AddFlag(options.ConfigDescriptionOption.Flag)

return cmd
}

func configRunE(cmd *cobra.Command, args []string) error {
l := logger.Get()
l.Debug().Msgf("Config Subcommand Called.")

if err := config_internal.RunInternalConfig(os.Stdin); err != nil {
return err
}

return nil
}
39 changes: 38 additions & 1 deletion cmd/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ import (

// Test Config Command Executes without issue
func TestConfigCmd_Execute(t *testing.T) {
err := testutils_cobra.ExecutePingctl(t, "config")
err := testutils_cobra.ExecutePingctl(t, "config",
"--profile", "production",
"--name", "myProfile",
"--description", "hello")

testutils.CheckExpectedError(t, err, nil)
}

Expand All @@ -28,3 +32,36 @@ func TestConfigCmd_HelpFlag(t *testing.T) {
err = testutils_cobra.ExecutePingctl(t, "config", "-h")
testutils.CheckExpectedError(t, err, nil)
}

// Test Config Command fails when provided a profile name that does not exist
func TestConfigCmd_ProfileDoesNotExist(t *testing.T) {
expectedErrorPattern := `^failed to update profile '.*' name to: .*\. invalid profile name: '.*' profile does not exist$`
err := testutils_cobra.ExecutePingctl(t, "config",
"--profile", "nonexistent",
"--name", "myProfile",
"--description", "hello")

testutils.CheckExpectedError(t, err, &expectedErrorPattern)
}

// Test Config Command fails when attempting to update the active profile
func TestConfigCmd_UpdateActiveProfile(t *testing.T) {
expectedErrorPattern := `^failed to update profile '.*' name to: .*\. '.*' is the active profile and cannot be deleted$`
err := testutils_cobra.ExecutePingctl(t, "config",
"--profile", "default",
"--name", "myProfile",
"--description", "hello")

testutils.CheckExpectedError(t, err, &expectedErrorPattern)
}

// 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 contain only alphanumeric characters, underscores, and dashes$`
err := testutils_cobra.ExecutePingctl(t, "config",
"--profile", "production",
"--name", "pname&*^*&^$&@!",
"--description", "hello")

testutils.CheckExpectedError(t, err, &expectedErrorPattern)
}
39 changes: 39 additions & 0 deletions cmd/config/delete_profile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package config

import (
"os"

"github.com/pingidentity/pingctl/cmd/common"
config_internal "github.com/pingidentity/pingctl/internal/commands/config"
"github.com/pingidentity/pingctl/internal/configuration/options"
"github.com/pingidentity/pingctl/internal/logger"
"github.com/spf13/cobra"
)

func NewConfigDeleteProfileCommand() *cobra.Command {
cmd := &cobra.Command{
Args: common.ExactArgs(0),
DisableFlagsInUseLine: true, // We write our own flags in @Use attribute
Example: `pingctl config delete-profile
pingctl config delete-profile --profile myprofile`,
Long: `Delete a configuration profile from pingctl.`,
RunE: configDeleteProfileRunE,
Short: "Delete a configuration profile from pingctl.",
Use: "delete-profile [flags]",
}

cmd.Flags().AddFlag(options.ConfigDeleteProfileOption.Flag)

return cmd
}

func configDeleteProfileRunE(cmd *cobra.Command, args []string) error {
l := logger.Get()
l.Debug().Msgf("Config delete-profile Subcommand Called.")

if err := config_internal.RunInternalConfigDeleteProfile(os.Stdin); err != nil {
return err
}

return nil
}
49 changes: 49 additions & 0 deletions cmd/config/delete_profile_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package config_test

import (
"testing"

"github.com/pingidentity/pingctl/internal/testing/testutils"
"github.com/pingidentity/pingctl/internal/testing/testutils_cobra"
)

// Test Config delete-profile Command Executes without issue
func TestConfigDeleteProfileCmd_Execute(t *testing.T) {
err := testutils_cobra.ExecutePingctl(t, "config", "delete-profile", "--profile", "production")
testutils.CheckExpectedError(t, err, nil)
}

// Test Config delete-profile Command fails when provided too many arguments
func TestConfigDeleteProfileCmd_TooManyArgs(t *testing.T) {
expectedErrorPattern := `^failed to execute 'pingctl config delete-profile': command accepts 0 arg\(s\), received 1$`
err := testutils_cobra.ExecutePingctl(t, "config", "delete-profile", "extra-arg")
testutils.CheckExpectedError(t, err, &expectedErrorPattern)
}

// Test Config delete-profile Command fails when provided an invalid flag
func TestConfigDeleteProfileCmd_InvalidFlag(t *testing.T) {
expectedErrorPattern := `^unknown flag: --invalid$`
err := testutils_cobra.ExecutePingctl(t, "config", "delete-profile", "--invalid")
testutils.CheckExpectedError(t, err, &expectedErrorPattern)
}

// Test Config delete-profile Command fails when provided an non-existent profile name
func TestConfigDeleteProfileCmd_NonExistentProfileName(t *testing.T) {
expectedErrorPattern := `^failed to delete profile: invalid profile name: '.*' profile does not exist$`
err := testutils_cobra.ExecutePingctl(t, "config", "delete-profile", "--profile", "nonexistent")
testutils.CheckExpectedError(t, err, &expectedErrorPattern)
}

// Test Config delete-profile Command fails when provided the active profile
func TestConfigDeleteProfileCmd_ActiveProfile(t *testing.T) {
expectedErrorPattern := `^failed to delete profile: '.*' is the active profile and cannot be deleted$`
err := testutils_cobra.ExecutePingctl(t, "config", "delete-profile", "--profile", "default")
testutils.CheckExpectedError(t, err, &expectedErrorPattern)
}

// Test Config delete-profile Command fails when provided an invalid profile name
func TestConfigDeleteProfileCmd_InvalidProfileName(t *testing.T) {
expectedErrorPattern := `^failed to delete profile: invalid profile name: '.*'\. name must contain only alphanumeric characters, underscores, and dashes$`
err := testutils_cobra.ExecutePingctl(t, "config", "delete-profile", "--profile", "pname&*^*&^$&@!")
testutils.CheckExpectedError(t, err, &expectedErrorPattern)
}
19 changes: 8 additions & 11 deletions cmd/config/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,34 @@ package config
import (
"github.com/pingidentity/pingctl/cmd/common"
config_internal "github.com/pingidentity/pingctl/internal/commands/config"
"github.com/pingidentity/pingctl/internal/configuration/options"
"github.com/pingidentity/pingctl/internal/logger"
"github.com/spf13/cobra"
)

func NewConfigGetCommand() *cobra.Command {
cmd := &cobra.Command{
Args: common.RangeArgs(0, 1),
Args: common.ExactArgs(1),
DisableFlagsInUseLine: true, // We write our own flags in @Use attribute
Example: `pingctl config get
pingctl config get pingone
pingctl config get pingctl.color
Example: `pingctl config get pingone
pingctl config get --profile myProfile pingctl.color
pingctl config get pingone.export.environmentID`,
Long: `Get pingctl configuration settings.`,
RunE: configGetRunE,
Short: "Get pingctl configuration settings.",
Use: "get [flags] [key]",
Use: "get [flags] key",
}

cmd.Flags().AddFlag(options.ConfigGetProfileOption.Flag)

return cmd
}

func configGetRunE(cmd *cobra.Command, args []string) error {
l := logger.Get()
l.Debug().Msgf("Config Get Subcommand Called.")

key := ""
if len(args) > 0 {
key = args[0]
}

if err := config_internal.RunInternalConfigGet(key); err != nil {
if err := config_internal.RunInternalConfigGet(args[0]); err != nil {
return err
}

Expand Down
Loading

0 comments on commit a3560e2

Please sign in to comment.