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 }