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

     1  package cmd
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  
     8  	"github.com/ghodss/yaml"
     9  	"github.com/qri-io/ioes"
    10  	"github.com/qri-io/qri/base/params"
    11  	"github.com/qri-io/qri/config"
    12  	"github.com/qri-io/qri/lib"
    13  	"github.com/spf13/cobra"
    14  )
    15  
    16  // NewPeersCommand cerates a new `qri peers` cobra command
    17  func NewPeersCommand(f Factory, ioStreams ioes.IOStreams) *cobra.Command {
    18  	o := &PeersOptions{IOStreams: ioStreams}
    19  	cmd := &cobra.Command{
    20  		Use:   "peers",
    21  		Short: "commands for working with peers",
    22  		Long: `The ` + "`peers`" + ` commands interact with other peers on the Qri network. In
    23  order for these commands to work, you must be running a Qri node, which is
    24  responsible for peer-to-peer communication. To spin up a Qri node, run
    25  ` + "`qri connect`" + ` in a separate terminal. This connects you to the network
    26  until you choose to close the connection by ending the session or closing 
    27  the terminal.`,
    28  		Annotations: map[string]string{
    29  			"group": "network",
    30  		},
    31  	}
    32  
    33  	info := &cobra.Command{
    34  		Use:   "info PEER",
    35  		Short: `get info on a Qri peer`,
    36  		Long: `The peers info command returns a peer's profile information. The default
    37  format is yaml.
    38  
    39  Using the ` + "`--verbose`" + ` flag, you can also view a peer's network information.
    40  
    41  You must have ` + "`qri connect`" + ` running in another terminal.`,
    42  		Example: `  # Show info on a peer named "b5":
    43    $ qri peers info b5
    44  
    45    # Show info in json:
    46    $ qri peers info b5 --format json`,
    47  		Args: cobra.ExactArgs(1),
    48  		RunE: func(cmd *cobra.Command, args []string) error {
    49  			if err := o.Complete(f, args); err != nil {
    50  				return err
    51  			}
    52  			return o.Info()
    53  		},
    54  	}
    55  
    56  	info.Flags().BoolVarP(&o.Verbose, "verbose", "v", false, "show verbose profile info")
    57  	info.Flags().StringVarP(&o.Format, "format", "", "yaml", "output format. formats: yaml, json")
    58  
    59  	list := &cobra.Command{
    60  		Use:   "list",
    61  		Short: "list known qri peers",
    62  		Long: `Lists the peers to which your Qri node is connected. 
    63  
    64  You must have ` + "`qri connect`" + ` running in another terminal.
    65  
    66  To find peers that are not online, but to which your node has previously been 
    67  connected, use the ` + "`--cached`" + ` flag.`,
    68  		Example: `  # Spin up a Qri node:
    69    $ qri connect
    70  
    71    # Then in a separate terminal, to list qri peers:
    72    $ qri peers list
    73  
    74    # To ensure you get a cached version of the list:
    75    $ qri peers list --cached`,
    76  		Aliases: []string{"ls"},
    77  		Args:    cobra.NoArgs,
    78  		RunE: func(cmd *cobra.Command, args []string) error {
    79  			if err := o.Complete(f, args); err != nil {
    80  				return err
    81  			}
    82  			return o.List()
    83  		},
    84  	}
    85  
    86  	list.Flags().BoolVarP(&o.Cached, "cached", "c", false, "show peers that aren't online, but previously seen")
    87  	list.Flags().StringVarP(&o.Network, "network", "n", "", "specify network to show peers from (qri|ipfs) (defaults to qri)")
    88  	list.Flags().StringVarP(&o.Format, "format", "", "", "output format. formats: simple")
    89  	list.Flags().IntVar(&o.Offset, "offset", 0, "number of peers to skip from the results, default 0")
    90  	list.Flags().IntVar(&o.Limit, "limit", 200, "max number of peers to show, default 200")
    91  
    92  	connect := &cobra.Command{
    93  		Use:   "connect (NAME|ADDRESS)",
    94  		Short: "connect to a peer",
    95  		Long: `Connect to a peer using a peername, peer ID, or multiaddress. Qri will use this name, id, or address
    96  to find a peer to which it has not automatically connected. 
    97  
    98  You must have a Qri node running (` + "`qri connect`" + `) in a separate terminal. You will only be able 
    99  to connect to a peer that also has spun up its own Qri node.
   100  
   101  A multiaddress, or multiaddr, is the most specific way to refer to a peer's location, and is therefore
   102  the most sure-fire way to connect to a peer.`,
   103  		Example: `  # Spin up a Qri node:
   104    $ qri connect
   105  
   106    # In a separate terminal, connect to a specific peer:
   107    $ qri peers connect /ip4/192.168.0.194/tcp/4001/ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn`,
   108  		Args: cobra.ExactArgs(1),
   109  		RunE: func(cmd *cobra.Command, args []string) error {
   110  			if err := o.Complete(f, args); err != nil {
   111  				return err
   112  			}
   113  			return o.Connect()
   114  		},
   115  	}
   116  
   117  	disconnect := &cobra.Command{
   118  		Use:   "disconnect (NAME|ADDRESS)",
   119  		Short: "explicitly close a connection to a peer",
   120  		Args:  cobra.ExactArgs(1),
   121  		Long: `Explicitly close a connection to a peer using a peername, peer id, or multiaddress. 
   122  
   123  You can close all connections to the Qri network by ending your Qri node session. 
   124  
   125  Use the disconnect command when you want to stay connected to the network, but want to 
   126  close your connection to a specific peer. This could be because that connection is hung,
   127  the connection is pulling too many resources, or because you simply no longer need an
   128  explicit connection.  This is not the same as blocking a peer or connection.
   129  
   130  Once you close a connection to a peer, you or that peer can immediately open another 
   131  connection.
   132  
   133  You must have ` + "`qri connect`" + ` running in another terminal.`,
   134  		Example: `  # Disconnect from a peer using a multiaddr:
   135    $ qri peers disconnect /ip4/192.168.0.194/tcp/4001/ipfs/QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn`,
   136  		RunE: func(cmd *cobra.Command, args []string) error {
   137  			if err := o.Complete(f, args); err != nil {
   138  				return err
   139  			}
   140  			return o.Disconnect()
   141  		},
   142  	}
   143  
   144  	cmd.AddCommand(info, list, connect, disconnect)
   145  
   146  	return cmd
   147  }
   148  
   149  // PeersOptions encapsulates state for the peers command
   150  type PeersOptions struct {
   151  	ioes.IOStreams
   152  
   153  	Peername string
   154  	Verbose  bool
   155  	Format   string
   156  	Cached   bool
   157  	Network  string
   158  	Offset   int
   159  	Limit    int
   160  
   161  	UsingRPC bool
   162  	Instance *lib.Instance
   163  }
   164  
   165  // Complete adds any missing configuration that can only be added just before calling Run
   166  func (o *PeersOptions) Complete(f Factory, args []string) (err error) {
   167  	if o.Instance, err = f.Instance(); err != nil {
   168  		return err
   169  	}
   170  	if len(args) > 0 {
   171  		o.Peername = args[0]
   172  	}
   173  	o.UsingRPC = f.HTTPClient() != nil
   174  	return
   175  }
   176  
   177  // Info gets peer info
   178  func (o *PeersOptions) Info() (err error) {
   179  	if !(o.Format == "yaml" || o.Format == "json") {
   180  		return fmt.Errorf("format must be either `yaml` or `json`")
   181  	}
   182  
   183  	var data []byte
   184  
   185  	p := &lib.PeerInfoParams{
   186  		Peername: o.Peername,
   187  		Verbose:  o.Verbose,
   188  	}
   189  
   190  	ctx := context.TODO()
   191  	res, err := o.Instance.Peer().Info(ctx, p)
   192  	if err != nil {
   193  		return err
   194  	}
   195  
   196  	switch o.Format {
   197  	case "json":
   198  		if data, err = json.MarshalIndent(res, "", "  "); err != nil {
   199  			return err
   200  		}
   201  	case "yaml":
   202  		if data, err = yaml.Marshal(res); err != nil {
   203  			return err
   204  		}
   205  	}
   206  
   207  	printInfo(o.Out, "\n"+string(data)+"\n")
   208  	return
   209  }
   210  
   211  // List shows a list of peers
   212  func (o *PeersOptions) List() (err error) {
   213  
   214  	res := []*config.ProfilePod{}
   215  	ctx := context.TODO()
   216  
   217  	if o.Network == "ipfs" {
   218  		params := &lib.ConnectionsParams{List: params.List{Limit: o.Limit}}
   219  		res, err = o.Instance.Peer().ConnectedQriProfiles(ctx, params)
   220  		if err != nil {
   221  			return err
   222  		}
   223  	} else {
   224  		// if we don't have an RPC client, assume we're not connected
   225  		if !o.UsingRPC && !o.Cached {
   226  			printInfo(o.ErrOut, "qri not connected, listing cached peers")
   227  			o.Cached = true
   228  		}
   229  
   230  		p := &lib.PeerListParams{
   231  			List: params.List{
   232  				Limit:  o.Limit,
   233  				Offset: o.Offset,
   234  			},
   235  			Cached: o.Cached,
   236  		}
   237  		res, err = o.Instance.Peer().List(ctx, p)
   238  		if err != nil {
   239  			return err
   240  		}
   241  	}
   242  
   243  	items := make([]fmt.Stringer, len(res))
   244  	peerNames := make([]string, len(res))
   245  	for i, p := range res {
   246  		items[i] = peerStringer(*p)
   247  		peerNames[i] = p.Peername
   248  	}
   249  
   250  	if o.Format == "simple" {
   251  		printlnStringItems(o.Out, peerNames)
   252  	} else {
   253  		printItems(o.Out, items, o.Offset)
   254  	}
   255  	return
   256  }
   257  
   258  // Connect attempts to connect to a peer
   259  func (o *PeersOptions) Connect() (err error) {
   260  	pcpod := lib.NewConnectParamsPod(o.Peername)
   261  	ctx := context.TODO()
   262  	res, err := o.Instance.Peer().Connect(ctx, pcpod)
   263  	if err != nil {
   264  		return err
   265  	}
   266  
   267  	printSuccess(o.Out, "successfully connected to %s:\n", res.Peername)
   268  	peer := peerStringer(*res)
   269  	fmt.Fprint(o.Out, peer.String())
   270  	return nil
   271  }
   272  
   273  // Disconnect attempts to disconnect from a peer
   274  func (o *PeersOptions) Disconnect() (err error) {
   275  	pcpod := lib.NewConnectParamsPod(o.Peername)
   276  	ctx := context.TODO()
   277  	if err = o.Instance.Peer().Disconnect(ctx, pcpod); err != nil {
   278  		return err
   279  	}
   280  
   281  	printSuccess(o.Out, "disconnected")
   282  	return nil
   283  }