github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/cmd/config.go (about)

     1  package cmd
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"strings"
    10  
    11  	"github.com/qri-io/ioes"
    12  	"github.com/qri-io/qri/config"
    13  	"github.com/qri-io/qri/lib"
    14  	qhttp "github.com/qri-io/qri/lib/http"
    15  	"github.com/spf13/cobra"
    16  )
    17  
    18  const profilePrefix = "profile."
    19  
    20  // NewConfigCommand creates a new `qri config` cobra command
    21  // config represents commands that read & modify configuration settings
    22  func NewConfigCommand(f Factory, ioStreams ioes.IOStreams) *cobra.Command {
    23  	o := ConfigOptions{IOStreams: ioStreams}
    24  	cmd := &cobra.Command{
    25  		Use:   "config",
    26  		Short: "get and set local configuration information",
    27  		Annotations: map[string]string{
    28  			"group": "other",
    29  		},
    30  		Long: `'qri config' encapsulates all settings that control the behaviour of qri.
    31  This includes all kinds of stuff: your profile details; enabling & disabling 
    32  different services; what kind of output qri logs to; 
    33  which ports on qri serves on; etc.
    34  
    35  Configuration is stored as a .yaml file kept at $QRI_PATH, or provided at CLI 
    36  runtime via command a line argument.
    37  
    38  For details on each config field checkout: 
    39  https://github.com/qri-io/qri/blob/master/config/readme.md`,
    40  		Example: `  # Get your profile information:
    41    $ qri config get profile
    42  
    43    # Set your API port to 4444:
    44    $ qri config set api.port 4444
    45  
    46    # Disable RPC connections:
    47    $ qri config set rpc.enabled false`,
    48  	}
    49  
    50  	get := &cobra.Command{
    51  		Use:   "get [FIELD]",
    52  		Short: "get configuration settings",
    53  		Long: `'qri config get' outputs your current configuration file with private keys 
    54  removed by default, making it easier to share your qri configuration settings.
    55  
    56  You can get particular parts of the config by using dot notation to
    57  traverse the config object. For details on each config field checkout: 
    58  https://github.com/qri-io/qri/blob/master/config/readme.md
    59  
    60  The --with-private-keys option will show private keys.
    61  PLEASE PLEASE PLEASE NEVER SHARE YOUR PRIVATE KEYS WITH ANYONE. EVER.
    62  Anyone with your private keys can impersonate you on qri.`,
    63  		Example: `  # Get the entire config:
    64    $ qri config get
    65  
    66    # Get the config profile:
    67    $ qri config get profile
    68  
    69    # Get the profile description:
    70    $ qri config get profile.description`,
    71  		Args: cobra.MaximumNArgs(1),
    72  		RunE: func(cmd *cobra.Command, args []string) error {
    73  			if err := o.Complete(f); err != nil {
    74  				return err
    75  			}
    76  			return o.Get(args)
    77  		},
    78  	}
    79  
    80  	set := &cobra.Command{
    81  		Use:   "set FIELD VALUE [FIELD VALUE ...]",
    82  		Short: "set configuration options",
    83  		Long: `'qri config set' allows you to set configuration options. You can set 
    84  particular parts of the config by using dot notation to traverse the 
    85  config object. 
    86  
    87  While the 'qri config get' command allows you to view the whole config,
    88  or only parts of it, the 'qri config set' command is more specific.
    89  
    90  If the config object were a tree and each field a branch, you can only
    91  set the leaves of the branches. In other words, the you cannot set a 
    92  field that is itself an object or array. For details on each config 
    93  field checkout: https://github.com/qri-io/qri/blob/master/config/readme.md`,
    94  		Example: `  # Set a profile description:
    95    $ qri config set profile.description "This is my new description that I
    96    am very proud of and want displayed in my profile"
    97  
    98    # Disable rpc communication:
    99    $ qri config set rpc.enabled false`,
   100  		Args: func(cmd *cobra.Command, args []string) error {
   101  			if len(args)%2 != 0 {
   102  				return fmt.Errorf("wrong number of arguments. arguments must be in the form: [path value]")
   103  			} else if len(args) < 2 {
   104  				return fmt.Errorf("please provide at least one field-value pair to set")
   105  			}
   106  			return nil
   107  		},
   108  		RunE: func(cmd *cobra.Command, args []string) error {
   109  			cmd.SilenceUsage = true
   110  			if err := o.Complete(f); err != nil {
   111  				return err
   112  			}
   113  			return o.Set(args)
   114  		},
   115  	}
   116  
   117  	get.Flags().BoolVar(&o.WithPrivateKeys, "with-private-keys", false, "include private keys in export")
   118  	get.Flags().BoolVarP(&o.Concise, "concise", "c", false, "print output without indentation, only applies to json format")
   119  	get.Flags().StringVarP(&o.Format, "format", "f", "yaml", "data format to export. either json or yaml")
   120  	get.Flags().StringVarP(&o.Output, "output", "o", "", "path to export to")
   121  	cmd.AddCommand(get)
   122  	cmd.AddCommand(set)
   123  
   124  	return cmd
   125  }
   126  
   127  // ConfigOptions encapsulates state for the config command
   128  type ConfigOptions struct {
   129  	ioes.IOStreams
   130  
   131  	Format          string
   132  	WithPrivateKeys bool
   133  	Concise         bool
   134  	Output          string
   135  
   136  	inst *lib.Instance
   137  }
   138  
   139  // Complete adds any missing configuration that can only be added just before calling Run
   140  func (o *ConfigOptions) Complete(f Factory) (err error) {
   141  	o.inst, err = f.Instance()
   142  	return err
   143  }
   144  
   145  // Get a configuration option
   146  func (o *ConfigOptions) Get(args []string) (err error) {
   147  	params := &lib.GetConfigParams{
   148  		WithPrivateKey: o.WithPrivateKeys,
   149  		Format:         o.Format,
   150  		Concise:        o.Concise,
   151  	}
   152  
   153  	if len(args) == 1 {
   154  		params.Field = args[0]
   155  	}
   156  
   157  	ctx := context.TODO()
   158  
   159  	data, err := o.inst.Config().GetConfig(ctx, params)
   160  	if err != nil {
   161  		if errors.Is(err, qhttp.ErrUnsupportedRPC) {
   162  			return fmt.Errorf("%w - this could mean you're running qri connect in another terminal or application", err)
   163  		}
   164  		return err
   165  	}
   166  
   167  	if o.Output != "" {
   168  		if err = ioutil.WriteFile(o.Output, data, os.ModePerm); err != nil {
   169  			return err
   170  		}
   171  		printSuccess(o.Out, "config file written to: %s", o.Output)
   172  		return
   173  	}
   174  
   175  	fmt.Fprintln(o.Out, string(data))
   176  	return
   177  }
   178  
   179  // Set a configuration option
   180  func (o *ConfigOptions) Set(args []string) (err error) {
   181  	ip := config.ImmutablePaths()
   182  	photoPaths := map[string]bool{
   183  		"profile.photo":  true,
   184  		"profile.poster": true,
   185  		"profile.thumb":  true,
   186  	}
   187  
   188  	profile := o.inst.GetConfig().Profile
   189  	profileChanged := false
   190  	ctx := context.TODO()
   191  
   192  	for i := 0; i < len(args)-1; i = i + 2 {
   193  		path := strings.ToLower(args[i])
   194  		value := args[i+1]
   195  
   196  		if ip[path] {
   197  			ErrExit(o.ErrOut, fmt.Errorf("cannot set path %s", path))
   198  		}
   199  
   200  		if photoPaths[path] {
   201  			profileMethods := o.inst.Profile()
   202  			if err = setPhotoPath(ctx, &profileMethods, path, args[i+1]); err != nil {
   203  				if errors.Is(err, qhttp.ErrUnsupportedRPC) {
   204  					return fmt.Errorf("%w - this could mean you're running qri connect in another terminal or application", err)
   205  				}
   206  				return err
   207  			}
   208  		} else if strings.HasPrefix(path, profilePrefix) {
   209  			field := strings.ToLower(path[len(profilePrefix):])
   210  			if err = profile.SetField(field, args[i+1]); err != nil {
   211  				return err
   212  			}
   213  			profileChanged = true
   214  		} else {
   215  			// TODO (b5): I think this'll result in configuration not getting set. should investigate
   216  			if err = o.inst.GetConfig().Set(path, value); err != nil {
   217  				return err
   218  			}
   219  		}
   220  	}
   221  	if _, err := o.inst.Config().SetConfig(ctx, o.inst.GetConfig()); err != nil {
   222  		if errors.Is(err, qhttp.ErrUnsupportedRPC) {
   223  			return fmt.Errorf("%w - this could mean you're running qri connect in another terminal or application", err)
   224  		}
   225  		return err
   226  	}
   227  	if profileChanged {
   228  		if _, err = o.inst.Profile().SetProfile(ctx, &lib.SetProfileParams{Pro: profile}); err != nil {
   229  			if errors.Is(err, qhttp.ErrUnsupportedRPC) {
   230  				return fmt.Errorf("%w - this could mean you're running qri connect in another terminal or application", err)
   231  			}
   232  			return err
   233  		}
   234  	}
   235  
   236  	printSuccess(o.Out, "config updated")
   237  	return nil
   238  }
   239  
   240  func setPhotoPath(ctx context.Context, m *lib.ProfileMethods, proppath, filepath string) error {
   241  	p := &lib.FileParams{
   242  		Filename: filepath,
   243  	}
   244  
   245  	switch proppath {
   246  	case "profile.photo", "profile.thumb":
   247  		if _, err := m.SetProfilePhoto(ctx, p); err != nil {
   248  			return err
   249  		}
   250  	case "profile.poster":
   251  		if _, err := m.SetPosterPhoto(ctx, p); err != nil {
   252  			return err
   253  		}
   254  	default:
   255  		return fmt.Errorf("unrecognized path to set photo: %s", proppath)
   256  	}
   257  
   258  	return nil
   259  }