github.com/pachyderm/pachyderm@v1.13.4/src/server/config/cmds.go (about)

     1  package cmds
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"log"
     9  	"os"
    10  	"sort"
    11  
    12  	"github.com/pachyderm/pachyderm/src/client/pkg/config"
    13  	"github.com/pachyderm/pachyderm/src/client/pkg/errors"
    14  	"github.com/pachyderm/pachyderm/src/client/pkg/grpcutil"
    15  	"github.com/pachyderm/pachyderm/src/server/cmd/pachctl/shell"
    16  	"github.com/pachyderm/pachyderm/src/server/pkg/cmdutil"
    17  
    18  	prompt "github.com/c-bata/go-prompt"
    19  	"github.com/gogo/protobuf/jsonpb"
    20  	"github.com/spf13/cobra"
    21  )
    22  
    23  const (
    24  	listContextHeader = "ACTIVE\tNAME"
    25  )
    26  
    27  // Cmds returns a slice containing admin commands.
    28  func Cmds() []*cobra.Command {
    29  	marshaller := &jsonpb.Marshaler{
    30  		Indent:   "  ",
    31  		OrigName: true,
    32  	}
    33  
    34  	var commands []*cobra.Command
    35  
    36  	getMetrics := &cobra.Command{
    37  		Short: "Gets whether metrics are enabled.",
    38  		Long:  "Gets whether metrics are enabled.",
    39  		Run: cmdutil.Run(func(args []string) (retErr error) {
    40  			cfg, err := config.Read(false, false)
    41  			if err != nil {
    42  				return err
    43  			}
    44  			fmt.Printf("%v\n", cfg.V2.Metrics)
    45  			return nil
    46  		}),
    47  	}
    48  	commands = append(commands, cmdutil.CreateAlias(getMetrics, "config get metrics"))
    49  
    50  	setMetrics := &cobra.Command{
    51  		Use:   "{{alias}} (true | false)",
    52  		Short: "Sets whether metrics are enabled.",
    53  		Long:  "Sets whether metrics are enabled.",
    54  		Run: cmdutil.RunFixedArgs(1, func(args []string) (retErr error) {
    55  			metrics := true
    56  			if args[0] == "false" {
    57  				metrics = false
    58  			} else if args[0] != "true" {
    59  				return errors.New("invalid argument; use either `true` or `false`")
    60  			}
    61  
    62  			cfg, err := config.Read(false, false)
    63  			if err != nil {
    64  				return err
    65  			}
    66  
    67  			cfg.V2.Metrics = metrics
    68  			return cfg.Write()
    69  		}),
    70  	}
    71  	commands = append(commands, cmdutil.CreateAlias(setMetrics, "config set metrics"))
    72  
    73  	getActiveContext := &cobra.Command{
    74  		Short: "Gets the currently active context.",
    75  		Long:  "Gets the currently active context.",
    76  		Run: cmdutil.Run(func(args []string) (retErr error) {
    77  			cfg, err := config.Read(false, false)
    78  			if err != nil {
    79  				return err
    80  			}
    81  			activeContext, _, err := cfg.ActiveContext(false)
    82  			if err != nil {
    83  				return err
    84  			}
    85  			if activeContext == "" {
    86  				fmt.Println("NONE")
    87  			} else {
    88  				fmt.Printf("%s\n", activeContext)
    89  			}
    90  			return nil
    91  		}),
    92  	}
    93  	commands = append(commands, cmdutil.CreateAlias(getActiveContext, "config get active-context"))
    94  
    95  	setActiveContext := &cobra.Command{
    96  		Use:   "{{alias}} <context>",
    97  		Short: "Sets the currently active context.",
    98  		Long:  "Sets the currently active context.",
    99  		Run: cmdutil.RunFixedArgs(1, func(args []string) (retErr error) {
   100  			cfg, err := config.Read(false, false)
   101  			if err != nil {
   102  				return err
   103  			}
   104  			if _, ok := cfg.V2.Contexts[args[0]]; !ok {
   105  				return errors.Errorf("context does not exist: %s", args[0])
   106  			}
   107  			cfg.V2.ActiveContext = args[0]
   108  			return cfg.Write()
   109  		}),
   110  	}
   111  	shell.RegisterCompletionFunc(setActiveContext, contextCompletion)
   112  	commands = append(commands, cmdutil.CreateAlias(setActiveContext, "config set active-context"))
   113  
   114  	getContext := &cobra.Command{
   115  		Use:   "{{alias}} <context>",
   116  		Short: "Gets a context.",
   117  		Long:  "Gets the config of a context by its name.",
   118  		Run: cmdutil.RunFixedArgs(1, func(args []string) (retErr error) {
   119  			cfg, err := config.Read(false, false)
   120  			if err != nil {
   121  				return err
   122  			}
   123  
   124  			context, ok := cfg.V2.Contexts[args[0]]
   125  			if !ok {
   126  				return errors.Errorf("context does not exist: %s", args[0])
   127  			}
   128  
   129  			if err = marshaller.Marshal(os.Stdout, context); err != nil {
   130  				return err
   131  			}
   132  
   133  			fmt.Println()
   134  			return nil
   135  		}),
   136  	}
   137  	shell.RegisterCompletionFunc(getContext, contextCompletion)
   138  	commands = append(commands, cmdutil.CreateAlias(getContext, "config get context"))
   139  
   140  	var overwrite bool
   141  	var kubeContextName string
   142  	setContext := &cobra.Command{
   143  		Use:   "{{alias}} <context>",
   144  		Short: "Set a context.",
   145  		Long:  "Set a context config from a given name and either JSON stdin, or a given kubernetes context.",
   146  		Run: cmdutil.RunFixedArgs(1, func(args []string) (retErr error) {
   147  			name := args[0]
   148  
   149  			cfg, err := config.Read(false, false)
   150  			if err != nil {
   151  				return err
   152  			}
   153  
   154  			if !overwrite {
   155  				if _, ok := cfg.V2.Contexts[name]; ok {
   156  					return errors.Errorf("context '%s' already exists, use `--overwrite` if you wish to replace it", args[0])
   157  				}
   158  			}
   159  
   160  			var context config.Context
   161  			if kubeContextName != "" {
   162  				kubeConfig, err := config.RawKubeConfig()
   163  				if err != nil {
   164  					return err
   165  				}
   166  
   167  				kubeContext := kubeConfig.Contexts[kubeContextName]
   168  				if kubeContext == nil {
   169  					return errors.Errorf("kubernetes context does not exist: %s", kubeContextName)
   170  				}
   171  
   172  				context = config.Context{
   173  					Source:      config.ContextSource_IMPORTED,
   174  					ClusterName: kubeContext.Cluster,
   175  					AuthInfo:    kubeContext.AuthInfo,
   176  					Namespace:   kubeContext.Namespace,
   177  				}
   178  			} else {
   179  				fmt.Println("Reading from stdin.")
   180  
   181  				var buf bytes.Buffer
   182  				var decoder *json.Decoder
   183  
   184  				contextReader := io.TeeReader(os.Stdin, &buf)
   185  				decoder = json.NewDecoder(contextReader)
   186  
   187  				if err := jsonpb.UnmarshalNext(decoder, &context); err != nil {
   188  					if errors.Is(err, io.EOF) {
   189  						return errors.New("unexpected EOF")
   190  					}
   191  					return errors.Wrapf(err, "malformed context")
   192  				}
   193  
   194  				pachdAddress, err := grpcutil.ParsePachdAddress(context.PachdAddress)
   195  				if err != nil {
   196  					if !errors.Is(err, grpcutil.ErrNoPachdAddress) {
   197  						return err
   198  					}
   199  				} else {
   200  					context.PachdAddress = pachdAddress.Qualified()
   201  				}
   202  			}
   203  
   204  			cfg.V2.Contexts[name] = &context
   205  			return cfg.Write()
   206  		}),
   207  	}
   208  	setContext.Flags().BoolVar(&overwrite, "overwrite", false, "Overwrite a context if it already exists.")
   209  	setContext.Flags().StringVarP(&kubeContextName, "kubernetes", "k", "", "Import a given kubernetes context's values into the Pachyderm context.")
   210  	shell.RegisterCompletionFunc(setContext, contextCompletion)
   211  	commands = append(commands, cmdutil.CreateAlias(setContext, "config set context"))
   212  
   213  	var pachdAddress string
   214  	var clusterName string
   215  	var authInfo string
   216  	var serverCAs string
   217  	var namespace string
   218  	var removeClusterDeploymentID bool
   219  	var updateContext *cobra.Command // standalone declaration so Run() can refer
   220  	updateContext = &cobra.Command{
   221  		Use:   "{{alias}} [<context>]",
   222  		Short: "Updates a context.",
   223  		Long: "Updates an existing context config from a given name (or the " +
   224  			"currently-active context, if no name is given).",
   225  		Run: cmdutil.RunBoundedArgs(0, 1, func(args []string) (retErr error) {
   226  			cfg, err := config.Read(false, false)
   227  			if err != nil {
   228  				return err
   229  			}
   230  
   231  			var context *config.Context
   232  			if len(args) > 0 {
   233  				var ok bool
   234  				context, ok = cfg.V2.Contexts[args[0]]
   235  				if !ok {
   236  					return errors.Errorf("context does not exist: %s", args[0])
   237  				}
   238  			} else {
   239  				var name string
   240  				var err error
   241  				name, context, err = cfg.ActiveContext(true)
   242  				if err != nil {
   243  					return err
   244  				}
   245  				fmt.Printf("editing the currently active context %q\n", name)
   246  			}
   247  
   248  			// Use this method since we want to differentiate between no
   249  			// flag being set (the value shouldn't be changed) vs the flag
   250  			// being an empty string (meaning we want to set the value to an
   251  			// empty string)
   252  			if updateContext.Flags().Changed("pachd-address") {
   253  				parsedPachdAddress, err := grpcutil.ParsePachdAddress(pachdAddress)
   254  				if err != nil {
   255  					if errors.Is(err, grpcutil.ErrNoPachdAddress) {
   256  						context.PachdAddress = ""
   257  					} else {
   258  						return err
   259  					}
   260  				} else {
   261  					context.PachdAddress = parsedPachdAddress.Qualified()
   262  				}
   263  			}
   264  			if updateContext.Flags().Changed("cluster-name") {
   265  				context.ClusterName = clusterName
   266  			}
   267  			if updateContext.Flags().Changed("auth-info") {
   268  				context.AuthInfo = authInfo
   269  			}
   270  			if updateContext.Flags().Changed("server-cas") {
   271  				context.ServerCAs = serverCAs
   272  			}
   273  			if updateContext.Flags().Changed("namespace") {
   274  				context.Namespace = namespace
   275  			}
   276  			if removeClusterDeploymentID {
   277  				context.ClusterDeploymentID = ""
   278  			}
   279  
   280  			return cfg.Write()
   281  		}),
   282  	}
   283  	updateContext.Flags().StringVar(&pachdAddress, "pachd-address", "", "Set a new name pachd address.")
   284  	updateContext.Flags().StringVar(&clusterName, "cluster-name", "", "Set a new cluster name.")
   285  	updateContext.Flags().StringVar(&authInfo, "auth-info", "", "Set a new k8s auth info.")
   286  	updateContext.Flags().StringVar(&serverCAs, "server-cas", "", "Set new trusted CA certs.")
   287  	updateContext.Flags().StringVar(&namespace, "namespace", "", "Set a new namespace.")
   288  	updateContext.Flags().BoolVar(&removeClusterDeploymentID, "remove-cluster-deployment-id", false, "Remove the cluster deployment ID field, which will be repopulated on the next `pachctl` call using this context.")
   289  	shell.RegisterCompletionFunc(updateContext, contextCompletion)
   290  	commands = append(commands, cmdutil.CreateAlias(updateContext, "config update context"))
   291  
   292  	deleteContext := &cobra.Command{
   293  		Use:   "{{alias}} <context>",
   294  		Short: "Deletes a context.",
   295  		Long:  "Deletes a context.",
   296  		Run: cmdutil.RunFixedArgs(1, func(args []string) (retErr error) {
   297  			cfg, err := config.Read(false, false)
   298  			if err != nil {
   299  				return err
   300  			}
   301  			if _, ok := cfg.V2.Contexts[args[0]]; !ok {
   302  				return errors.Errorf("context does not exist: %s", args[0])
   303  			}
   304  			if cfg.V2.ActiveContext == args[0] {
   305  				return errors.New("cannot delete an active context")
   306  			}
   307  			delete(cfg.V2.Contexts, args[0])
   308  			return cfg.Write()
   309  		}),
   310  	}
   311  	shell.RegisterCompletionFunc(deleteContext, contextCompletion)
   312  	commands = append(commands, cmdutil.CreateAlias(deleteContext, "config delete context"))
   313  
   314  	listContext := &cobra.Command{
   315  		Short: "Lists contexts.",
   316  		Long:  "Lists contexts.",
   317  		Run: cmdutil.Run(func(args []string) (retErr error) {
   318  			cfg, err := config.Read(false, false)
   319  			if err != nil {
   320  				return err
   321  			}
   322  
   323  			keys := make([]string, len(cfg.V2.Contexts))
   324  			i := 0
   325  			for key := range cfg.V2.Contexts {
   326  				keys[i] = key
   327  				i++
   328  			}
   329  			sort.Strings(keys)
   330  
   331  			activeContext, _, err := cfg.ActiveContext(false)
   332  			if err != nil {
   333  				return err
   334  			}
   335  
   336  			fmt.Println(listContextHeader)
   337  			for _, key := range keys {
   338  				if key == activeContext {
   339  					fmt.Printf("*\t%s\n", key)
   340  				} else {
   341  					fmt.Printf("\t%s\n", key)
   342  				}
   343  			}
   344  			return nil
   345  		}),
   346  	}
   347  	commands = append(commands, cmdutil.CreateAlias(listContext, "config list context"))
   348  
   349  	configDocs := &cobra.Command{
   350  		Short: "Manages the pachyderm config.",
   351  		Long:  "Gets/sets pachyderm config values.",
   352  	}
   353  	commands = append(commands, cmdutil.CreateDocsAlias(configDocs, "config", " config "))
   354  
   355  	configGetRoot := &cobra.Command{
   356  		Short: "Commands for getting pachyderm config values",
   357  		Long:  "Commands for getting pachyderm config values",
   358  	}
   359  	commands = append(commands, cmdutil.CreateAlias(configGetRoot, "config get"))
   360  
   361  	configSetRoot := &cobra.Command{
   362  		Short: "Commands for setting pachyderm config values",
   363  		Long:  "Commands for setting pachyderm config values",
   364  	}
   365  	commands = append(commands, cmdutil.CreateAlias(configSetRoot, "config set"))
   366  
   367  	configUpdateRoot := &cobra.Command{
   368  		Short: "Commands for updating pachyderm config values",
   369  		Long:  "Commands for updating pachyderm config values",
   370  	}
   371  	commands = append(commands, cmdutil.CreateAlias(configUpdateRoot, "config update"))
   372  
   373  	configDeleteRoot := &cobra.Command{
   374  		Short: "Commands for deleting pachyderm config values",
   375  		Long:  "Commands for deleting pachyderm config values",
   376  	}
   377  	commands = append(commands, cmdutil.CreateAlias(configDeleteRoot, "config delete"))
   378  
   379  	configListRoot := &cobra.Command{
   380  		Short: "Commands for listing pachyderm config values",
   381  		Long:  "Commands for listing pachyderm config values",
   382  	}
   383  	commands = append(commands, cmdutil.CreateAlias(configListRoot, "config list"))
   384  
   385  	return commands
   386  }
   387  
   388  func contextCompletion(_, text string, maxCompletions int64) ([]prompt.Suggest, shell.CacheFunc) {
   389  	cfg, err := config.Read(false, false)
   390  	if err != nil {
   391  		log.Fatal(err)
   392  	}
   393  	activeContext, _, err := cfg.ActiveContext(false)
   394  	if err != nil {
   395  		log.Fatal(err)
   396  	}
   397  	var result []prompt.Suggest
   398  	for name, ctx := range cfg.V2.Contexts {
   399  		desc := ctx.PachdAddress
   400  		if name == activeContext {
   401  			desc += " (active)"
   402  		}
   403  		result = append(result, prompt.Suggest{
   404  			Text:        name,
   405  			Description: desc,
   406  		})
   407  	}
   408  	sort.Slice(result, func(i, j int) bool {
   409  		switch {
   410  		case result[i].Text == activeContext:
   411  			return true
   412  		case result[j].Text == activeContext:
   413  			return false
   414  		default:
   415  			return result[i].Text < result[j].Text
   416  		}
   417  	})
   418  	return result, shell.CacheAll
   419  }