id.go 5.14 KB
Newer Older
1 2 3 4 5 6
package commands

import (
	"encoding/base64"
	"encoding/json"
	"errors"
7
	"fmt"
8
	"io"
9
	"strings"
10

Steven Allen's avatar
Steven Allen committed
11
	version "github.com/ipfs/go-ipfs"
12
	core "github.com/ipfs/go-ipfs/core"
13
	cmdenv "github.com/ipfs/go-ipfs/core/commands/cmdenv"
Jeromy's avatar
Jeromy committed
14

Jakub Sztandera's avatar
Jakub Sztandera committed
15
	cmds "github.com/ipfs/go-ipfs-cmds"
Raúl Kripalani's avatar
Raúl Kripalani committed
16
	ic "github.com/libp2p/go-libp2p-core/crypto"
Steven Allen's avatar
Steven Allen committed
17
	"github.com/libp2p/go-libp2p-core/host"
Raúl Kripalani's avatar
Raúl Kripalani committed
18 19
	peer "github.com/libp2p/go-libp2p-core/peer"
	pstore "github.com/libp2p/go-libp2p-core/peerstore"
Jakub Sztandera's avatar
Jakub Sztandera committed
20 21
	kb "github.com/libp2p/go-libp2p-kbucket"
	identify "github.com/libp2p/go-libp2p/p2p/protocol/identify"
22 23
)

24 25 26 27 28 29 30 31 32
const offlineIdErrorMessage = `'ipfs id' currently cannot query information on remote
peers without a running daemon; we are working to fix this.
In the meantime, if you want to query remote peers using 'ipfs id',
please run the daemon:

    ipfs daemon &
    ipfs id QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
`

33 34 35 36 37 38 39 40
type IdOutput struct {
	ID              string
	PublicKey       string
	Addresses       []string
	AgentVersion    string
	ProtocolVersion string
}

Kejie Zhang's avatar
Kejie Zhang committed
41 42 43 44
const (
	formatOptionName = "format"
)

45
var IDCmd = &cmds.Command{
Steven Allen's avatar
Steven Allen committed
46
	Helptext: cmds.HelpText{
47
		Tagline: "Show ipfs node id info.",
Jeromy's avatar
Jeromy committed
48
		ShortDescription: `
Richard Littauer's avatar
Richard Littauer committed
49 50
Prints out information about the specified peer.
If no peer is specified, prints out information for local peers.
Jeromy's avatar
Jeromy committed
51

Richard Littauer's avatar
Richard Littauer committed
52 53 54 55 56 57
'ipfs id' supports the format option for output with the following keys:
<id> : The peers id.
<aver>: Agent version.
<pver>: Protocol version.
<pubkey>: Public key.
<addrs>: Addresses (newline delimited).
58 59 60 61

EXAMPLE:

    ipfs id Qmece2RkXhsKe5CRooNisBTh4SK119KrXXGmoK6V3kb8aH -f="<addrs>\n"
Jeromy's avatar
Jeromy committed
62
`,
63
	},
Steven Allen's avatar
Steven Allen committed
64 65
	Arguments: []cmds.Argument{
		cmds.StringArg("peerid", false, false, "Peer.ID of node to look up."),
66
	},
Steven Allen's avatar
Steven Allen committed
67 68
	Options: []cmds.Option{
		cmds.StringOption(formatOptionName, "f", "Optional output format."),
69
	},
70 71
	Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
		n, err := cmdenv.GetNode(env)
72
		if err != nil {
73
			return err
74 75
		}

Jeromy's avatar
Jeromy committed
76
		var id peer.ID
77
		if len(req.Arguments) > 0 {
Steven Allen's avatar
Steven Allen committed
78
			var err error
79
			id, err = peer.Decode(req.Arguments[0])
Steven Allen's avatar
Steven Allen committed
80
			if err != nil {
81
				return fmt.Errorf("invalid peer id")
Jeromy's avatar
Jeromy committed
82 83
			}
		} else {
84
			id = n.Identity
Jeromy's avatar
Jeromy committed
85 86
		}

87 88
		if id == n.Identity {
			output, err := printSelf(n)
89
			if err != nil {
90
				return err
91
			}
92
			return cmds.EmitOnce(res, output)
93 94
		}

95
		// TODO handle offline mode with polymorphism instead of conditionals
96
		if !n.IsOnline {
97
			return errors.New(offlineIdErrorMessage)
98 99
		}

100 101 102 103 104
		// We need to actually connect to run identify.
		err = n.PeerHost.Connect(req.Context, peer.AddrInfo{ID: id})
		switch err {
		case nil:
		case kb.ErrLookupFailure:
105
			return errors.New(offlineIdErrorMessage)
106
		default:
107
			return err
108
		}
109

110
		output, err := printPeer(n.Peerstore, id)
111
		if err != nil {
112
			return err
113
		}
114
		return cmds.EmitOnce(res, output)
115
	},
116
	Encoders: cmds.EncoderMap{
117
		cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *IdOutput) error {
118
			format, found := req.Options[formatOptionName].(string)
119 120
			if found {
				output := format
121 122 123 124 125
				output = strings.Replace(output, "<id>", out.ID, -1)
				output = strings.Replace(output, "<aver>", out.AgentVersion, -1)
				output = strings.Replace(output, "<pver>", out.ProtocolVersion, -1)
				output = strings.Replace(output, "<pubkey>", out.PublicKey, -1)
				output = strings.Replace(output, "<addrs>", strings.Join(out.Addresses, "\n"), -1)
126 127
				output = strings.Replace(output, "\\n", "\n", -1)
				output = strings.Replace(output, "\\t", "\t", -1)
128
				fmt.Fprint(w, output)
129
			} else {
130
				marshaled, err := json.MarshalIndent(out, "", "\t")
131
				if err != nil {
132
					return err
133
				}
134
				marshaled = append(marshaled, byte('\n'))
135
				fmt.Fprintln(w, string(marshaled))
136
			}
137 138
			return nil
		}),
139
	},
140
	Type: IdOutput{},
141 142
}

Jeromy's avatar
Jeromy committed
143
func printPeer(ps pstore.Peerstore, p peer.ID) (interface{}, error) {
144
	if p == "" {
Łukasz Magiera's avatar
Łukasz Magiera committed
145
		return nil, errors.New("attempted to print nil peer")
146
	}
147

148
	info := new(IdOutput)
149
	info.ID = p.Pretty()
150

151 152
	if pk := ps.PubKey(p); pk != nil {
		pkb, err := ic.MarshalPublicKey(pk)
Jeromy's avatar
Jeromy committed
153 154 155 156
		if err != nil {
			return nil, err
		}
		info.PublicKey = base64.StdEncoding.EncodeToString(pkb)
157
	}
158

159 160 161 162 163 164 165
	addrInfo := ps.PeerInfo(p)
	addrs, err := peer.AddrInfoToP2pAddrs(&addrInfo)
	if err != nil {
		return nil, err
	}

	for _, a := range addrs {
166 167 168
		info.Addresses = append(info.Addresses, a.String())
	}

169 170
	if v, err := ps.Get(p, "ProtocolVersion"); err == nil {
		if vs, ok := v.(string); ok {
171
			info.ProtocolVersion = vs
172 173 174 175
		}
	}
	if v, err := ps.Get(p, "AgentVersion"); err == nil {
		if vs, ok := v.(string); ok {
176
			info.AgentVersion = vs
177 178
		}
	}
179 180 181

	return info, nil
}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
182 183 184 185 186 187 188 189 190 191 192 193 194 195

// printing self is special cased as we get values differently.
func printSelf(node *core.IpfsNode) (interface{}, error) {
	info := new(IdOutput)
	info.ID = node.Identity.Pretty()

	pk := node.PrivateKey.GetPublic()
	pkb, err := ic.MarshalPublicKey(pk)
	if err != nil {
		return nil, err
	}
	info.PublicKey = base64.StdEncoding.EncodeToString(pkb)

	if node.PeerHost != nil {
Steven Allen's avatar
Steven Allen committed
196 197 198 199 200 201
		addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(node.PeerHost))
		if err != nil {
			return nil, err
		}
		for _, a := range addrs {
			info.Addresses = append(info.Addresses, a.String())
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
202 203
		}
	}
204
	info.ProtocolVersion = identify.LibP2PVersion
Steven Allen's avatar
Steven Allen committed
205
	info.AgentVersion = version.UserAgent
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
206 207
	return info, nil
}