Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0fe6fe2

Browse files
committedAug 22, 2015
check for API -- WIP
This is a WIP to make the ipfs cli tool check for a running daemon via the API, and make sure the APIs match. I ran into some complexity i need to think more about before attepting to fix... anyone feel free to pick this up! License: MIT Signed-off-by: Juan Batiz-Benet <juan@benet.ai>
1 parent 27e6840 commit 0fe6fe2

File tree

2 files changed

+105
-6
lines changed

2 files changed

+105
-6
lines changed
 

‎cmd/ipfs/main.go

+77-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"io"
8+
"io/ioutil"
89
"math/rand"
910
"os"
1011
"os/signal"
@@ -23,6 +24,7 @@ import (
2324
cmdsCli "github.com/ipfs/go-ipfs/commands/cli"
2425
cmdsHttp "github.com/ipfs/go-ipfs/commands/http"
2526
core "github.com/ipfs/go-ipfs/core"
27+
coreCmds "github.com/ipfs/go-ipfs/core/commands"
2628
config "github.com/ipfs/go-ipfs/repo/config"
2729
fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
2830
eventlog "github.com/ipfs/go-ipfs/thirdparty/eventlog"
@@ -32,8 +34,12 @@ import (
3234
// log is the command logger
3335
var log = eventlog.Logger("cmd/ipfs")
3436

35-
// signal to output help
36-
var errHelpRequested = errors.New("Help Requested")
37+
var (
38+
errHelpRequested = errors.New("Help Requested")
39+
errApiNotRunning = errors.New("api not running")
40+
errUnexpectedApiOutput = errors.New("api returned unexpected output")
41+
errApiVersionMismatch = errors.New("api version mismatch")
42+
)
3743

3844
const (
3945
EnvEnableProfiling = "IPFS_PROF"
@@ -293,6 +299,9 @@ func callCommand(ctx context.Context, req cmds.Request, root *cmds.Command, cmd
293299
}
294300

295301
log.Debug("looking for running daemon...")
302+
303+
getApiClient(repoPath, apiAddrStr)
304+
296305
useDaemon, err := commandShouldRunOnDaemon(*details, req, root)
297306
if err != nil {
298307
return nil, err
@@ -571,3 +580,69 @@ func profileIfEnabled() (func(), error) {
571580
}
572581
return func() {}, nil
573582
}
583+
584+
// getApiClient checks the repo, and the given options, checking for
585+
// a running API service. if there is one, it returns a client.
586+
// otherwise, it returns errApiNotRunning, or another error.
587+
func getApiClient(repoPath, apiAddrStr string) (*cmdsHttp.Client, error) {
588+
// parse given option
589+
if apiAddrStr != "" {
590+
addr, err = ma.NewMultiaddr(apiAddrStr)
591+
} else { // get opt from file.
592+
addr, err = fsrepo.APIAddr(repoPath)
593+
}
594+
if err != nil {
595+
return nil, err
596+
}
597+
598+
client, err := apiClientForAddr(addr)
599+
if err != nil {
600+
return nil, err
601+
}
602+
603+
// make sure the api is actually running.
604+
// this is slow, as it might mean an RTT to a remote server.
605+
// TODO: optimize some way
606+
if err := checkApiVersionMatch(client); err != nil {
607+
return nil, err
608+
}
609+
610+
return client, nil
611+
}
612+
613+
// apiVersionMatches checks whether the api server is running the
614+
// same version of go-ipfs. for now, only the exact same version of
615+
// client + server work. In the future, we should use semver for
616+
// proper API versioning! \o/
617+
func apiVersionMatches(client *cmdsHttp.Client) (err error) {
618+
cmd := coreCmds.VersionCmd
619+
req, err := cmds.NewRequest([]string{"version"}, nil, nil, nil, cmd, nil)
620+
if err != nil {
621+
return err
622+
}
623+
624+
res, err := client.Send(req)
625+
if err != nil {
626+
return err
627+
}
628+
629+
ver, ok := res.Output().(*coreCmds.VersionOutput)
630+
if !ok {
631+
return errApiOutputErr
632+
}
633+
634+
currv := config.CurrentVersionNumber
635+
if ver.Version != currv {
636+
return fmt.Errorf("%s (%s != %s)", errApiVersionMismatch, ver.Version, currv)
637+
}
638+
return nil
639+
}
640+
641+
func apiClientForAddr(addr ma.Multiaddr) (*cmdsHttp.Client, error) {
642+
_, host, err := manet.DialArgs(addr)
643+
if err != nil {
644+
return false, err
645+
}
646+
647+
return cmdsHttp.NewClient(host), nil
648+
}

‎repo/fsrepo/fsrepo.go

+28-4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
levelds "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore/leveldb"
1616
"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore/measure"
1717
"github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore/mount"
18+
ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
1819
ldbopts "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/syndtr/goleveldb/leveldb/opt"
1920
repo "github.com/ipfs/go-ipfs/repo"
2021
"github.com/ipfs/go-ipfs/repo/common"
@@ -58,6 +59,7 @@ func (err NoRepoError) Error() string {
5859
const (
5960
leveldbDirectory = "datastore"
6061
flatfsDirectory = "blocks"
62+
apiFile = "api"
6163
)
6264

6365
var (
@@ -285,14 +287,36 @@ func Remove(repoPath string) error {
285287
// process. If true, then the repo cannot be opened by this process.
286288
func LockedByOtherProcess(repoPath string) (bool, error) {
287289
repoPath = path.Clean(repoPath)
288-
289-
// TODO replace this with the "api" file
290-
// https://github.com/ipfs/specs/tree/master/repo/fs-repo
291-
292290
// NB: the lock is only held when repos are Open
293291
return lockfile.Locked(repoPath)
294292
}
295293

294+
// APIAddr returns the registered API addr, according to the api file
295+
// in the fsrepo. This is a concurrent operation, meaning that any
296+
// process may read this file. modifying this file, therefore, should
297+
// use "mv" to replace the whole file and avoid interleaved read/writes.
298+
func APIAddr(repoPath string) (ma.Multiaddr, error) {
299+
repoPath = path.Clean(repoPath)
300+
apiFilePath := path.Join(repoPath, apiFile)
301+
302+
// if there is no file, assume there is no api addr.
303+
f, err := os.Open(apiFilePath)
304+
if err != nil {
305+
return "", err
306+
}
307+
defer f.Close()
308+
309+
// read up to 2048 bytes. io.ReadAll is a vulnerability, as
310+
// someone could hose the process by putting a massive file there.
311+
buf := make([]byte, 0, 2048)
312+
n, err := f.Read(buf)
313+
if err != nil && err != io.EOF {
314+
return "", err
315+
}
316+
317+
return ma.NewMultiaddr(string(buf[:n]))
318+
}
319+
296320
// openConfig returns an error if the config file is not present.
297321
func (r *FSRepo) openConfig() error {
298322
configFilename, err := config.Filename(r.path)

0 commit comments

Comments
 (0)
Please sign in to comment.