1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-09-10 11:52:21 +08:00

implement support for --api option

This commit adds support for the --api option, which allows users
to specify an API endpoint to run the cli command against. It enables
much easier control of remote daemons.

It also
- ensures the API server version matches the API client
- implements support for the $IPFS_PATH/api file

Still TODO:
- tests!
- multiaddr to support /dns/

License: MIT
Signed-off-by: Juan Batiz-Benet <juan@benet.ai>
This commit is contained in:
Juan Batiz-Benet
2015-08-27 07:28:27 +02:00
parent 7abebb1653
commit 5040fee906
6 changed files with 213 additions and 50 deletions

View File

@ -23,6 +23,8 @@ import (
cmdsCli "github.com/ipfs/go-ipfs/commands/cli"
cmdsHttp "github.com/ipfs/go-ipfs/commands/http"
core "github.com/ipfs/go-ipfs/core"
coreCmds "github.com/ipfs/go-ipfs/core/commands"
repo "github.com/ipfs/go-ipfs/repo"
config "github.com/ipfs/go-ipfs/repo/config"
fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
eventlog "github.com/ipfs/go-ipfs/thirdparty/eventlog"
@ -32,8 +34,10 @@ import (
// log is the command logger
var log = eventlog.Logger("cmd/ipfs")
// signal to output help
var errHelpRequested = errors.New("Help Requested")
var (
errUnexpectedApiOutput = errors.New("api returned unexpected output")
errApiVersionMismatch = errors.New("api version mismatch")
)
const (
EnvEnableProfiling = "IPFS_PROF"
@ -292,8 +296,7 @@ func callCommand(ctx context.Context, req cmds.Request, root *cmds.Command, cmd
return nil, err
}
log.Debug("looking for running daemon...")
useDaemon, err := commandShouldRunOnDaemon(*details, req, root)
client, err := commandShouldRunOnDaemon(*details, req, root)
if err != nil {
return nil, err
}
@ -310,28 +313,13 @@ func callCommand(ctx context.Context, req cmds.Request, root *cmds.Command, cmd
}
}
if useDaemon {
cfg, err := req.InvocContext().GetConfig()
if err != nil {
return nil, err
}
addr, err := ma.NewMultiaddr(cfg.Addresses.API)
if err != nil {
return nil, err
}
log.Infof("Executing command on daemon running at %s", addr)
_, host, err := manet.DialArgs(addr)
if err != nil {
return nil, err
}
client := cmdsHttp.NewClient(host)
if client != nil {
log.Debug("Executing command via API")
res, err = client.Send(req)
if err != nil {
if isConnRefused(err) {
err = repo.ErrApiNotRunning
}
return nil, err
}
@ -380,48 +368,67 @@ func commandDetails(path []string, root *cmds.Command) (*cmdDetails, error) {
// commandShouldRunOnDaemon determines, from commmand details, whether a
// command ought to be executed on an IPFS daemon.
//
// It returns true if the command should be executed on a daemon and false if
// It returns a client if the command should be executed on a daemon and nil if
// it should be executed on a client. It returns an error if the command must
// NOT be executed on either.
func commandShouldRunOnDaemon(details cmdDetails, req cmds.Request, root *cmds.Command) (bool, error) {
func commandShouldRunOnDaemon(details cmdDetails, req cmds.Request, root *cmds.Command) (cmdsHttp.Client, error) {
path := req.Path()
// root command.
if len(path) < 1 {
return false, nil
return nil, nil
}
if details.cannotRunOnClient && details.cannotRunOnDaemon {
return false, fmt.Errorf("command disabled: %s", path[0])
return nil, fmt.Errorf("command disabled: %s", path[0])
}
if details.doesNotUseRepo && details.canRunOnClient() {
return false, nil
return nil, nil
}
// at this point need to know whether daemon is running. we defer
// to this point so that some commands dont open files unnecessarily.
daemonLocked, err := fsrepo.LockedByOtherProcess(req.InvocContext().ConfigRoot)
// at this point need to know whether api is running. we defer
// to this point so that we dont check unnecessarily
// did user specify an api to use for this command?
apiAddrStr, _, err := req.Option(coreCmds.ApiOption).String()
if err != nil {
return false, err
return nil, err
}
if daemonLocked {
log.Info("a daemon is running...")
if details.cannotRunOnDaemon {
e := "ipfs daemon is running. please stop it to run this command"
return false, cmds.ClientError(e)
client, err := getApiClient(req.InvocContext().ConfigRoot, apiAddrStr)
if err == repo.ErrApiNotRunning {
if apiAddrStr != "" && req.Command() != daemonCmd {
// if user SPECIFIED an api, and this cmd is not daemon
// we MUST use it. so error out.
return nil, err
}
return true, nil
// ok for api not to be running
} else if err != nil { // some other api error
return nil, err
}
if client != nil { // daemon is running
if details.cannotRunOnDaemon {
e := "cannot use API with this command."
// check if daemon locked. legacy error text, for now.
daemonLocked, _ := fsrepo.LockedByOtherProcess(req.InvocContext().ConfigRoot)
if daemonLocked {
e = "ipfs daemon is running. please stop it to run this command"
}
return nil, cmds.ClientError(e)
}
return client, nil
}
if details.cannotRunOnClient {
return false, cmds.ClientError("must run on the ipfs daemon")
return nil, cmds.ClientError("must run on the ipfs daemon")
}
return false, nil
return nil, nil
}
func isClientError(err error) bool {
@ -571,3 +578,92 @@ func profileIfEnabled() (func(), error) {
}
return func() {}, nil
}
// getApiClient checks the repo, and the given options, checking for
// a running API service. if there is one, it returns a client.
// otherwise, it returns errApiNotRunning, or another error.
func getApiClient(repoPath, apiAddrStr string) (cmdsHttp.Client, error) {
if apiAddrStr == "" {
var err error
if apiAddrStr, err = fsrepo.APIAddr(repoPath); err != nil {
return nil, err
}
}
addr, err := ma.NewMultiaddr(apiAddrStr)
if err != nil {
return nil, err
}
client, err := apiClientForAddr(addr)
if err != nil {
return nil, err
}
// make sure the api is actually running.
// this is slow, as it might mean an RTT to a remote server.
// TODO: optimize some way
if err := apiVersionMatches(client); err != nil {
return nil, err
}
return client, nil
}
// apiVersionMatches checks whether the api server is running the
// same version of go-ipfs. for now, only the exact same version of
// client + server work. In the future, we should use semver for
// proper API versioning! \o/
func apiVersionMatches(client cmdsHttp.Client) (err error) {
ver, err := doVersionRequest(client)
if err != nil {
return err
}
currv := config.CurrentVersionNumber
if ver.Version != currv {
return fmt.Errorf("%s (%s != %s)", errApiVersionMismatch, ver.Version, currv)
}
return nil
}
func doVersionRequest(client cmdsHttp.Client) (*coreCmds.VersionOutput, error) {
cmd := coreCmds.VersionCmd
optDefs, err := cmd.GetOptions([]string{})
if err != nil {
return nil, err
}
req, err := cmds.NewRequest([]string{"version"}, nil, nil, nil, cmd, optDefs)
if err != nil {
return nil, err
}
res, err := client.Send(req)
if err != nil {
if isConnRefused(err) {
err = repo.ErrApiNotRunning
}
return nil, err
}
ver, ok := res.Output().(*coreCmds.VersionOutput)
if !ok {
return nil, errUnexpectedApiOutput
}
return ver, nil
}
func apiClientForAddr(addr ma.Multiaddr) (cmdsHttp.Client, error) {
_, host, err := manet.DialArgs(addr)
if err != nil {
return nil, err
}
return cmdsHttp.NewClient(host), nil
}
func isConnRefused(err error) bool {
return strings.Contains(err.Error(), "connection refused")
}