176 lines
5.9 KiB
Go
176 lines
5.9 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package cmdutil provides reusable CLI helper functions for Cobra commands.
|
|
// Both the open-source CLI and private overlays import this package to avoid
|
|
// duplicating flag validation, time parsing, and UX helpers.
|
|
package cmdutil
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const hintOnlyCommandAnnotation = "dws.command.hint_only"
|
|
|
|
// GroupRunE is a reusable RunE for parent (group) commands that have no
|
|
// business logic of their own. With args it returns an error listing available
|
|
// subcommands; without args it shows help.
|
|
func GroupRunE(cmd *cobra.Command, args []string) error {
|
|
if len(args) > 0 {
|
|
var names []string
|
|
for _, sub := range cmd.Commands() {
|
|
if !sub.Hidden {
|
|
names = append(names, sub.Name())
|
|
}
|
|
}
|
|
return fmt.Errorf("unknown subcommand %q for %q\n available: %s\n hint: %s --help",
|
|
args[0], cmd.CommandPath(), strings.Join(names, ", "), cmd.CommandPath())
|
|
}
|
|
return cmd.Help()
|
|
}
|
|
|
|
// HintSubCmd creates a hidden subcommand that only prints a disambiguation hint.
|
|
func HintSubCmd(use, hint string) *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: use,
|
|
Hidden: true,
|
|
Annotations: map[string]string{hintOnlyCommandAnnotation: "true"},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
return fmt.Errorf("ambiguous command %q for %q\n hint: %s",
|
|
use, cmd.Parent().CommandPath(), hint)
|
|
},
|
|
}
|
|
}
|
|
|
|
// IsHintOnlyCommand reports whether cmd is a hidden compatibility prompt that
|
|
// has no business execution of its own. Reviewed command-path fallbacks may
|
|
// supersede these nodes, but must still reject collisions with real commands.
|
|
func IsHintOnlyCommand(cmd *cobra.Command) bool {
|
|
return cmd != nil && cmd.Annotations[hintOnlyCommandAnnotation] == "true"
|
|
}
|
|
|
|
// MustGetFlag retrieves a string flag value, checking both local and inherited flags.
|
|
func MustGetFlag(cmd *cobra.Command, name string) string {
|
|
val, _ := cmd.Flags().GetString(name)
|
|
if val == "" {
|
|
val, _ = cmd.InheritedFlags().GetString(name)
|
|
}
|
|
return val
|
|
}
|
|
|
|
// FlagOrFallback reads the primary flag; if empty, falls back through alias
|
|
// flags in order, returning the first non-empty value.
|
|
func FlagOrFallback(cmd *cobra.Command, primary string, aliases ...string) string {
|
|
if v, _ := cmd.Flags().GetString(primary); v != "" {
|
|
return v
|
|
}
|
|
for _, alias := range aliases {
|
|
if v, _ := cmd.Flags().GetString(alias); v != "" {
|
|
return v
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// MustFlagOrFallback works like FlagOrFallback but returns an error when all
|
|
// flags are empty.
|
|
func MustFlagOrFallback(cmd *cobra.Command, primary string, aliases ...string) (string, error) {
|
|
v := FlagOrFallback(cmd, primary, aliases...)
|
|
if v == "" {
|
|
return "", fmt.Errorf("flag --%s is required\n hint: %s", primary, cmd.Example)
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
// MustFlagWithHint returns an error with an explicit usage example when the
|
|
// flag is empty.
|
|
func MustFlagWithHint(cmd *cobra.Command, name, example string) (string, error) {
|
|
v := MustGetFlag(cmd, name)
|
|
if v == "" {
|
|
return "", fmt.Errorf("flag --%s is required\n hint: %s", name, example)
|
|
}
|
|
return v, nil
|
|
}
|
|
|
|
// ValidateRequiredFlags checks that all named string flags are non-empty.
|
|
// Returns a formatted error listing all missing flags, or nil.
|
|
func ValidateRequiredFlags(cmd *cobra.Command, names ...string) error {
|
|
var missing []string
|
|
for _, name := range names {
|
|
v, _ := cmd.Flags().GetString(name)
|
|
if v == "" {
|
|
missing = append(missing, name)
|
|
}
|
|
}
|
|
return MissingRequiredFlagsError(cmd, missing...)
|
|
}
|
|
|
|
// MissingRequiredFlagsError formats the unified "missing required flag(s)"
|
|
// error for the given flag names, or returns nil when none are missing. Use
|
|
// this when the caller has already determined which flags are missing (e.g.
|
|
// after alias/env fallback resolution).
|
|
func MissingRequiredFlagsError(cmd *cobra.Command, names ...string) error {
|
|
if len(names) == 0 {
|
|
return nil
|
|
}
|
|
flags := make([]string, 0, len(names))
|
|
for _, name := range names {
|
|
flags = append(flags, "--"+name)
|
|
}
|
|
return fmt.Errorf("missing required flag(s): %s\n usage: %s\n example:\n%s",
|
|
strings.Join(flags, ", "), cmd.UseLine(), cmd.Example)
|
|
}
|
|
|
|
// ValidateRequiredFlagWithAliases checks that at least one of the primary flag
|
|
// or its aliases is non-empty.
|
|
func ValidateRequiredFlagWithAliases(cmd *cobra.Command, primary string, aliases ...string) error {
|
|
if FlagOrFallback(cmd, primary, aliases...) != "" {
|
|
return nil
|
|
}
|
|
all := append([]string{primary}, aliases...)
|
|
return fmt.Errorf("missing required flag: --%s (or %s)\n usage: %s\n example:\n%s",
|
|
primary, "--"+strings.Join(all[1:], " / --"), cmd.UseLine(), cmd.Example)
|
|
}
|
|
|
|
// ConfirmDelete asks for interactive confirmation before destructive operations.
|
|
// Returns true if --yes/-y flag is set or the user types "yes"/"y".
|
|
func ConfirmDelete(cmd *cobra.Command, resourceType, resourceName string) bool {
|
|
if yes, _ := cmd.Flags().GetBool("yes"); yes {
|
|
return true
|
|
}
|
|
for _, arg := range os.Args[1:] {
|
|
if arg == "--yes" || arg == "-y" {
|
|
return true
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(cmd.ErrOrStderr(), "About to delete %s: %s\n", resourceType, resourceName)
|
|
fmt.Fprint(cmd.ErrOrStderr(), "Confirm deletion? (yes/no): ")
|
|
|
|
reader := bufio.NewReader(os.Stdin)
|
|
answer, _ := reader.ReadString('\n')
|
|
answer = strings.TrimSpace(strings.ToLower(answer))
|
|
|
|
if answer == "yes" || answer == "y" {
|
|
return true
|
|
}
|
|
|
|
fmt.Fprintln(cmd.ErrOrStderr(), "Operation cancelled")
|
|
return false
|
|
}
|