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

import (
4
	"bytes"
5 6 7
	"encoding/base64"
	"encoding/json"
	"errors"
8
	"io"
9
	"strings"
10

11
	b58 "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-base58"
12

13 14 15 16 17 18 19
	cmds "github.com/ipfs/go-ipfs/commands"
	core "github.com/ipfs/go-ipfs/core"
	ic "github.com/ipfs/go-ipfs/p2p/crypto"
	"github.com/ipfs/go-ipfs/p2p/peer"
	identify "github.com/ipfs/go-ipfs/p2p/protocol/identify"
	kb "github.com/ipfs/go-ipfs/routing/kbucket"
	u "github.com/ipfs/go-ipfs/util"
20 21
)

22 23 24 25 26 27
const offlineIdErrorMessage = `ID command fails when run without daemon, we are working to fix this.
In the meantime, please run the daemon if you want to use 'ipfs id':

    ipfs daemon &
    ipfs id QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ
`
Jeromy's avatar
Jeromy committed
28

29 30 31 32 33 34 35 36
type IdOutput struct {
	ID              string
	PublicKey       string
	Addresses       []string
	AgentVersion    string
	ProtocolVersion string
}

37
var IDCmd = &cmds.Command{
38
	Helptext: cmds.HelpText{
Jeromy's avatar
Jeromy committed
39 40 41 42
		Tagline: "Show IPFS Node ID info",
		ShortDescription: `
Prints out information about the specified peer,
if no peer is specified, prints out local peers info.
Jeromy's avatar
Jeromy committed
43 44 45 46 47 48

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
49
<addrs>: addresses (newline delimited)
Jeromy's avatar
Jeromy committed
50
`,
51
	},
52
	Arguments: []cmds.Argument{
53
		cmds.StringArg("peerid", false, false, "peer.ID of node to look up").EnableStdin(),
54
	},
55
	Options: []cmds.Option{
rht's avatar
rht committed
56
		cmds.StringOption("format", "f", "optional output format"),
57
	},
58
	Run: func(req cmds.Request, res cmds.Response) {
Jeromy's avatar
Jeromy committed
59
		node, err := req.InvocContext().GetNode()
60
		if err != nil {
61 62
			res.SetError(err, cmds.ErrNormal)
			return
63 64
		}

Jeromy's avatar
Jeromy committed
65 66 67 68 69 70 71 72 73 74 75 76
		var id peer.ID
		if len(req.Arguments()) > 0 {
			id = peer.ID(b58.Decode(req.Arguments()[0]))
			if len(id) == 0 {
				res.SetError(cmds.ClientError("Invalid peer id"), cmds.ErrClient)
				return
			}
		} else {
			id = node.Identity
		}

		if id == node.Identity {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
77
			output, err := printSelf(node)
78 79 80 81 82 83
			if err != nil {
				res.SetError(err, cmds.ErrNormal)
				return
			}
			res.SetOutput(output)
			return
84 85
		}

86 87
		// TODO handle offline mode with polymorphism instead of conditionals
		if !node.OnlineMode() {
88 89
			res.SetError(errors.New(offlineIdErrorMessage), cmds.ErrClient)
			return
90 91
		}

Jeromy's avatar
Jeromy committed
92
		p, err := node.Routing.FindPeer(req.Context(), id)
93
		if err == kb.ErrLookupFailure {
94 95
			res.SetError(errors.New(offlineIdErrorMessage), cmds.ErrClient)
			return
96 97
		}
		if err != nil {
98 99
			res.SetError(err, cmds.ErrNormal)
			return
100
		}
101 102 103 104 105 106 107

		output, err := printPeer(node.Peerstore, p.ID)
		if err != nil {
			res.SetError(err, cmds.ErrNormal)
			return
		}
		res.SetOutput(output)
108 109
	},
	Marshalers: cmds.MarshalerMap{
110
		cmds.Text: func(res cmds.Response) (io.Reader, error) {
111 112 113 114 115
			val, ok := res.Output().(*IdOutput)
			if !ok {
				return nil, u.ErrCast()
			}

116
			format, found, err := res.Request().Option("format").String()
117 118 119
			if err != nil {
				return nil, err
			}
120 121 122 123 124 125
			if found {
				output := format
				output = strings.Replace(output, "<id>", val.ID, -1)
				output = strings.Replace(output, "<aver>", val.AgentVersion, -1)
				output = strings.Replace(output, "<pver>", val.ProtocolVersion, -1)
				output = strings.Replace(output, "<pubkey>", val.PublicKey, -1)
126 127 128
				output = strings.Replace(output, "<addrs>", strings.Join(val.Addresses, "\n"), -1)
				output = strings.Replace(output, "\\n", "\n", -1)
				output = strings.Replace(output, "\\t", "\t", -1)
129 130 131 132 133 134 135 136 137
				return strings.NewReader(output), nil
			} else {

				marshaled, err := json.MarshalIndent(val, "", "\t")
				if err != nil {
					return nil, err
				}
				return bytes.NewReader(marshaled), nil
			}
138 139
		},
	},
140
	Type: IdOutput{},
141 142
}

143 144
func printPeer(ps peer.Peerstore, p peer.ID) (interface{}, error) {
	if p == "" {
145 146
		return nil, errors.New("Attempted to print nil peer!")
	}
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
	for _, a := range ps.Addrs(p) {
160 161 162
		info.Addresses = append(info.Addresses, a.String())
	}

163 164
	if v, err := ps.Get(p, "ProtocolVersion"); err == nil {
		if vs, ok := v.(string); ok {
165
			info.ProtocolVersion = vs
166 167 168 169
		}
	}
	if v, err := ps.Get(p, "AgentVersion"); err == nil {
		if vs, ok := v.(string); ok {
170
			info.AgentVersion = vs
171 172
		}
	}
173 174 175

	return info, nil
}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204

// 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()

	if node.PrivateKey == nil {
		if err := node.LoadPrivateKey(); err != nil {
			return nil, err
		}
	}

	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 {
		for _, a := range node.PeerHost.Addrs() {
			s := a.String() + "/ipfs/" + info.ID
			info.Addresses = append(info.Addresses, s)
		}
	}
	info.ProtocolVersion = identify.IpfsVersion
	info.AgentVersion = identify.ClientVersion
	return info, nil
}