diff --git a/cmd/talosctl/cmd/talos/config.go b/cmd/talosctl/cmd/talos/config.go index 599afd007..ecc05ad82 100644 --- a/cmd/talosctl/cmd/talos/config.go +++ b/cmd/talosctl/cmd/talos/config.go @@ -10,6 +10,7 @@ import ( "crypto/x509" "encoding/base64" "encoding/pem" + "errors" "fmt" "os" "sort" @@ -19,6 +20,7 @@ import ( "time" "github.com/dustin/go-humanize" + "github.com/ryanuber/go-glob" "github.com/siderolabs/gen/maps" "github.com/spf13/cobra" "google.golang.org/protobuf/types/known/durationpb" @@ -186,6 +188,89 @@ var configAddCmd = &cobra.Command{ }, } +// configRemoveCmdFlags represents the `config remove` command flags. +var configRemoveCmdFlags struct { + noconfirm bool + dry bool +} + +// configRemoveCmd represents the `config remove` command. +var configRemoveCmd = &cobra.Command{ + Use: "remove ", + Short: "Remove contexts", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + pattern := args[0] + if pattern == "" { + return fmt.Errorf("no context specified") + } + + c, err := clientconfig.Open(GlobalArgs.Talosconfig) + if err != nil { + return fmt.Errorf("error reading config: %w", err) + } + + if len(c.Contexts) == 0 { + return errors.New("no contexts defined") + } + + matches := sortInPlace(maps.Keys( + maps.Filter(c.Contexts, func(context string, _ *clientconfig.Context) bool { + return glob.Glob(pattern, context) + }), + )) + if len(matches) == 0 { + return fmt.Errorf("no contexts matched %q", pattern) + } + + // we want to prevent file updates in case there were no changes + noChanges := true + + for _, match := range matches { + if match == c.Context { + fmt.Fprintf( + os.Stderr, + "skipping removal of current context %q, please change it to another before removing\n", + match, + ) + + continue + } + + if !configRemoveCmdFlags.noconfirm { + prompt := fmt.Sprintf("remove context %q", match) + + if !helpers.Confirm(prompt + "?") { + continue + } + } else { + fmt.Fprintf(os.Stderr, "removing context %q\n", match) + } + + noChanges = false + delete(c.Contexts, match) + } + + if configRemoveCmdFlags.dry || noChanges { + return nil + } + + err = c.Save(GlobalArgs.Talosconfig) + if err != nil { + return fmt.Errorf("error writing config: %w", err) + } + + return nil + }, + ValidArgsFunction: CompleteConfigContext, +} + +func sortInPlace(slc []string) []string { + sort.Slice(slc, func(i, j int) bool { return slc[i] < slc[j] }) + + return slc +} + func checkAndSetCrtAndKey(configContext *clientconfig.Context) error { crt := configAddCmdFlags.crt key := configAddCmdFlags.key @@ -430,7 +515,8 @@ var configInfoCmd = &cobra.Command{ }, } -// CompleteConfigContext represents tab completion for `--context` argument and `config context` command. +// CompleteConfigContext represents tab completion for `--context` +// argument and `config [context|remove]` command. func CompleteConfigContext(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { c, err := clientconfig.Open(GlobalArgs.Talosconfig) if err != nil { @@ -449,6 +535,7 @@ func init() { configNodeCmd, configContextCmd, configAddCmd, + configRemoveCmd, configGetContextsCmd, configMergeCmd, configNewCmd, @@ -459,6 +546,14 @@ func init() { configAddCmd.Flags().StringVar(&configAddCmdFlags.crt, "crt", "", "the path to the certificate") configAddCmd.Flags().StringVar(&configAddCmdFlags.key, "key", "", "the path to the key") + configRemoveCmd.Flags().BoolVarP( + &configRemoveCmdFlags.noconfirm, "noconfirm", "y", false, + "do not ask for confirmation", + ) + configRemoveCmd.Flags().BoolVar( + &configRemoveCmdFlags.dry, "dry-run", false, "dry run", + ) + configNewCmd.Flags().StringSliceVar(&configNewCmdFlags.roles, "roles", role.MakeSet(role.Admin).Strings(), "roles") configNewCmd.Flags().DurationVar(&configNewCmdFlags.crtTTL, "crt-ttl", 87600*time.Hour, "certificate TTL") diff --git a/cmd/talosctl/pkg/talos/helpers/confirm.go b/cmd/talosctl/pkg/talos/helpers/confirm.go new file mode 100644 index 000000000..19f1d74b9 --- /dev/null +++ b/cmd/talosctl/pkg/talos/helpers/confirm.go @@ -0,0 +1,30 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package helpers + +import ( + "fmt" + "strings" +) + +var okays = []string{"y", "yes"} + +// Confirm asks the user to confirm their action. Anything other than +// `y` and `yes` returns false. +func Confirm(prompt string) bool { + var inp string + + fmt.Printf("%s (y/N): ", prompt) + fmt.Scanf("%s", &inp) + inp = strings.TrimSpace(inp) + + for _, ok := range okays { + if strings.EqualFold(inp, ok) { + return true + } + } + + return false +} diff --git a/website/content/v1.4/reference/cli.md b/website/content/v1.4/reference/cli.md index ed4b9bfd5..80a31884d 100644 --- a/website/content/v1.4/reference/cli.md +++ b/website/content/v1.4/reference/cli.md @@ -571,6 +571,36 @@ talosctl config node ... [flags] * [talosctl config](#talosctl-config) - Manage the client configuration file (talosconfig) +## talosctl config remove + +Remove contexts + +``` +talosctl config remove [flags] +``` + +### Options + +``` + --dry-run dry run + -h, --help help for remove + -y, --noconfirm do not ask for confirmation +``` + +### Options inherited from parent commands + +``` + --cluster string Cluster to connect to if a proxy endpoint is used. + --context string Context to be used in command + -e, --endpoints strings override default endpoints in Talos configuration + -n, --nodes strings target the specified nodes + --talosconfig string The path to the Talos configuration file. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order. +``` + +### SEE ALSO + +* [talosctl config](#talosctl-config) - Manage the client configuration file (talosconfig) + ## talosctl config Manage the client configuration file (talosconfig) @@ -602,6 +632,7 @@ Manage the client configuration file (talosconfig) * [talosctl config merge](#talosctl-config-merge) - Merge additional contexts from another client configuration file * [talosctl config new](#talosctl-config-new) - Generate a new client configuration file * [talosctl config node](#talosctl-config-node) - Set the node(s) for the current context +* [talosctl config remove](#talosctl-config-remove) - Remove contexts ## talosctl conformance kubernetes