From 7e9e610e24914ca84eab32f62553e249788b112c Mon Sep 17 00:00:00 2001 From: wesleymccollam Date: Wed, 5 Mar 2025 16:17:49 -0500 Subject: [PATCH 01/11] add platform export --service-group flag for PingOne connectors, update tests for new idp --- cmd/platform/export.go | 9 +++ cmd/platform/export_test.go | 22 +++++- docs/tool-configuration/configuration-key.md | 1 - internal/commands/config/set_internal.go | 6 ++ internal/commands/platform/export_internal.go | 68 ++++++++++++++---- internal/configuration/options/options.go | 2 + internal/configuration/platform/export.go | 30 +++++++- ...ingone_identity_provider_attribute_test.go | 2 +- .../pingone_identity_provider_test.go | 2 +- .../sso/resources/pingone_population_test.go | 5 ++ internal/customtypes/export_service_group.go | 52 ++++++++++++++ .../customtypes/export_service_group_test.go | 71 +++++++++++++++++++ internal/customtypes/export_services.go | 22 ++++-- internal/profiles/validate.go | 25 +++++++ 14 files changed, 291 insertions(+), 26 deletions(-) create mode 100644 internal/customtypes/export_service_group.go create mode 100644 internal/customtypes/export_service_group_test.go diff --git a/cmd/platform/export.go b/cmd/platform/export.go index 8b58271d..f4923131 100644 --- a/cmd/platform/export.go +++ b/cmd/platform/export.go @@ -25,6 +25,9 @@ const ( Export configuration-as-code packages for PingOne (core platform and SSO services). pingcli platform export --services pingone-platform,pingone-sso + Export all configuration-as-code packages for PingOne. The --service-group flag can be used instead of listing all pingone-* packages in --services flag. + pingcli platform export --service-group pingone + Export configuration-as-code packages for PingOne (core platform), specifying the PingOne environment connection details. pingcli platform export --services pingone-platform --pingone-client-environment-id 3cf2... --pingone-worker-client-id a719... --pingone-worker-client-secret ey..... --pingone-region-code EU @@ -90,9 +93,15 @@ func exportRunE(cmd *cobra.Command, args []string) error { func initGeneralExportFlags(cmd *cobra.Command) { cmd.Flags().AddFlag(options.PlatformExportExportFormatOption.Flag) cmd.Flags().AddFlag(options.PlatformExportServiceOption.Flag) + cmd.Flags().AddFlag(options.PlatformExportServiceGroupOption.Flag) cmd.Flags().AddFlag(options.PlatformExportOutputDirectoryOption.Flag) cmd.Flags().AddFlag(options.PlatformExportOverwriteOption.Flag) cmd.Flags().AddFlag(options.PlatformExportPingOneEnvironmentIDOption.Flag) + + cmd.MarkFlagsMutuallyExclusive( + options.PlatformExportServiceOption.CobraParamName, + options.PlatformExportServiceGroupOption.CobraParamName, + ) } func initPingOneExportFlags(cmd *cobra.Command) { diff --git a/cmd/platform/export_test.go b/cmd/platform/export_test.go index 523d9b88..562572c6 100644 --- a/cmd/platform/export_test.go +++ b/cmd/platform/export_test.go @@ -46,8 +46,26 @@ func TestPlatformExportCmd_HelpFlag(t *testing.T) { testutils.CheckExpectedError(t, err, nil) } +// Test Platform Export Command --service-group, -g flag +func TestPlatformExportCmd_ServiceGroupFlag(t *testing.T) { + outputDir := t.TempDir() + + err := testutils_cobra.ExecutePingcli(t, "platform", "export", + "--output-directory", outputDir, + "--overwrite", + "--service-group", "pingone") + testutils.CheckExpectedError(t, err, nil) +} + +// Test Platform Export Command --service-group with non-supported service group +func TestPlatformExportCmd_ServiceGroupFlagInvalidServiceGroup(t *testing.T) { + expectedErrorPattern := `^invalid argument ".*" for "-g, --service-group" flag: unrecognized service group '.*'\. Must be one of: .*$` + err := testutils_cobra.ExecutePingcli(t, "platform", "export", "--service-group", "invalid") + testutils.CheckExpectedError(t, err, &expectedErrorPattern) +} + // Test Platform Export Command --services flag -func TestPlatformExportCmd_ServiceFlag(t *testing.T) { +func TestPlatformExportCmd_ServicesFlag(t *testing.T) { outputDir := t.TempDir() err := testutils_cobra.ExecutePingcli(t, "platform", "export", @@ -58,7 +76,7 @@ func TestPlatformExportCmd_ServiceFlag(t *testing.T) { } // Test Platform Export Command --services flag with invalid service -func TestPlatformExportCmd_ServiceFlagInvalidService(t *testing.T) { +func TestPlatformExportCmd_ServicesFlagInvalidService(t *testing.T) { expectedErrorPattern := `^invalid argument ".*" for "-s, --services" flag: failed to set ExportServices: Invalid service: .*\. Allowed services: .*$` err := testutils_cobra.ExecutePingcli(t, "platform", "export", "--services", "invalid") testutils.CheckExpectedError(t, err, &expectedErrorPattern) diff --git a/docs/tool-configuration/configuration-key.md b/docs/tool-configuration/configuration-key.md index 50011f67..bfd27cb9 100644 --- a/docs/tool-configuration/configuration-key.md +++ b/docs/tool-configuration/configuration-key.md @@ -42,7 +42,6 @@ The following parameters can be configured in Ping CLI's static configuration fi | export.outputDirectory | ENUM_STRING | --output-directory / -d | Specifies the output directory for export. Example: `$HOME/pingcli-export` | | export.overwrite | ENUM_BOOL | --overwrite / -o | Overwrite 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. | -| export.services | ENUM_EXPORT_SERVICES | --services / -s | Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services.

Options are: pingfederate, pingone-mfa, pingone-platform, pingone-protect, pingone-sso.

Example: `pingone-sso,pingone-mfa,pingfederate` | #### Custom Request Properties diff --git a/internal/commands/config/set_internal.go b/internal/commands/config/set_internal.go index 17ce9460..af94231b 100644 --- a/internal/commands/config/set_internal.go +++ b/internal/commands/config/set_internal.go @@ -121,6 +121,12 @@ func setValue(profileViper *viper.Viper, vKey, vValue string, valueType options. return fmt.Errorf("value for key '%s' must be a valid export format. Allowed [%s]: %v", vKey, strings.Join(customtypes.ExportFormatValidValues(), ", "), err) } profileViper.Set(vKey, exportFormat) + 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]: %v", vKey, strings.Join(customtypes.ExportServiceGroupValidValues(), ", "), err) + } + profileViper.Set(vKey, exportServiceGroup) case options.ENUM_EXPORT_SERVICES: exportServices := new(customtypes.ExportServices) if err = exportServices.Set(vValue); err != nil { diff --git a/internal/commands/platform/export_internal.go b/internal/commands/platform/export_internal.go index c047bfa7..fc9d9b6d 100644 --- a/internal/commands/platform/export_internal.go +++ b/internal/commands/platform/export_internal.go @@ -48,6 +48,10 @@ func RunInternalExport(ctx context.Context, commandVersion string) (err error) { if err != nil { return err } + exportServiceGroup, err := profiles.GetOptionValue(options.PlatformExportServiceGroupOption) + if err != nil { + return err + } exportServices, err := profiles.GetOptionValue(options.PlatformExportServiceOption) if err != nil { return err @@ -61,20 +65,38 @@ func RunInternalExport(ctx context.Context, commandVersion string) (err error) { return err } - es := new(customtypes.ExportServices) - if err = es.Set(exportServices); err != nil { - return err - } - - if es.ContainsPingOneService() { - if err = initPingOneServices(ctx, commandVersion); err != nil { + var exportableConnectors *[]connector.Exportable + if exportServices != "" { + es := new(customtypes.ExportServices) + if err = es.Set(exportServices); err != nil { return err } + + if es.ContainsPingOneService() { + if err = initPingOneServices(ctx, commandVersion); err != nil { + return err + } + } + + if es.ContainsPingFederateService() { + if err = initPingFederateServices(ctx, commandVersion); err != nil { + return err + } + } + + exportableConnectors = getExportableConnectors(es) } - if es.ContainsPingFederateService() { - if err = initPingFederateServices(ctx, commandVersion); err != nil { - return err + if exportServiceGroup != "" { + switch exportServiceGroup { + case customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE: + if err = initPingOneServices(ctx, commandVersion); err != nil { + return err + } + + exportableConnectors = getPingOneExportableConnectors() + default: + return fmt.Errorf("failed to get service group %s. Allowed service groups: %s", exportServiceGroup, strings.Join(customtypes.ExportServiceGroupValidValues(), ", ")) } } @@ -86,8 +108,6 @@ func RunInternalExport(ctx context.Context, commandVersion string) (err error) { return err } - exportableConnectors := getExportableConnectors(es) - if err := exportConnectors(exportableConnectors, exportFormat, outputDir, overwriteExportBool); err != nil { return err } @@ -453,6 +473,30 @@ func validatePingOneExportEnvID(ctx context.Context) (err error) { return nil } +func getPingOneExportableConnectors() (exportableConnectors *[]connector.Exportable) { + // Using the --service-group pingone parameter provided by user, build list of pingone connectors to export + pingOneConnectors := []connector.Exportable{} + + for _, service := range customtypes.ExportServicesPingOneValidValues() { + switch service { + case customtypes.ENUM_EXPORT_SERVICE_PINGONE_PLATFORM: + pingOneConnectors = append(pingOneConnectors, platform.PlatformConnector(pingoneContext, pingoneApiClient, &pingoneApiClientId, pingoneExportEnvID)) + case customtypes.ENUM_EXPORT_SERVICE_PINGONE_AUTHORIZE: + pingOneConnectors = append(pingOneConnectors, authorize.AuthorizeConnector(pingoneContext, pingoneApiClient, &pingoneApiClientId, pingoneExportEnvID)) + case customtypes.ENUM_EXPORT_SERVICE_PINGONE_SSO: + pingOneConnectors = append(pingOneConnectors, sso.SSOConnector(pingoneContext, pingoneApiClient, &pingoneApiClientId, pingoneExportEnvID)) + case customtypes.ENUM_EXPORT_SERVICE_PINGONE_MFA: + pingOneConnectors = append(pingOneConnectors, mfa.MFAConnector(pingoneContext, pingoneApiClient, &pingoneApiClientId, pingoneExportEnvID)) + case customtypes.ENUM_EXPORT_SERVICE_PINGONE_PROTECT: + pingOneConnectors = append(pingOneConnectors, protect.ProtectConnector(pingoneContext, pingoneApiClient, &pingoneApiClientId, pingoneExportEnvID)) + // default: + // This unrecognized service condition is handled by cobra with the custom type MultiService + } + } + + return &pingOneConnectors +} + func getExportableConnectors(exportServices *customtypes.ExportServices) (exportableConnectors *[]connector.Exportable) { // Using the --service parameter(s) provided by user, build list of connectors to export connectors := []connector.Exportable{} diff --git a/internal/configuration/options/options.go b/internal/configuration/options/options.go index 52e0781d..48deaf1f 100644 --- a/internal/configuration/options/options.go +++ b/internal/configuration/options/options.go @@ -14,6 +14,7 @@ const ( ENUM_BOOL OptionType = "ENUM_BOOL" ENUM_EXPORT_FORMAT OptionType = "ENUM_EXPORT_FORMAT" ENUM_INT OptionType = "ENUM_INT" + ENUM_EXPORT_SERVICE_GROUP OptionType = "ENUM_EXPORT_SERVICE_GROUP" ENUM_EXPORT_SERVICES OptionType = "ENUM_EXPORT_SERVICES" ENUM_OUTPUT_FORMAT OptionType = "ENUM_OUTPUT_FORMAT" ENUM_PINGFEDERATE_AUTH_TYPE OptionType = "ENUM_PINGFEDERATE_AUTH_TYPE" @@ -140,6 +141,7 @@ var ( var ( PlatformExportExportFormatOption Option PlatformExportServiceOption Option + PlatformExportServiceGroupOption Option PlatformExportOutputDirectoryOption Option PlatformExportOverwriteOption Option PlatformExportPingOneEnvironmentIDOption Option diff --git a/internal/configuration/platform/export.go b/internal/configuration/platform/export.go index cbe4bc63..483f7a87 100644 --- a/internal/configuration/platform/export.go +++ b/internal/configuration/platform/export.go @@ -12,6 +12,7 @@ import ( func InitPlatformExportOptions() { initFormatOption() initServicesOption() + initServiceGroupOption() initOutputDirectoryOption() initOverwriteOption() initPingOneEnvironmentIDOption() @@ -45,12 +46,37 @@ func initFormatOption() { } } +func initServiceGroupOption() { + cobraParamName := "service-group" + cobraValue := new(customtypes.ExportServiceGroup) + defaultValue := customtypes.ExportServiceGroup("") + envVar := "PINGCLI_EXPORT_SERVICE_GROUP" + options.PlatformExportServiceGroupOption = options.Option{ + CobraParamName: cobraParamName, + CobraParamValue: cobraValue, + DefaultValue: &defaultValue, + EnvVar: envVar, + Flag: &pflag.Flag{ + Name: cobraParamName, + Shorthand: "g", + Usage: fmt.Sprintf( + "Specifies the service group to export. "+ + "\nOptions are: %s.", + strings.Join(customtypes.ExportServiceGroupValidValues(), ", "), + ), + Value: cobraValue, + }, + Sensitive: false, + Type: options.ENUM_EXPORT_SERVICE_GROUP, + ViperKey: "export.serviceGroup", + } +} + func initServicesOption() { cobraParamName := "services" cobraValue := new(customtypes.ExportServices) - defaultValue := customtypes.ExportServices(customtypes.ExportServicesValidValues()) + defaultValue := customtypes.ExportServices([]string{}) envVar := "PINGCLI_EXPORT_SERVICES" - options.PlatformExportServiceOption = options.Option{ CobraParamName: cobraParamName, CobraParamValue: cobraValue, diff --git a/internal/connector/pingone/sso/resources/pingone_identity_provider_attribute_test.go b/internal/connector/pingone/sso/resources/pingone_identity_provider_attribute_test.go index a989b488..61aba367 100644 --- a/internal/connector/pingone/sso/resources/pingone_identity_provider_attribute_test.go +++ b/internal/connector/pingone/sso/resources/pingone_identity_provider_attribute_test.go @@ -18,7 +18,7 @@ func TestIdentityProviderAttributeExport(t *testing.T) { expectedImportBlocks := []connector.ImportBlock{ { ResourceType: "pingone_identity_provider_attribute", - ResourceName: "Test IdP_username", + ResourceName: "Default Idp Test_username", ResourceID: fmt.Sprintf("%s/a99df558-7090-4303-8f35-860ac660e371/51a036c6-41ed-44f7-bd1d-eacaa2a1feab", testutils.GetEnvironmentID()), }, } diff --git a/internal/connector/pingone/sso/resources/pingone_identity_provider_test.go b/internal/connector/pingone/sso/resources/pingone_identity_provider_test.go index 73b023f4..8cc66e23 100644 --- a/internal/connector/pingone/sso/resources/pingone_identity_provider_test.go +++ b/internal/connector/pingone/sso/resources/pingone_identity_provider_test.go @@ -18,7 +18,7 @@ func TestIdentityProviderExport(t *testing.T) { expectedImportBlocks := []connector.ImportBlock{ { ResourceType: "pingone_identity_provider", - ResourceName: "Test IdP", + ResourceName: "Default Idp Test", ResourceID: fmt.Sprintf("%s/a99df558-7090-4303-8f35-860ac660e371", testutils.GetEnvironmentID()), }, } diff --git a/internal/connector/pingone/sso/resources/pingone_population_test.go b/internal/connector/pingone/sso/resources/pingone_population_test.go index 384f2010..2c445d51 100644 --- a/internal/connector/pingone/sso/resources/pingone_population_test.go +++ b/internal/connector/pingone/sso/resources/pingone_population_test.go @@ -26,6 +26,11 @@ func TestPopulationExport(t *testing.T) { ResourceName: "LDAP Gateway Population", ResourceID: fmt.Sprintf("%s/374fdb3c-4e94-4547-838a-0c200b9a7c70", testutils.GetEnvironmentID()), }, + { + ResourceType: "pingone_population", + ResourceName: "Test Default Idp Population", + ResourceID: fmt.Sprintf("%s/2814912d-4a0f-4104-a779-80c13b2a6dcd", testutils.GetEnvironmentID()), + }, } testutils.ValidateImportBlocks(t, resource, &expectedImportBlocks) diff --git a/internal/customtypes/export_service_group.go b/internal/customtypes/export_service_group.go new file mode 100644 index 00000000..7f24985a --- /dev/null +++ b/internal/customtypes/export_service_group.go @@ -0,0 +1,52 @@ +package customtypes + +import ( + "fmt" + "slices" + "strings" + + "github.com/spf13/pflag" +) + +const ( + ENUM_EXPORT_SERVICE_GROUP_PINGONE string = "pingone" +) + +type ExportServiceGroup string + +// Verify that the custom type satisfies the pflag.Value interface +var _ pflag.Value = (*ExportServiceGroup)(nil) + +func (esg *ExportServiceGroup) Set(serviceGroup string) error { + if esg == nil { + return fmt.Errorf("failed to set Service Group value: %s. Service Group is nil", serviceGroup) + } + + switch { + case strings.EqualFold(serviceGroup, ENUM_EXPORT_SERVICE_GROUP_PINGONE): + *esg = ExportServiceGroup(ENUM_EXPORT_SERVICE_GROUP_PINGONE) + case strings.EqualFold(serviceGroup, ""): + *esg = ExportServiceGroup("") + default: + return fmt.Errorf("unrecognized service group '%s'. Must be one of: %s", serviceGroup, strings.Join(ExportServiceGroupValidValues(), ", ")) + } + return nil +} + +func (esg ExportServiceGroup) Type() string { + return "string" +} + +func (esg ExportServiceGroup) String() string { + return string(esg) +} + +func ExportServiceGroupValidValues() []string { + validServiceGroups := []string{ + ENUM_EXPORT_SERVICE_GROUP_PINGONE, + } + + slices.Sort(validServiceGroups) + + return validServiceGroups +} diff --git a/internal/customtypes/export_service_group_test.go b/internal/customtypes/export_service_group_test.go new file mode 100644 index 00000000..a1819ea2 --- /dev/null +++ b/internal/customtypes/export_service_group_test.go @@ -0,0 +1,71 @@ +package customtypes_test + +import ( + "testing" + + "github.com/pingidentity/pingcli/internal/customtypes" + "github.com/pingidentity/pingcli/internal/testing/testutils" +) + +// Test ExportServiceGroup Set function +func Test_ExportServiceGroup_Set(t *testing.T) { + // Create a new ExportServiceGroup + exportServiceGroup := new(customtypes.ExportServiceGroup) + + err := exportServiceGroup.Set(customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE) + if err != nil { + t.Errorf("Set returned error: %v", err) + } +} + +// Test ExportServiceGroup Set function fails with invalid value +func Test_ExportServiceGroup_Set_InvalidValue(t *testing.T) { + // Create a new ExportServiceGroup + exportServiceGroup := new(customtypes.ExportServiceGroup) + + invalidValue := "invalid" + + expectedErrorPattern := `^unrecognized service group '.*'. Must be one of: .*$` + err := exportServiceGroup.Set(invalidValue) + testutils.CheckExpectedError(t, err, &expectedErrorPattern) +} + +// Test ExportServiceGroup Set function fails with nil +func Test_ExportServiceGroup_Set_Nil(t *testing.T) { + var exportServiceGroup *customtypes.ExportServiceGroup + + val := customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE + + expectedErrorPattern := `^failed to set Service Group value: .* Service Group is nil$` + err := exportServiceGroup.Set(val) + testutils.CheckExpectedError(t, err, &expectedErrorPattern) +} + +// Test ExportServiceGroup String function +func Test_ExportServiceGroup_String(t *testing.T) { + exportServiceGroup := customtypes.ExportServiceGroup(customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE) + + expected := customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE + actual := exportServiceGroup.String() + if actual != expected { + t.Errorf("String returned: %s, expected: %s", actual, expected) + } +} + +// Test ExportServiceGroupValidValues +func Test_ExportServiceGroupValidValues(t *testing.T) { + serviceGroupEnum := customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE + + serviceGroupValidValues := customtypes.ExportServiceGroupValidValues() + if serviceGroupValidValues[0] != serviceGroupEnum { + t.Errorf("ExportServiceGroupValidValues returned: %v, expected: %v", serviceGroupValidValues, serviceGroupEnum) + } +} + +// Test ExportServicePingOneValidValues +func Test_ExportServicesPingOneValidValues(t *testing.T) { + pingOneServiceGroupValidValues := customtypes.ExportServicesPingOneValidValues() + if len(pingOneServiceGroupValidValues) != 5 { + t.Errorf("ExportServicesPingOneValidValues returned: %v, expected: %v", len(pingOneServiceGroupValidValues), 5) + } +} diff --git a/internal/customtypes/export_services.go b/internal/customtypes/export_services.go index 41be1159..db04ddc2 100644 --- a/internal/customtypes/export_services.go +++ b/internal/customtypes/export_services.go @@ -63,13 +63,7 @@ func (es ExportServices) ContainsPingOneService() bool { return false } - pingoneServices := []string{ - ENUM_EXPORT_SERVICE_PINGONE_PLATFORM, - ENUM_EXPORT_SERVICE_PINGONE_AUTHORIZE, - ENUM_EXPORT_SERVICE_PINGONE_SSO, - ENUM_EXPORT_SERVICE_PINGONE_MFA, - ENUM_EXPORT_SERVICE_PINGONE_PROTECT, - } + pingoneServices := ExportServicesPingOneValidValues() for _, service := range es { if slices.ContainsFunc(pingoneServices, func(s string) bool { @@ -112,3 +106,17 @@ func ExportServicesValidValues() []string { return allServices } + +func ExportServicesPingOneValidValues() []string { + pingOneServices := []string{ + ENUM_EXPORT_SERVICE_PINGONE_PLATFORM, + ENUM_EXPORT_SERVICE_PINGONE_AUTHORIZE, + ENUM_EXPORT_SERVICE_PINGONE_SSO, + ENUM_EXPORT_SERVICE_PINGONE_MFA, + ENUM_EXPORT_SERVICE_PINGONE_PROTECT, + } + + slices.Sort(pingOneServices) + + return pingOneServices +} diff --git a/internal/profiles/validate.go b/internal/profiles/validate.go index f9a7e40d..49490dc0 100644 --- a/internal/profiles/validate.go +++ b/internal/profiles/validate.go @@ -183,6 +183,31 @@ func validateProfileValues(pName string, profileViper *viper.Viper) (err error) default: return fmt.Errorf("profile '%s': variable type %T for key '%s' is not a string slice value", pName, typedValue, key) } + case options.ENUM_EXPORT_SERVICE_GROUP: + switch typedValue := vValue.(type) { + case *customtypes.ExportServiceGroup: + continue + case string: + esg := new(customtypes.ExportServiceGroup) + if err = esg.Set(typedValue); err != nil { + return fmt.Errorf("profile '%s': variable type '%T' for key '%s' is not a export service group value: %v", pName, typedValue, key, err) + } + case []any: + esg := new(customtypes.ExportServiceGroup) + for _, v := range typedValue { + switch innerTypedValue := v.(type) { + case string: + if err = esg.Set(innerTypedValue); err != nil { + return fmt.Errorf("profile '%s': variable type '%T' for key '%s' is not a export service group value: %v", pName, typedValue, key, err) + } + default: + return fmt.Errorf("profile '%s': variable type %T for key '%s' is not a export service group value", pName, typedValue, key) + } + + } + default: + return fmt.Errorf("profile '%s': variable type %T for key '%s' is not a export service group value", pName, typedValue, key) + } case options.ENUM_EXPORT_SERVICES: switch typedValue := vValue.(type) { case *customtypes.ExportServices: From e52049f859c6e1d12c09f90232399de2c4d1621c Mon Sep 17 00:00:00 2001 From: wesleymccollam Date: Wed, 5 Mar 2025 16:28:58 -0500 Subject: [PATCH 02/11] set export service group properly --- internal/commands/platform/export_internal.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/commands/platform/export_internal.go b/internal/commands/platform/export_internal.go index fc9d9b6d..c35a89d9 100644 --- a/internal/commands/platform/export_internal.go +++ b/internal/commands/platform/export_internal.go @@ -88,7 +88,12 @@ func RunInternalExport(ctx context.Context, commandVersion string) (err error) { } if exportServiceGroup != "" { - switch exportServiceGroup { + esg := new(customtypes.ExportServiceGroup) + if err = esg.Set(exportServiceGroup); err != nil { + return err + } + + switch esg.String() { case customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE: if err = initPingOneServices(ctx, commandVersion); err != nil { return err From 48daedab1582c3b1d3cb1199b2a18367c5557116 Mon Sep 17 00:00:00 2001 From: wesleymccollam Date: Wed, 5 Mar 2025 17:14:49 -0500 Subject: [PATCH 03/11] update default viper config for test, correct doc --- docs/tool-configuration/configuration-key.md | 2 ++ internal/testing/testutils_viper/viper_utils.go | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/docs/tool-configuration/configuration-key.md b/docs/tool-configuration/configuration-key.md index bfd27cb9..a9bfd442 100644 --- a/docs/tool-configuration/configuration-key.md +++ b/docs/tool-configuration/configuration-key.md @@ -42,6 +42,8 @@ The following parameters can be configured in Ping CLI's static configuration fi | export.outputDirectory | ENUM_STRING | --output-directory / -d | Specifies the output directory for export. Example: `$HOME/pingcli-export` | | export.overwrite | ENUM_BOOL | --overwrite / -o | Overwrite 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. | +| export.serviceGroup | ENUM_EXPORT_SERVICE_GROUP | --service-group / -g | Specifies the service group to export.

Options are: pingone.

Example: `pingone` | +| export.services | ENUM_EXPORT_SERVICES | --services / -s | Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services.

Options are: pingfederate, pingone-mfa, pingone-platform, pingone-protect, pingone-sso.

Example: `pingone-sso,pingone-mfa,pingfederate` | #### Custom Request Properties diff --git a/internal/testing/testutils_viper/viper_utils.go b/internal/testing/testutils_viper/viper_utils.go index 7d28fcd8..6bc4e693 100644 --- a/internal/testing/testutils_viper/viper_utils.go +++ b/internal/testing/testutils_viper/viper_utils.go @@ -8,6 +8,7 @@ import ( "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" ) @@ -24,6 +25,8 @@ default: outputFormat: text export: outputDirectory: %s + serviceGroup: %s + services: ["%s"] service: pingone: regionCode: %s @@ -109,6 +112,8 @@ func InitVipersCustomFile(t *testing.T, fileContents string) { func getDefaultConfigFileContents() string { return fmt.Sprintf(defaultConfigFileContentsPattern, outputDirectoryReplacement, + customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE, + customtypes.ENUM_EXPORT_SERVICE_PINGFEDERATE, os.Getenv(options.PingOneRegionCodeOption.EnvVar), os.Getenv(options.PingOneAuthenticationWorkerClientIDOption.EnvVar), os.Getenv(options.PingOneAuthenticationWorkerClientSecretOption.EnvVar), From 0f7e27dbae9b5e62ed0a96da0a12860d8caafe5b Mon Sep 17 00:00:00 2001 From: wesleymccollam Date: Wed, 5 Mar 2025 17:31:28 -0500 Subject: [PATCH 04/11] add PlatformExportServiceGroupOption to options, update list keys test --- cmd/config/list_keys_test.go | 1 + internal/configuration/options/options.go | 1 + 2 files changed, 2 insertions(+) diff --git a/cmd/config/list_keys_test.go b/cmd/config/list_keys_test.go index be4bf0a8..39a39622 100644 --- a/cmd/config/list_keys_test.go +++ b/cmd/config/list_keys_test.go @@ -52,6 +52,7 @@ func Example_listKeysValue() { // - export.outputDirectory // - export.overwrite // - export.pingone.environmentID + // - export.serviceGroup // - export.services // - noColor // - outputFormat diff --git a/internal/configuration/options/options.go b/internal/configuration/options/options.go index 48deaf1f..34740e46 100644 --- a/internal/configuration/options/options.go +++ b/internal/configuration/options/options.go @@ -47,6 +47,7 @@ func Options() []Option { PingOneRegionCodeOption, PlatformExportExportFormatOption, + PlatformExportServiceGroupOption, PlatformExportServiceOption, PlatformExportOutputDirectoryOption, PlatformExportOverwriteOption, From 630c31f10d0185bfc2c66d4f63f7e66be45c4e47 Mon Sep 17 00:00:00 2001 From: wesleymccollam Date: Wed, 19 Mar 2025 18:05:11 -0400 Subject: [PATCH 05/11] fix services multiple same value bug, allow services and service group flags simultaneously, remove ExportServiceGroup customtype, edit and move service group tests --- cmd/platform/export.go | 5 -- internal/commands/config/set_internal.go | 2 +- internal/commands/platform/export_internal.go | 71 +++++-------------- internal/configuration/platform/export.go | 4 +- internal/customtypes/export_service_group.go | 52 -------------- .../customtypes/export_service_group_test.go | 71 ------------------- internal/customtypes/export_services.go | 54 ++++++++++++-- internal/customtypes/export_services_test.go | 52 ++++++++++++++ internal/profiles/validate.go | 6 +- 9 files changed, 126 insertions(+), 191 deletions(-) delete mode 100644 internal/customtypes/export_service_group.go delete mode 100644 internal/customtypes/export_service_group_test.go diff --git a/cmd/platform/export.go b/cmd/platform/export.go index f4923131..861d7558 100644 --- a/cmd/platform/export.go +++ b/cmd/platform/export.go @@ -97,11 +97,6 @@ func initGeneralExportFlags(cmd *cobra.Command) { cmd.Flags().AddFlag(options.PlatformExportOutputDirectoryOption.Flag) cmd.Flags().AddFlag(options.PlatformExportOverwriteOption.Flag) cmd.Flags().AddFlag(options.PlatformExportPingOneEnvironmentIDOption.Flag) - - cmd.MarkFlagsMutuallyExclusive( - options.PlatformExportServiceOption.CobraParamName, - options.PlatformExportServiceGroupOption.CobraParamName, - ) } func initPingOneExportFlags(cmd *cobra.Command) { diff --git a/internal/commands/config/set_internal.go b/internal/commands/config/set_internal.go index af94231b..3a872b58 100644 --- a/internal/commands/config/set_internal.go +++ b/internal/commands/config/set_internal.go @@ -122,7 +122,7 @@ func setValue(profileViper *viper.Viper, vKey, vValue string, valueType options. } profileViper.Set(vKey, exportFormat) case options.ENUM_EXPORT_SERVICE_GROUP: - exportServiceGroup := new(customtypes.ExportServiceGroup) + exportServiceGroup := new(customtypes.String) if err = exportServiceGroup.Set(vValue); err != nil { return fmt.Errorf("value for key '%s' must be valid export service group. Allowed [%s]: %v", vKey, strings.Join(customtypes.ExportServiceGroupValidValues(), ", "), err) } diff --git a/internal/commands/platform/export_internal.go b/internal/commands/platform/export_internal.go index c35a89d9..ce3bf1f9 100644 --- a/internal/commands/platform/export_internal.go +++ b/internal/commands/platform/export_internal.go @@ -66,45 +66,34 @@ func RunInternalExport(ctx context.Context, commandVersion string) (err error) { } var exportableConnectors *[]connector.Exportable - if exportServices != "" { - es := new(customtypes.ExportServices) - if err = es.Set(exportServices); err != nil { - return err - } - - if es.ContainsPingOneService() { - if err = initPingOneServices(ctx, commandVersion); err != nil { - return err - } - } + es := new(customtypes.ExportServices) + if err = es.Set(exportServices); err != nil { + return err + } - if es.ContainsPingFederateService() { - if err = initPingFederateServices(ctx, commandVersion); err != nil { - return err - } - } + es2 := new(customtypes.ExportServices) + if err = es2.SetServiceGroup(exportServiceGroup); err != nil { + return err + } - exportableConnectors = getExportableConnectors(es) + if err = es.Merge(*es2); err != nil { + return err } - if exportServiceGroup != "" { - esg := new(customtypes.ExportServiceGroup) - if err = esg.Set(exportServiceGroup); err != nil { + if es.ContainsPingOneService() { + if err = initPingOneServices(ctx, commandVersion); err != nil { return err } + } - switch esg.String() { - case customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE: - if err = initPingOneServices(ctx, commandVersion); err != nil { - return err - } - - exportableConnectors = getPingOneExportableConnectors() - default: - return fmt.Errorf("failed to get service group %s. Allowed service groups: %s", exportServiceGroup, strings.Join(customtypes.ExportServiceGroupValidValues(), ", ")) + if es.ContainsPingFederateService() { + if err = initPingFederateServices(ctx, commandVersion); err != nil { + return err } } + exportableConnectors = getExportableConnectors(es) + overwriteExportBool, err := strconv.ParseBool(overwriteExport) if err != nil { return err @@ -478,30 +467,6 @@ func validatePingOneExportEnvID(ctx context.Context) (err error) { return nil } -func getPingOneExportableConnectors() (exportableConnectors *[]connector.Exportable) { - // Using the --service-group pingone parameter provided by user, build list of pingone connectors to export - pingOneConnectors := []connector.Exportable{} - - for _, service := range customtypes.ExportServicesPingOneValidValues() { - switch service { - case customtypes.ENUM_EXPORT_SERVICE_PINGONE_PLATFORM: - pingOneConnectors = append(pingOneConnectors, platform.PlatformConnector(pingoneContext, pingoneApiClient, &pingoneApiClientId, pingoneExportEnvID)) - case customtypes.ENUM_EXPORT_SERVICE_PINGONE_AUTHORIZE: - pingOneConnectors = append(pingOneConnectors, authorize.AuthorizeConnector(pingoneContext, pingoneApiClient, &pingoneApiClientId, pingoneExportEnvID)) - case customtypes.ENUM_EXPORT_SERVICE_PINGONE_SSO: - pingOneConnectors = append(pingOneConnectors, sso.SSOConnector(pingoneContext, pingoneApiClient, &pingoneApiClientId, pingoneExportEnvID)) - case customtypes.ENUM_EXPORT_SERVICE_PINGONE_MFA: - pingOneConnectors = append(pingOneConnectors, mfa.MFAConnector(pingoneContext, pingoneApiClient, &pingoneApiClientId, pingoneExportEnvID)) - case customtypes.ENUM_EXPORT_SERVICE_PINGONE_PROTECT: - pingOneConnectors = append(pingOneConnectors, protect.ProtectConnector(pingoneContext, pingoneApiClient, &pingoneApiClientId, pingoneExportEnvID)) - // default: - // This unrecognized service condition is handled by cobra with the custom type MultiService - } - } - - return &pingOneConnectors -} - func getExportableConnectors(exportServices *customtypes.ExportServices) (exportableConnectors *[]connector.Exportable) { // Using the --service parameter(s) provided by user, build list of connectors to export connectors := []connector.Exportable{} diff --git a/internal/configuration/platform/export.go b/internal/configuration/platform/export.go index 483f7a87..3c8a19af 100644 --- a/internal/configuration/platform/export.go +++ b/internal/configuration/platform/export.go @@ -48,8 +48,8 @@ func initFormatOption() { func initServiceGroupOption() { cobraParamName := "service-group" - cobraValue := new(customtypes.ExportServiceGroup) - defaultValue := customtypes.ExportServiceGroup("") + cobraValue := new(customtypes.String) + defaultValue := customtypes.String("") envVar := "PINGCLI_EXPORT_SERVICE_GROUP" options.PlatformExportServiceGroupOption = options.Option{ CobraParamName: cobraParamName, diff --git a/internal/customtypes/export_service_group.go b/internal/customtypes/export_service_group.go deleted file mode 100644 index 7f24985a..00000000 --- a/internal/customtypes/export_service_group.go +++ /dev/null @@ -1,52 +0,0 @@ -package customtypes - -import ( - "fmt" - "slices" - "strings" - - "github.com/spf13/pflag" -) - -const ( - ENUM_EXPORT_SERVICE_GROUP_PINGONE string = "pingone" -) - -type ExportServiceGroup string - -// Verify that the custom type satisfies the pflag.Value interface -var _ pflag.Value = (*ExportServiceGroup)(nil) - -func (esg *ExportServiceGroup) Set(serviceGroup string) error { - if esg == nil { - return fmt.Errorf("failed to set Service Group value: %s. Service Group is nil", serviceGroup) - } - - switch { - case strings.EqualFold(serviceGroup, ENUM_EXPORT_SERVICE_GROUP_PINGONE): - *esg = ExportServiceGroup(ENUM_EXPORT_SERVICE_GROUP_PINGONE) - case strings.EqualFold(serviceGroup, ""): - *esg = ExportServiceGroup("") - default: - return fmt.Errorf("unrecognized service group '%s'. Must be one of: %s", serviceGroup, strings.Join(ExportServiceGroupValidValues(), ", ")) - } - return nil -} - -func (esg ExportServiceGroup) Type() string { - return "string" -} - -func (esg ExportServiceGroup) String() string { - return string(esg) -} - -func ExportServiceGroupValidValues() []string { - validServiceGroups := []string{ - ENUM_EXPORT_SERVICE_GROUP_PINGONE, - } - - slices.Sort(validServiceGroups) - - return validServiceGroups -} diff --git a/internal/customtypes/export_service_group_test.go b/internal/customtypes/export_service_group_test.go deleted file mode 100644 index a1819ea2..00000000 --- a/internal/customtypes/export_service_group_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package customtypes_test - -import ( - "testing" - - "github.com/pingidentity/pingcli/internal/customtypes" - "github.com/pingidentity/pingcli/internal/testing/testutils" -) - -// Test ExportServiceGroup Set function -func Test_ExportServiceGroup_Set(t *testing.T) { - // Create a new ExportServiceGroup - exportServiceGroup := new(customtypes.ExportServiceGroup) - - err := exportServiceGroup.Set(customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE) - if err != nil { - t.Errorf("Set returned error: %v", err) - } -} - -// Test ExportServiceGroup Set function fails with invalid value -func Test_ExportServiceGroup_Set_InvalidValue(t *testing.T) { - // Create a new ExportServiceGroup - exportServiceGroup := new(customtypes.ExportServiceGroup) - - invalidValue := "invalid" - - expectedErrorPattern := `^unrecognized service group '.*'. Must be one of: .*$` - err := exportServiceGroup.Set(invalidValue) - testutils.CheckExpectedError(t, err, &expectedErrorPattern) -} - -// Test ExportServiceGroup Set function fails with nil -func Test_ExportServiceGroup_Set_Nil(t *testing.T) { - var exportServiceGroup *customtypes.ExportServiceGroup - - val := customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE - - expectedErrorPattern := `^failed to set Service Group value: .* Service Group is nil$` - err := exportServiceGroup.Set(val) - testutils.CheckExpectedError(t, err, &expectedErrorPattern) -} - -// Test ExportServiceGroup String function -func Test_ExportServiceGroup_String(t *testing.T) { - exportServiceGroup := customtypes.ExportServiceGroup(customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE) - - expected := customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE - actual := exportServiceGroup.String() - if actual != expected { - t.Errorf("String returned: %s, expected: %s", actual, expected) - } -} - -// Test ExportServiceGroupValidValues -func Test_ExportServiceGroupValidValues(t *testing.T) { - serviceGroupEnum := customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE - - serviceGroupValidValues := customtypes.ExportServiceGroupValidValues() - if serviceGroupValidValues[0] != serviceGroupEnum { - t.Errorf("ExportServiceGroupValidValues returned: %v, expected: %v", serviceGroupValidValues, serviceGroupEnum) - } -} - -// Test ExportServicePingOneValidValues -func Test_ExportServicesPingOneValidValues(t *testing.T) { - pingOneServiceGroupValidValues := customtypes.ExportServicesPingOneValidValues() - if len(pingOneServiceGroupValidValues) != 5 { - t.Errorf("ExportServicesPingOneValidValues returned: %v, expected: %v", len(pingOneServiceGroupValidValues), 5) - } -} diff --git a/internal/customtypes/export_services.go b/internal/customtypes/export_services.go index db04ddc2..0e309d6c 100644 --- a/internal/customtypes/export_services.go +++ b/internal/customtypes/export_services.go @@ -15,6 +15,7 @@ const ( ENUM_EXPORT_SERVICE_PINGONE_MFA string = "pingone-mfa" ENUM_EXPORT_SERVICE_PINGONE_PROTECT string = "pingone-protect" ENUM_EXPORT_SERVICE_PINGFEDERATE string = "pingfederate" + ENUM_EXPORT_SERVICE_GROUP_PINGONE string = "pingone" ) type ExportServices []string @@ -39,11 +40,14 @@ func (es *ExportServices) Set(services string) error { validServices := ExportServicesValidValues() serviceList := strings.Split(services, ",") + returnServiceList := []string{} - for i, service := range serviceList { + for _, service := range serviceList { if !slices.ContainsFunc(validServices, func(validService string) bool { if strings.EqualFold(validService, service) { - serviceList[i] = validService + if !slices.Contains(returnServiceList, validService) { + returnServiceList = append(returnServiceList, validService) + } return true } return false @@ -52,9 +56,37 @@ func (es *ExportServices) Set(services string) error { } } - slices.Sort(serviceList) + slices.Sort(returnServiceList) - *es = ExportServices(serviceList) + *es = ExportServices(returnServiceList) + return nil +} + +func (es *ExportServices) SetServiceGroup(serviceGroup string) error { + if es == nil { + return fmt.Errorf("failed to set ExportServices group value: %s. ExportServices is nil", serviceGroup) + } + + if serviceGroup == "" || serviceGroup == "[]" { + return nil + } + + validServiceGroups := ExportServiceGroupValidValues() + + if !slices.ContainsFunc(validServiceGroups, func(validServiceGroup string) bool { + if strings.EqualFold(validServiceGroup, serviceGroup) { + switch { + case strings.EqualFold(ENUM_EXPORT_SERVICE_GROUP_PINGONE, serviceGroup): + *es = append(*es, ExportServicesPingOneValidValues()...) + } + return true + } + return false + }) { + return fmt.Errorf("failed to set ExportServices: Invalid service group: %s. Allowed service group(s): %s", serviceGroup, strings.Join(validServiceGroups, ", ")) + } + + slices.Sort(*es) return nil } @@ -120,3 +152,17 @@ func ExportServicesPingOneValidValues() []string { return pingOneServices } + +func ExportServiceGroupValidValues() []string { + validServiceGroups := []string{ + ENUM_EXPORT_SERVICE_GROUP_PINGONE, + } + + slices.Sort(validServiceGroups) + + return validServiceGroups +} + +func (es *ExportServices) Merge(es2 ExportServices) error { + return es.Set(strings.Join(append(es.GetServices(), es2.GetServices()...), ",")) +} diff --git a/internal/customtypes/export_services_test.go b/internal/customtypes/export_services_test.go index 6d780f57..a8a7d6b9 100644 --- a/internal/customtypes/export_services_test.go +++ b/internal/customtypes/export_services_test.go @@ -93,3 +93,55 @@ func Test_ExportServices_String(t *testing.T) { t.Errorf("String returned: %s, expected: %s", actual, expected) } } + +// Test ExportServiceGroup Set function +func Test_ExportServiceGroup_Set(t *testing.T) { + // Create a new ExportServiceGroup + exportServiceGroup := new(customtypes.ExportServices) + + err := exportServiceGroup.SetServiceGroup(customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE) + if err != nil { + t.Errorf("Set returned error: %v", err) + } +} + +// Test ExportServiceGroup Set function fails with invalid value +func Test_ExportServiceGroup_Set_InvalidValue(t *testing.T) { + // Create a new ExportServiceGroup + exportServiceGroup := new(customtypes.ExportServices) + + invalidValue := "invalid" + + expectedErrorPattern := `^failed to set ExportServices: Invalid service group: .*\. Allowed service group\(s\): .*$` + err := exportServiceGroup.SetServiceGroup(invalidValue) + testutils.CheckExpectedError(t, err, &expectedErrorPattern) +} + +// Test ExportServiceGroup Set function fails with nil +func Test_ExportServiceGroup_Set_Nil(t *testing.T) { + var exportServiceGroup *customtypes.ExportServices + + val := customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE + + expectedErrorPattern := `^failed to set ExportServices group value: .* ExportServices is nil$` + err := exportServiceGroup.SetServiceGroup(val) + testutils.CheckExpectedError(t, err, &expectedErrorPattern) +} + +// Test ExportServiceGroupValidValues +func Test_ExportServiceGroupValidValues(t *testing.T) { + serviceGroupEnum := customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE + + serviceGroupValidValues := customtypes.ExportServiceGroupValidValues() + if serviceGroupValidValues[0] != serviceGroupEnum { + t.Errorf("ExportServiceGroupValidValues returned: %v, expected: %v", serviceGroupValidValues, serviceGroupEnum) + } +} + +// Test ExportServicePingOneValidValues +func Test_ExportServicesPingOneValidValues(t *testing.T) { + pingOneServiceGroupValidValues := customtypes.ExportServicesPingOneValidValues() + if len(pingOneServiceGroupValidValues) != 5 { + t.Errorf("ExportServicesPingOneValidValues returned: %v, expected: %v", len(pingOneServiceGroupValidValues), 5) + } +} diff --git a/internal/profiles/validate.go b/internal/profiles/validate.go index 49490dc0..204d9a77 100644 --- a/internal/profiles/validate.go +++ b/internal/profiles/validate.go @@ -185,15 +185,15 @@ func validateProfileValues(pName string, profileViper *viper.Viper) (err error) } case options.ENUM_EXPORT_SERVICE_GROUP: switch typedValue := vValue.(type) { - case *customtypes.ExportServiceGroup: + case *customtypes.String: continue case string: - esg := new(customtypes.ExportServiceGroup) + esg := new(customtypes.String) if err = esg.Set(typedValue); err != nil { return fmt.Errorf("profile '%s': variable type '%T' for key '%s' is not a export service group value: %v", pName, typedValue, key, err) } case []any: - esg := new(customtypes.ExportServiceGroup) + esg := new(customtypes.String) for _, v := range typedValue { switch innerTypedValue := v.(type) { case string: From 61c2105615c84f0e79ee6b4f249dbfe2b4e038e5 Mon Sep 17 00:00:00 2001 From: wesleymccollam Date: Thu, 20 Mar 2025 10:39:54 -0400 Subject: [PATCH 06/11] reinsert export service group, set value properly, ensure no duplicates in merge method, edit tests --- cmd/platform/export_test.go | 6 +- internal/commands/platform/export_internal.go | 7 +- internal/configuration/platform/export.go | 4 +- .../sso/resources/pingone_population_test.go | 2 +- internal/customtypes/export_service_group.go | 68 +++++++++++++++++++ .../customtypes/export_service_group_test.go | 52 ++++++++++++++ internal/customtypes/export_services.go | 48 +++---------- internal/customtypes/export_services_test.go | 44 ------------ internal/profiles/validate.go | 6 +- 9 files changed, 144 insertions(+), 93 deletions(-) create mode 100644 internal/customtypes/export_service_group.go create mode 100644 internal/customtypes/export_service_group_test.go diff --git a/cmd/platform/export_test.go b/cmd/platform/export_test.go index ba1f295c..db254eb1 100644 --- a/cmd/platform/export_test.go +++ b/cmd/platform/export_test.go @@ -53,9 +53,9 @@ func TestPlatformExportCmd_ServiceGroupFlag(t *testing.T) { outputDir := t.TempDir() err := testutils_cobra.ExecutePingcli(t, "platform", "export", - "--output-directory", outputDir, - "--overwrite", - "--service-group", "pingone") + "--"+options.PlatformExportOutputDirectoryOption.CobraParamName, outputDir, + "--"+options.PlatformExportOverwriteOption.CobraParamName, + "--"+options.PlatformExportServiceGroupOption.CobraParamName, "pingone") testutils.CheckExpectedError(t, err, nil) } diff --git a/internal/commands/platform/export_internal.go b/internal/commands/platform/export_internal.go index 7f065c7a..55236d41 100644 --- a/internal/commands/platform/export_internal.go +++ b/internal/commands/platform/export_internal.go @@ -73,8 +73,13 @@ func RunInternalExport(ctx context.Context, commandVersion string) (err error) { return err } + esg := new(customtypes.ExportServiceGroup) + if err = esg.Set(exportServiceGroup); err != nil { + return err + } + es2 := new(customtypes.ExportServices) - if err = es2.SetServiceGroup(exportServiceGroup); err != nil { + if err = es2.SetServiceGroup(esg.String()); err != nil { return err } diff --git a/internal/configuration/platform/export.go b/internal/configuration/platform/export.go index 3fbcba71..fbd1ffd7 100644 --- a/internal/configuration/platform/export.go +++ b/internal/configuration/platform/export.go @@ -50,8 +50,8 @@ func initFormatOption() { func initServiceGroupOption() { cobraParamName := "service-group" - cobraValue := new(customtypes.String) - defaultValue := customtypes.String("") + cobraValue := new(customtypes.ExportServiceGroup) + defaultValue := customtypes.ExportServiceGroup("") envVar := "PINGCLI_EXPORT_SERVICE_GROUP" options.PlatformExportServiceGroupOption = options.Option{ CobraParamName: cobraParamName, diff --git a/internal/connector/pingone/sso/resources/pingone_population_test.go b/internal/connector/pingone/sso/resources/pingone_population_test.go index 35617cc3..5bfc231d 100644 --- a/internal/connector/pingone/sso/resources/pingone_population_test.go +++ b/internal/connector/pingone/sso/resources/pingone_population_test.go @@ -36,7 +36,7 @@ func TestPopulationExport(t *testing.T) { { ResourceType: "pingone_population", ResourceName: "Test Default Idp Population", - ResourceID: fmt.Sprintf("%s/2814912d-4a0f-4104-a779-80c13b2a6dcd", testutils.GetEnvironmentID()), + ResourceID: fmt.Sprintf("%s/2814912d-4a0f-4104-a779-80c13b2a6dcd", clientInfo.PingOneExportEnvironmentID), }, } diff --git a/internal/customtypes/export_service_group.go b/internal/customtypes/export_service_group.go new file mode 100644 index 00000000..3e6b5b21 --- /dev/null +++ b/internal/customtypes/export_service_group.go @@ -0,0 +1,68 @@ +package customtypes + +import ( + "fmt" + "slices" + "strings" + + "github.com/spf13/pflag" +) + +const ( + ENUM_EXPORT_SERVICE_GROUP_PINGONE string = "pingone" +) + +type ExportServiceGroup string + +// Verify that the custom type satisfies the pflag.Value interface +var _ pflag.Value = (*ExportServiceGroup)(nil) + +func (esg *ExportServiceGroup) Set(serviceGroup string) error { + if esg == nil { + return fmt.Errorf("failed to set ExportServiceGroup value: %s. ExportServiceGroup is nil", serviceGroup) + } + + if serviceGroup == "" { + return nil + } + + switch { + case strings.EqualFold(serviceGroup, ENUM_EXPORT_SERVICE_GROUP_PINGONE): + *esg = ExportServiceGroup(ENUM_EXPORT_SERVICE_GROUP_PINGONE) + default: + return fmt.Errorf("unrecognized service group '%s'. Must be one of: %s", serviceGroup, strings.Join(ExportServiceGroupValidValues(), ", ")) + } + return nil +} + +func (es *ExportServices) SetServiceGroup(serviceGroup string) error { + if es == nil { + return fmt.Errorf("failed to set ExportServices value: %s. ExportServices is nil", serviceGroup) + } + + switch { + case strings.EqualFold(ENUM_EXPORT_SERVICE_GROUP_PINGONE, serviceGroup): + *es = append(*es, ExportServicesPingOneValidValues()...) + } + + slices.Sort(*es) + return nil +} + +func (esg ExportServiceGroup) Type() string { + return "string" +} + +func (esg ExportServiceGroup) String() string { + return string(esg) +} + +func ExportServiceGroupValidValues() []string { + validServiceGroups := []string{ + ENUM_EXPORT_SERVICE_GROUP_PINGONE, + } + + slices.Sort(validServiceGroups) + + return validServiceGroups +} diff --git a/internal/customtypes/export_service_group_test.go b/internal/customtypes/export_service_group_test.go new file mode 100644 index 00000000..9d7783a3 --- /dev/null +++ b/internal/customtypes/export_service_group_test.go @@ -0,0 +1,52 @@ +package customtypes_test + +import ( + "testing" + + "github.com/pingidentity/pingcli/internal/customtypes" + "github.com/pingidentity/pingcli/internal/testing/testutils" +) + +// Test ExportServiceGroup Set function +func Test_ExportServiceGroup_Set(t *testing.T) { + // Create a new ExportServiceGroup + esg := new(customtypes.ExportServiceGroup) + + err := esg.Set(customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE) + if err != nil { + t.Errorf("Set returned error: %v", err) + } +} + +// Test ExportServiceGroup Set function fails with invalid value +func Test_ExportServiceGroup_Set_InvalidValue(t *testing.T) { + // Create a new ExportServiceGroup + esg := new(customtypes.ExportServiceGroup) + + invalidValue := "invalid" + + expectedErrorPattern := `^unrecognized service group .*\. Must be one of: .*$` + err := esg.Set(invalidValue) + testutils.CheckExpectedError(t, err, &expectedErrorPattern) +} + +// Test ExportServiceGroup Set function fails with nil +func Test_ExportServiceGroup_Set_Nil(t *testing.T) { + var esg *customtypes.ExportServiceGroup + + val := customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE + + expectedErrorPattern := `^failed to set ExportServiceGroup value: .* ExportServiceGroup is nil$` + err := esg.Set(val) + testutils.CheckExpectedError(t, err, &expectedErrorPattern) +} + +// Test ExportServiceGroup Valid Values returns expected amount +func Test_ExportServiceGroupValidValues(t *testing.T) { + serviceGroupEnum := customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE + + serviceGroupValidValues := customtypes.ExportServiceGroupValidValues() + if serviceGroupValidValues[0] != serviceGroupEnum { + t.Errorf("ExportServiceGroupValidValues returned: %v, expected: %v", serviceGroupValidValues, serviceGroupEnum) + } +} diff --git a/internal/customtypes/export_services.go b/internal/customtypes/export_services.go index c2bb4d0d..5b48cb47 100644 --- a/internal/customtypes/export_services.go +++ b/internal/customtypes/export_services.go @@ -17,7 +17,6 @@ const ( ENUM_EXPORT_SERVICE_PINGONE_MFA string = "pingone-mfa" ENUM_EXPORT_SERVICE_PINGONE_PROTECT string = "pingone-protect" ENUM_EXPORT_SERVICE_PINGFEDERATE string = "pingfederate" - ENUM_EXPORT_SERVICE_GROUP_PINGONE string = "pingone" ) type ExportServices []string @@ -64,34 +63,6 @@ func (es *ExportServices) Set(services string) error { return nil } -func (es *ExportServices) SetServiceGroup(serviceGroup string) error { - if es == nil { - return fmt.Errorf("failed to set ExportServices group value: %s. ExportServices is nil", serviceGroup) - } - - if serviceGroup == "" || serviceGroup == "[]" { - return nil - } - - validServiceGroups := ExportServiceGroupValidValues() - - if !slices.ContainsFunc(validServiceGroups, func(validServiceGroup string) bool { - if strings.EqualFold(validServiceGroup, serviceGroup) { - switch { - case strings.EqualFold(ENUM_EXPORT_SERVICE_GROUP_PINGONE, serviceGroup): - *es = append(*es, ExportServicesPingOneValidValues()...) - } - return true - } - return false - }) { - return fmt.Errorf("failed to set ExportServices: Invalid service group: %s. Allowed service group(s): %s", serviceGroup, strings.Join(validServiceGroups, ", ")) - } - - slices.Sort(*es) - return nil -} - func (es ExportServices) ContainsPingOneService() bool { if es == nil { return false @@ -155,16 +126,15 @@ func ExportServicesPingOneValidValues() []string { return pingOneServices } -func ExportServiceGroupValidValues() []string { - validServiceGroups := []string{ - ENUM_EXPORT_SERVICE_GROUP_PINGONE, - } - - slices.Sort(validServiceGroups) +func (es *ExportServices) Merge(es2 ExportServices) error { + mergedServices := []string{} - return validServiceGroups -} + for _, service := range append(es.GetServices(), es2.GetServices()...) { + if !slices.Contains(mergedServices, service) { + mergedServices = append(mergedServices, service) + } + } -func (es *ExportServices) Merge(es2 ExportServices) error { - return es.Set(strings.Join(append(es.GetServices(), es2.GetServices()...), ",")) + slices.Sort(mergedServices) + return es.Set(strings.Join(mergedServices, ",")) } diff --git a/internal/customtypes/export_services_test.go b/internal/customtypes/export_services_test.go index 446d0f18..f78df857 100644 --- a/internal/customtypes/export_services_test.go +++ b/internal/customtypes/export_services_test.go @@ -96,50 +96,6 @@ func Test_ExportServices_String(t *testing.T) { } } -// Test ExportServiceGroup Set function -func Test_ExportServiceGroup_Set(t *testing.T) { - // Create a new ExportServiceGroup - exportServiceGroup := new(customtypes.ExportServices) - - err := exportServiceGroup.SetServiceGroup(customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE) - if err != nil { - t.Errorf("Set returned error: %v", err) - } -} - -// Test ExportServiceGroup Set function fails with invalid value -func Test_ExportServiceGroup_Set_InvalidValue(t *testing.T) { - // Create a new ExportServiceGroup - exportServiceGroup := new(customtypes.ExportServices) - - invalidValue := "invalid" - - expectedErrorPattern := `^failed to set ExportServices: Invalid service group: .*\. Allowed service group\(s\): .*$` - err := exportServiceGroup.SetServiceGroup(invalidValue) - testutils.CheckExpectedError(t, err, &expectedErrorPattern) -} - -// Test ExportServiceGroup Set function fails with nil -func Test_ExportServiceGroup_Set_Nil(t *testing.T) { - var exportServiceGroup *customtypes.ExportServices - - val := customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE - - expectedErrorPattern := `^failed to set ExportServices group value: .* ExportServices is nil$` - err := exportServiceGroup.SetServiceGroup(val) - testutils.CheckExpectedError(t, err, &expectedErrorPattern) -} - -// Test ExportServiceGroupValidValues -func Test_ExportServiceGroupValidValues(t *testing.T) { - serviceGroupEnum := customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE - - serviceGroupValidValues := customtypes.ExportServiceGroupValidValues() - if serviceGroupValidValues[0] != serviceGroupEnum { - t.Errorf("ExportServiceGroupValidValues returned: %v, expected: %v", serviceGroupValidValues, serviceGroupEnum) - } -} - // Test ExportServicePingOneValidValues func Test_ExportServicesPingOneValidValues(t *testing.T) { pingOneServiceGroupValidValues := customtypes.ExportServicesPingOneValidValues() diff --git a/internal/profiles/validate.go b/internal/profiles/validate.go index 45629fb5..bcbfd91a 100644 --- a/internal/profiles/validate.go +++ b/internal/profiles/validate.go @@ -187,15 +187,15 @@ func validateProfileValues(pName string, profileViper *viper.Viper) (err error) } case options.ENUM_EXPORT_SERVICE_GROUP: switch typedValue := vValue.(type) { - case *customtypes.String: + case *customtypes.ExportServiceGroup: continue case string: - esg := new(customtypes.String) + esg := new(customtypes.ExportServiceGroup) if err = esg.Set(typedValue); err != nil { return fmt.Errorf("profile '%s': variable type '%T' for key '%s' is not a export service group value: %v", pName, typedValue, key, err) } case []any: - esg := new(customtypes.String) + esg := new(customtypes.ExportServiceGroup) for _, v := range typedValue { switch innerTypedValue := v.(type) { case string: From 82b88aeae861d0eba708e093b16fe6fc5cb6ea69 Mon Sep 17 00:00:00 2001 From: wesleymccollam Date: Thu, 20 Mar 2025 10:53:45 -0400 Subject: [PATCH 07/11] rename and move method for retrieving services in group, update equalfold method to match other format --- internal/commands/platform/export_internal.go | 2 +- internal/customtypes/export_service_group.go | 16 +--------------- internal/customtypes/export_services.go | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/internal/commands/platform/export_internal.go b/internal/commands/platform/export_internal.go index 55236d41..f2af1468 100644 --- a/internal/commands/platform/export_internal.go +++ b/internal/commands/platform/export_internal.go @@ -79,7 +79,7 @@ func RunInternalExport(ctx context.Context, commandVersion string) (err error) { } es2 := new(customtypes.ExportServices) - if err = es2.SetServiceGroup(esg.String()); err != nil { + if err = es2.GetServicesInGroup(esg.String()); err != nil { return err } diff --git a/internal/customtypes/export_service_group.go b/internal/customtypes/export_service_group.go index 3e6b5b21..3cb79b00 100644 --- a/internal/customtypes/export_service_group.go +++ b/internal/customtypes/export_service_group.go @@ -27,7 +27,7 @@ func (esg *ExportServiceGroup) Set(serviceGroup string) error { } switch { - case strings.EqualFold(serviceGroup, ENUM_EXPORT_SERVICE_GROUP_PINGONE): + case strings.EqualFold(ENUM_EXPORT_SERVICE_GROUP_PINGONE, serviceGroup): *esg = ExportServiceGroup(ENUM_EXPORT_SERVICE_GROUP_PINGONE) default: return fmt.Errorf("unrecognized service group '%s'. Must be one of: %s", serviceGroup, strings.Join(ExportServiceGroupValidValues(), ", ")) @@ -35,20 +35,6 @@ func (esg *ExportServiceGroup) Set(serviceGroup string) error { return nil } -func (es *ExportServices) SetServiceGroup(serviceGroup string) error { - if es == nil { - return fmt.Errorf("failed to set ExportServices value: %s. ExportServices is nil", serviceGroup) - } - - switch { - case strings.EqualFold(ENUM_EXPORT_SERVICE_GROUP_PINGONE, serviceGroup): - *es = append(*es, ExportServicesPingOneValidValues()...) - } - - slices.Sort(*es) - return nil -} - func (esg ExportServiceGroup) Type() string { return "string" } diff --git a/internal/customtypes/export_services.go b/internal/customtypes/export_services.go index 5b48cb47..429fc431 100644 --- a/internal/customtypes/export_services.go +++ b/internal/customtypes/export_services.go @@ -63,6 +63,20 @@ func (es *ExportServices) Set(services string) error { return nil } +func (es *ExportServices) GetServicesInGroup(serviceGroup string) error { + if es == nil { + return fmt.Errorf("failed to set ExportServices value: %s. ExportServices is nil", serviceGroup) + } + + switch { + case strings.EqualFold(ENUM_EXPORT_SERVICE_GROUP_PINGONE, serviceGroup): + *es = append(*es, ExportServicesPingOneValidValues()...) + } + + slices.Sort(*es) + return nil +} + func (es ExportServices) ContainsPingOneService() bool { if es == nil { return false From 46cc4bee4d6ed02650026d5f1c04a8c4dedb7dea Mon Sep 17 00:00:00 2001 From: wesleymccollam Date: Thu, 20 Mar 2025 10:58:22 -0400 Subject: [PATCH 08/11] remove duplicate default idp population in test as it was a result of merging main --- .../pingone/sso/resources/pingone_population_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/internal/connector/pingone/sso/resources/pingone_population_test.go b/internal/connector/pingone/sso/resources/pingone_population_test.go index 5bfc231d..430cdf63 100644 --- a/internal/connector/pingone/sso/resources/pingone_population_test.go +++ b/internal/connector/pingone/sso/resources/pingone_population_test.go @@ -33,11 +33,6 @@ func TestPopulationExport(t *testing.T) { ResourceName: "Test Default Idp Population", ResourceID: fmt.Sprintf("%s/2814912d-4a0f-4104-a779-80c13b2a6dcd", clientInfo.PingOneExportEnvironmentID), }, - { - ResourceType: "pingone_population", - ResourceName: "Test Default Idp Population", - ResourceID: fmt.Sprintf("%s/2814912d-4a0f-4104-a779-80c13b2a6dcd", clientInfo.PingOneExportEnvironmentID), - }, } testutils.ValidateImportBlocks(t, resource, &expectedImportBlocks) From f058ec1ee3370e358142b28bff660f8f4a6b50de Mon Sep 17 00:00:00 2001 From: wesleymccollam Date: Thu, 20 Mar 2025 11:16:53 -0400 Subject: [PATCH 09/11] correct export service group type in config setvalue --- internal/commands/config/set_internal.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/commands/config/set_internal.go b/internal/commands/config/set_internal.go index 89933c23..e8c0af25 100644 --- a/internal/commands/config/set_internal.go +++ b/internal/commands/config/set_internal.go @@ -124,7 +124,7 @@ func setValue(profileViper *viper.Viper, vKey, vValue string, valueType options. } profileViper.Set(vKey, exportFormat) case options.ENUM_EXPORT_SERVICE_GROUP: - exportServiceGroup := new(customtypes.String) + 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]: %v", vKey, strings.Join(customtypes.ExportServiceGroupValidValues(), ", "), err) } From 48f8846551808dfff5a1a8a0f71c7a341256826d Mon Sep 17 00:00:00 2001 From: wesleymccollam Date: Thu, 20 Mar 2025 12:37:44 -0400 Subject: [PATCH 10/11] remove uneeded slice validation case for export service group, update Makefile golangci-lint target for correct command value --- Makefile | 2 +- internal/profiles/validate.go | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/Makefile b/Makefile index 3db5c84b..ac628f91 100644 --- a/Makefile +++ b/Makefile @@ -95,7 +95,7 @@ importfmtlint: golangcilint: @echo -n "Running 'golangci-lint' to check for code quality issues..." @# Clear the cache for every run, so that the linter outputs the same results as the GH Actions workflow - @if golangci-lint cache clear && golangci-lint run --timeout 5m ./...; then \ + @if golangci-lint cache clean && golangci-lint run --timeout 5m ./...; then \ echo " SUCCESS"; \ else \ echo " FAILED"; \ diff --git a/internal/profiles/validate.go b/internal/profiles/validate.go index bcbfd91a..8f2ec38d 100644 --- a/internal/profiles/validate.go +++ b/internal/profiles/validate.go @@ -194,19 +194,6 @@ func validateProfileValues(pName string, profileViper *viper.Viper) (err error) if err = esg.Set(typedValue); err != nil { return fmt.Errorf("profile '%s': variable type '%T' for key '%s' is not a export service group value: %v", pName, typedValue, key, err) } - case []any: - esg := new(customtypes.ExportServiceGroup) - for _, v := range typedValue { - switch innerTypedValue := v.(type) { - case string: - if err = esg.Set(innerTypedValue); err != nil { - return fmt.Errorf("profile '%s': variable type '%T' for key '%s' is not a export service group value: %v", pName, typedValue, key, err) - } - default: - return fmt.Errorf("profile '%s': variable type %T for key '%s' is not a export service group value", pName, typedValue, key) - } - - } default: return fmt.Errorf("profile '%s': variable type %T for key '%s' is not a export service group value", pName, typedValue, key) } From da7b123537f7b268b26c866ba84b78e1abef752f Mon Sep 17 00:00:00 2001 From: wesleymccollam Date: Thu, 20 Mar 2025 13:13:04 -0400 Subject: [PATCH 11/11] edit service group method, update export test to use cobraparamname, edit services and service group flag usage content --- cmd/platform/export_test.go | 3 ++- internal/commands/platform/export_internal.go | 2 +- internal/configuration/platform/export.go | 6 +++--- internal/customtypes/export_services.go | 11 +++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cmd/platform/export_test.go b/cmd/platform/export_test.go index db254eb1..7d315c3b 100644 --- a/cmd/platform/export_test.go +++ b/cmd/platform/export_test.go @@ -62,7 +62,8 @@ func TestPlatformExportCmd_ServiceGroupFlag(t *testing.T) { // Test Platform Export Command --service-group with non-supported service group func TestPlatformExportCmd_ServiceGroupFlagInvalidServiceGroup(t *testing.T) { expectedErrorPattern := `^invalid argument ".*" for "-g, --service-group" flag: unrecognized service group '.*'\. Must be one of: .*$` - err := testutils_cobra.ExecutePingcli(t, "platform", "export", "--service-group", "invalid") + err := testutils_cobra.ExecutePingcli(t, "platform", "export", + "--"+options.PlatformExportServiceGroupOption.CobraParamName, "invalid") testutils.CheckExpectedError(t, err, &expectedErrorPattern) } diff --git a/internal/commands/platform/export_internal.go b/internal/commands/platform/export_internal.go index f2af1468..647d8f14 100644 --- a/internal/commands/platform/export_internal.go +++ b/internal/commands/platform/export_internal.go @@ -79,7 +79,7 @@ func RunInternalExport(ctx context.Context, commandVersion string) (err error) { } es2 := new(customtypes.ExportServices) - if err = es2.GetServicesInGroup(esg.String()); err != nil { + if err = es2.SetServicesByServiceGroup(esg); err != nil { return err } diff --git a/internal/configuration/platform/export.go b/internal/configuration/platform/export.go index fbd1ffd7..40609728 100644 --- a/internal/configuration/platform/export.go +++ b/internal/configuration/platform/export.go @@ -63,8 +63,10 @@ func initServiceGroupOption() { Shorthand: "g", Usage: fmt.Sprintf( "Specifies the service group to export. "+ - "\nOptions are: %s.", + "\nOptions are: %s."+ + "\nExample: '%s'", strings.Join(customtypes.ExportServiceGroupValidValues(), ", "), + string(customtypes.ENUM_EXPORT_SERVICE_GROUP_PINGONE), ), Value: cobraValue, }, @@ -89,11 +91,9 @@ func initServicesOption() { Shorthand: "s", Usage: fmt.Sprintf( "Specifies the service(s) to export. Accepts a comma-separated string to delimit multiple services. "+ - "(default %s)"+ "\nOptions are: %s."+ "\nExample: '%s,%s,%s'", strings.Join(customtypes.ExportServicesValidValues(), ", "), - strings.Join(customtypes.ExportServicesValidValues(), ", "), string(customtypes.ENUM_EXPORT_SERVICE_PINGONE_SSO), string(customtypes.ENUM_EXPORT_SERVICE_PINGONE_MFA), string(customtypes.ENUM_EXPORT_SERVICE_PINGFEDERATE), diff --git a/internal/customtypes/export_services.go b/internal/customtypes/export_services.go index 429fc431..c3cfe2f8 100644 --- a/internal/customtypes/export_services.go +++ b/internal/customtypes/export_services.go @@ -63,18 +63,17 @@ func (es *ExportServices) Set(services string) error { return nil } -func (es *ExportServices) GetServicesInGroup(serviceGroup string) error { +func (es *ExportServices) SetServicesByServiceGroup(serviceGroup *ExportServiceGroup) error { if es == nil { return fmt.Errorf("failed to set ExportServices value: %s. ExportServices is nil", serviceGroup) } switch { - case strings.EqualFold(ENUM_EXPORT_SERVICE_GROUP_PINGONE, serviceGroup): - *es = append(*es, ExportServicesPingOneValidValues()...) + case strings.EqualFold(ENUM_EXPORT_SERVICE_GROUP_PINGONE, serviceGroup.String()): + return es.Set(strings.Join(ExportServicesPingOneValidValues(), ",")) + default: + return fmt.Errorf("failed to SetServicesByServiceGroup: Invalid service group: %s. Allowed services: %s", serviceGroup.String(), strings.Join(ExportServiceGroupValidValues(), ", ")) } - - slices.Sort(*es) - return nil } func (es ExportServices) ContainsPingOneService() bool {