diff --git a/commands/dns/dns.go b/commands/dns/dns.go index 39a6af049..67529c608 100644 --- a/commands/dns/dns.go +++ b/commands/dns/dns.go @@ -26,5 +26,5 @@ func DNSCommand() *core.Command { cmd.AddCommand(dnssec.Root()) cmd.AddCommand(secondary_zones.Root()) - return cmd + return core.WithRegionalFlags(cmd, "https://dns.%s.ionos.com", []string{"de/fra", "de/txl"}) } diff --git a/internal/core/command.go b/internal/core/command.go index ef702a199..435e92027 100644 --- a/internal/core/command.go +++ b/internal/core/command.go @@ -38,21 +38,64 @@ func (c *Command) Name() string { } } +// WithRegionalFlags adds regional flag support to a command, allowing users to specify a location or override the server URL. +// +// # Usage +// +// To use this function, wrap the root command of your API and specify the baseURL and allowed locations. +// +// Example: +// +// ``` +// +// func DNSCommand() *core.Command { +// cmd := &core.Command{ +// Command: &cobra.Command{ +// Use: "dns", +// Short: "The sub-commands of the 'dns' resource help automate DNS Zone and Record management", +// TraverseChildren: true, +// }, +// } +// +// // Add regional flags +// return core.WithRegionalFlags(cmd, "https://dns.%s.ionos.com", []string{"de/fra", "de/txl"}) +// } +// +// ``` +// +// - 'baseURL': The base URL for the API, with an optional '%s' placeholder for the location (e.g., '"https://dns.%s.ionos.com"'). +// - 'allowedLocations': A slice of allowed locations (e.g., '[]string{"de/fra", "de/txl"}'). These will populate the '--location' flag completion. +// +// # Notes +// +// - The '--server-url' flag allows users to override the API host URL manually. +// - The '--location' flag allows users to specify a region, which replaces the '%s' placeholder in the 'baseURL'. +// - If '--location' is used and is valid (from 'allowedLocations'), the 'baseURL' is formatted with the normalized location. +// - If '--location' is invalid or unsupported, a warning is logged, but the constructed URL is still attempted. +// - If 'allowedLocations' is empty, the function panics, as this is considered a programming error.// +// - If an unsupported location is provided, a warning is logged: +// 'WARN: is an invalid location. Valid locations are: ' +// - This also marks '--api-url' and '--location' flags as mutually exclusive. +// - The first location in 'allowedLocations' is used as the default URL if no location is provided. func WithRegionalFlags(c *Command, baseURL string, allowedLocations []string) *Command { - locationsToUrl := make(map[string]string, len(allowedLocations)) - for _, loc := range allowedLocations { - // de/fra -> de-fra - normalizedLoc := strings.ReplaceAll(loc, "/", "-") - locationsToUrl[normalizedLoc] = fmt.Sprintf(baseURL, normalizedLoc) - } + // Generate the default URL using the first provided location, if available + var defaultUrl string + if len(allowedLocations) > 0 { + // if baseURL does not contain a placeholder, throw a panic, as this is a programming error + if !strings.Contains(baseURL, "%s") { + panic(fmt.Errorf("baseURL %s does not contain a placeholder for location", baseURL)) + } - // generate the default URL as the first provided location - defaultLocation := allowedLocations[0] - defaultUrl := fmt.Sprintf(baseURL, strings.ReplaceAll(defaultLocation, "/", "-")) + defaultLocation := allowedLocations[0] + defaultUrl = fmt.Sprintf(baseURL, strings.ReplaceAll(defaultLocation, "/", "-")) + } else { + // If no locations are provided, panic, this is a programming error + panic(fmt.Errorf("no allowedLocations provided for %s", c.Command.Name())) + } - // add the server URL flag + // Add the server URL flag c.Command.PersistentFlags().StringP( - constants.ArgServerUrl, constants.ArgServerUrlShort, defaultUrl, "Override default host url", + constants.ArgServerUrl, constants.ArgServerUrlShort, defaultUrl, "Override default host URL", ) viper.BindPFlag(constants.ArgServerUrl, c.Command.PersistentFlags().Lookup(constants.ArgServerUrl)) @@ -67,7 +110,7 @@ func WithRegionalFlags(c *Command, baseURL string, allowedLocations []string) *C }, ) - // wrap the pre-run logic to handle mutually exclusive flags + // Wrap the pre-run logic to handle mutually exclusive flags originalPreRun := c.Command.PersistentPreRunE c.Command.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { if originalPreRun != nil { @@ -76,15 +119,18 @@ func WithRegionalFlags(c *Command, baseURL string, allowedLocations []string) *C } } + // Mark the flags as mutually exclusive c.Command.MarkFlagsMutuallyExclusive(constants.ArgServerUrl, constants.FlagLocation) + // Handle the location flag if location, _ := cmd.Flags().GetString(constants.FlagLocation); location != "" { - if url, ok := locationsToUrl[location]; ok { - viper.Set(constants.ArgServerUrl, url) + normalizedLoc := strings.ReplaceAll(location, "/", "-") + if strings.Contains(baseURL, "%s") { + viper.Set(constants.ArgServerUrl, fmt.Sprintf(baseURL, normalizedLoc)) } else { + // Log a warning if trying to use location with a non-placeholder URL fmt.Fprintf(c.Command.ErrOrStderr(), jsontabwriter.GenerateLogOutput( - "WARN: %s is an invalid location. Valid locations are: %s", - location, allowedLocations)) + "WARN: Ignoring location %s because this API does not support region-based URLs", location)) } }