feat: new talosctl config remove to remove context

Adds a new sub-command to talosctl config. It takes in the context to be
deleted as argument and supports glob matching.

A local flag --noconfirm|-y can be passed to bypass the confirmation
prompt.

It also supports dry run by passing the --dry-run flag similar to
apply-config and edit commands.

Example:

    $ talosctl config remove 'ctx-*'
    Remove context ctx-a? (y/N): y
    Remove context ctx-b? (y/N): y

Signed-off-by: Murtaza Udaipurwala <murtaza@murtazau.xyz>
Signed-off-by: Dmitriy Matrenichev <dmitry.matrenichev@siderolabs.com>
This commit is contained in:
Murtaza Udaipurwala 2022-12-21 18:39:31 +05:30 committed by Dmitriy Matrenichev
parent fcb19ff516
commit ba8265bc5c
No known key found for this signature in database
GPG Key ID: D3363CF894E68892
3 changed files with 157 additions and 1 deletions

View File

@ -10,6 +10,7 @@ import (
"crypto/x509" "crypto/x509"
"encoding/base64" "encoding/base64"
"encoding/pem" "encoding/pem"
"errors"
"fmt" "fmt"
"os" "os"
"sort" "sort"
@ -19,6 +20,7 @@ import (
"time" "time"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/ryanuber/go-glob"
"github.com/siderolabs/gen/maps" "github.com/siderolabs/gen/maps"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"google.golang.org/protobuf/types/known/durationpb" "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 <context>",
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 { func checkAndSetCrtAndKey(configContext *clientconfig.Context) error {
crt := configAddCmdFlags.crt crt := configAddCmdFlags.crt
key := configAddCmdFlags.key 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) { func CompleteConfigContext(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
c, err := clientconfig.Open(GlobalArgs.Talosconfig) c, err := clientconfig.Open(GlobalArgs.Talosconfig)
if err != nil { if err != nil {
@ -449,6 +535,7 @@ func init() {
configNodeCmd, configNodeCmd,
configContextCmd, configContextCmd,
configAddCmd, configAddCmd,
configRemoveCmd,
configGetContextsCmd, configGetContextsCmd,
configMergeCmd, configMergeCmd,
configNewCmd, configNewCmd,
@ -459,6 +546,14 @@ func init() {
configAddCmd.Flags().StringVar(&configAddCmdFlags.crt, "crt", "", "the path to the certificate") configAddCmd.Flags().StringVar(&configAddCmdFlags.crt, "crt", "", "the path to the certificate")
configAddCmd.Flags().StringVar(&configAddCmdFlags.key, "key", "", "the path to the key") 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().StringSliceVar(&configNewCmdFlags.roles, "roles", role.MakeSet(role.Admin).Strings(), "roles")
configNewCmd.Flags().DurationVar(&configNewCmdFlags.crtTTL, "crt-ttl", 87600*time.Hour, "certificate TTL") configNewCmd.Flags().DurationVar(&configNewCmdFlags.crtTTL, "crt-ttl", 87600*time.Hour, "certificate TTL")

View File

@ -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
}

View File

@ -571,6 +571,36 @@ talosctl config node <endpoint>... [flags]
* [talosctl config](#talosctl-config) - Manage the client configuration file (talosconfig) * [talosctl config](#talosctl-config) - Manage the client configuration file (talosconfig)
## talosctl config remove
Remove contexts
```
talosctl config remove <context> [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 ## talosctl config
Manage the client configuration file (talosconfig) 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 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 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 node](#talosctl-config-node) - Set the node(s) for the current context
* [talosctl config remove](#talosctl-config-remove) - Remove contexts
## talosctl conformance kubernetes ## talosctl conformance kubernetes