2018-08-23 12:12:55 +00:00
|
|
|
// Package client provides a Go Client for the IPFS Cluster API provided
|
|
|
|
// by the "api/rest" component. It supports both the HTTP(s) endpoint and
|
|
|
|
// the libp2p-http endpoint.
|
2017-12-05 20:44:22 +00:00
|
|
|
package client
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2017-12-08 14:38:30 +00:00
|
|
|
"fmt"
|
2017-12-05 20:44:22 +00:00
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
|
2018-08-30 23:14:06 +00:00
|
|
|
"github.com/ipfs/go-ipfs-cmdkit/files"
|
2018-10-01 01:53:26 +00:00
|
|
|
|
2018-08-30 23:14:06 +00:00
|
|
|
"github.com/ipfs/ipfs-cluster/api"
|
|
|
|
|
|
|
|
cid "github.com/ipfs/go-cid"
|
2018-03-21 12:25:32 +00:00
|
|
|
shell "github.com/ipfs/go-ipfs-api"
|
2017-12-05 20:44:22 +00:00
|
|
|
logging "github.com/ipfs/go-log"
|
2018-03-15 13:44:18 +00:00
|
|
|
host "github.com/libp2p/go-libp2p-host"
|
2018-08-30 23:14:06 +00:00
|
|
|
peer "github.com/libp2p/go-libp2p-peer"
|
2017-12-05 20:44:22 +00:00
|
|
|
ma "github.com/multiformats/go-multiaddr"
|
2017-12-08 14:38:30 +00:00
|
|
|
madns "github.com/multiformats/go-multiaddr-dns"
|
2017-12-05 20:44:22 +00:00
|
|
|
manet "github.com/multiformats/go-multiaddr-net"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Configuration defaults
|
|
|
|
var (
|
2018-08-08 19:11:26 +00:00
|
|
|
DefaultTimeout = 0
|
2018-03-21 12:25:32 +00:00
|
|
|
DefaultAPIAddr = "/ip4/127.0.0.1/tcp/9094"
|
|
|
|
DefaultLogLevel = "info"
|
|
|
|
DefaultProxyPort = 9095
|
2018-08-08 19:11:26 +00:00
|
|
|
ResolveTimeout = 30 * time.Second
|
2018-08-10 13:10:16 +00:00
|
|
|
DefaultPort = 9094
|
2017-12-05 20:44:22 +00:00
|
|
|
)
|
|
|
|
|
2017-12-06 23:04:51 +00:00
|
|
|
var loggingFacility = "apiclient"
|
|
|
|
var logger = logging.Logger(loggingFacility)
|
2017-12-05 20:44:22 +00:00
|
|
|
|
2018-08-30 23:14:06 +00:00
|
|
|
// Client interface defines the interface to be used by API clients to
|
|
|
|
// interact with the ipfs-cluster-service
|
|
|
|
type Client interface {
|
|
|
|
// ID returns information about the cluster Peer.
|
|
|
|
ID() (api.ID, error)
|
|
|
|
|
|
|
|
// Peers requests ID information for all cluster peers.
|
|
|
|
Peers() ([]api.ID, error)
|
|
|
|
// PeerAdd adds a new peer to the cluster.
|
|
|
|
PeerAdd(pid peer.ID) (api.ID, error)
|
|
|
|
// PeerRm removes a current peer from the cluster
|
2018-10-01 01:53:26 +00:00
|
|
|
PeerRm(pid peer.ID) error
|
2018-08-30 23:14:06 +00:00
|
|
|
|
|
|
|
// Add imports files to the cluster from the given paths.
|
|
|
|
Add(paths []string, params *api.AddParams, out chan<- *api.AddedOutput) error
|
|
|
|
// AddMultiFile imports new files from a MultiFileReader.
|
|
|
|
AddMultiFile(multiFileR *files.MultiFileReader, params *api.AddParams, out chan<- *api.AddedOutput) error
|
|
|
|
|
|
|
|
// Pin tracks a Cid with the given replication factor and a name for
|
|
|
|
// human-friendliness.
|
2018-10-01 01:53:26 +00:00
|
|
|
Pin(ci cid.Cid, replicationFactorMin, replicationFactorMax int, name string) error
|
2018-08-30 23:14:06 +00:00
|
|
|
// Unpin untracks a Cid from cluster.
|
2018-10-01 01:53:26 +00:00
|
|
|
Unpin(ci cid.Cid) error
|
2018-08-30 23:14:06 +00:00
|
|
|
|
|
|
|
// Allocations returns the consensus state listing all tracked items
|
|
|
|
// and the peers that should be pinning them.
|
|
|
|
Allocations(filter api.PinType) ([]api.Pin, error)
|
|
|
|
// Allocation returns the current allocations for a given Cid.
|
2018-10-01 01:53:26 +00:00
|
|
|
Allocation(ci cid.Cid) (api.Pin, error)
|
2018-08-30 23:14:06 +00:00
|
|
|
|
|
|
|
// Status returns the current ipfs state for a given Cid. If local is true,
|
|
|
|
// the information affects only the current peer, otherwise the information
|
|
|
|
// is fetched from all cluster peers.
|
2018-10-01 01:53:26 +00:00
|
|
|
Status(ci cid.Cid, local bool) (api.GlobalPinInfo, error)
|
2018-08-30 23:14:06 +00:00
|
|
|
// StatusAll gathers Status() for all tracked items.
|
|
|
|
StatusAll(local bool) ([]api.GlobalPinInfo, error)
|
|
|
|
|
|
|
|
// Sync makes sure the state of a Cid corresponds to the state reported
|
|
|
|
// by the ipfs daemon, and returns it. If local is true, this operation
|
|
|
|
// only happens on the current peer, otherwise it happens on every
|
|
|
|
// cluster peer.
|
2018-10-01 01:53:26 +00:00
|
|
|
Sync(ci cid.Cid, local bool) (api.GlobalPinInfo, error)
|
2018-08-30 23:14:06 +00:00
|
|
|
// SyncAll triggers Sync() operations for all tracked items. It only
|
|
|
|
// returns informations for items that were de-synced or have an error
|
|
|
|
// state. If local is true, the operation is limited to the current
|
|
|
|
// peer. Otherwise it happens on every cluster peer.
|
|
|
|
SyncAll(local bool) ([]api.GlobalPinInfo, error)
|
|
|
|
|
|
|
|
// Recover retriggers pin or unpin ipfs operations for a Cid in error
|
|
|
|
// state. If local is true, the operation is limited to the current
|
|
|
|
// peer, otherwise it happens on every cluster peer.
|
2018-10-01 01:53:26 +00:00
|
|
|
Recover(ci cid.Cid, local bool) (api.GlobalPinInfo, error)
|
2018-08-30 23:14:06 +00:00
|
|
|
// RecoverAll triggers Recover() operations on all tracked items. If
|
|
|
|
// local is true, the operation is limited to the current peer.
|
|
|
|
// Otherwise, it happens everywhere.
|
|
|
|
RecoverAll(local bool) ([]api.GlobalPinInfo, error)
|
|
|
|
|
|
|
|
// Version returns the ipfs-cluster peer's version.
|
|
|
|
Version() (api.Version, error)
|
|
|
|
|
2018-10-01 18:09:26 +00:00
|
|
|
// IPFS returns an instance of go-ipfs-api's Shell, pointing to a
|
|
|
|
// Cluster's IPFS proxy endpoint.
|
|
|
|
IPFS() *shell.Shell
|
|
|
|
|
2018-08-30 23:14:06 +00:00
|
|
|
// GetConnectGraph returns an ipfs-cluster connection graph. The
|
|
|
|
// serialized version, strings instead of pids, is returned
|
|
|
|
GetConnectGraph() (api.ConnectGraphSerial, error)
|
|
|
|
}
|
|
|
|
|
2017-12-05 20:44:22 +00:00
|
|
|
// Config allows to configure the parameters to connect
|
|
|
|
// to the ipfs-cluster REST API.
|
|
|
|
type Config struct {
|
2018-03-15 13:44:18 +00:00
|
|
|
// Enable SSL support. Only valid without PeerAddr.
|
2017-12-05 20:44:22 +00:00
|
|
|
SSL bool
|
|
|
|
// Skip certificate verification (insecure)
|
|
|
|
NoVerifyCert bool
|
|
|
|
|
|
|
|
// Username and password for basic authentication
|
|
|
|
Username string
|
|
|
|
Password string
|
|
|
|
|
2017-12-08 14:38:30 +00:00
|
|
|
// The ipfs-cluster REST API endpoint in multiaddress form
|
2018-08-10 13:10:16 +00:00
|
|
|
// (takes precedence over host:port). It this address contains
|
|
|
|
// an /ipfs/, /p2p/ or /dnsaddr, the API will be contacted
|
|
|
|
// through a libp2p tunnel, thus getting encryption for
|
2018-08-13 08:28:25 +00:00
|
|
|
// free. Using the libp2p tunnel will ignore any configurations.
|
2017-12-05 20:44:22 +00:00
|
|
|
APIAddr ma.Multiaddr
|
|
|
|
|
2018-08-10 13:10:16 +00:00
|
|
|
// PeerAddr is deprecated. It's aliased to APIAddr
|
|
|
|
PeerAddr ma.Multiaddr
|
|
|
|
|
2017-12-08 14:38:30 +00:00
|
|
|
// REST API endpoint host and port. Only valid without
|
2018-03-15 13:44:18 +00:00
|
|
|
// APIAddr and PeerAddr
|
2017-12-08 14:38:30 +00:00
|
|
|
Host string
|
|
|
|
Port string
|
|
|
|
|
2018-03-15 13:44:18 +00:00
|
|
|
// If PeerAddr is provided, and the peer uses private networks
|
|
|
|
// (pnet), then we need to provide the key. If the peer is the
|
|
|
|
// cluster peer, this corresponds to the cluster secret.
|
|
|
|
ProtectorKey []byte
|
|
|
|
|
2018-03-21 12:25:32 +00:00
|
|
|
// ProxyAddr is used to obtain a go-ipfs-api Shell instance pointing
|
|
|
|
// to the ipfs proxy endpoint of ipfs-cluster. If empty, the location
|
2018-08-10 13:10:16 +00:00
|
|
|
// will be guessed from one of APIAddr/Host,
|
2018-03-21 12:25:32 +00:00
|
|
|
// and the port used will be ipfs-cluster's proxy default port (9095)
|
|
|
|
ProxyAddr ma.Multiaddr
|
|
|
|
|
2017-12-05 20:44:22 +00:00
|
|
|
// Define timeout for network operations
|
|
|
|
Timeout time.Duration
|
|
|
|
|
2017-12-07 10:27:37 +00:00
|
|
|
// Specifies if we attempt to re-use connections to the same
|
|
|
|
// hosts.
|
|
|
|
DisableKeepAlives bool
|
|
|
|
|
2017-12-06 23:04:51 +00:00
|
|
|
// LogLevel defines the verbosity of the logging facility
|
2017-12-05 20:44:22 +00:00
|
|
|
LogLevel string
|
|
|
|
}
|
|
|
|
|
2018-08-30 23:14:06 +00:00
|
|
|
// DefaultClient provides methods to interact with the ipfs-cluster API. Use
|
|
|
|
// NewDefaultClient() to create one.
|
|
|
|
type defaultClient struct {
|
2017-12-05 20:44:22 +00:00
|
|
|
ctx context.Context
|
|
|
|
cancel func()
|
|
|
|
config *Config
|
2018-03-16 12:23:38 +00:00
|
|
|
transport *http.Transport
|
2018-03-15 13:44:18 +00:00
|
|
|
net string
|
|
|
|
hostname string
|
2017-12-07 10:27:37 +00:00
|
|
|
client *http.Client
|
2018-03-15 13:44:18 +00:00
|
|
|
p2p host.Host
|
2017-12-05 20:44:22 +00:00
|
|
|
}
|
|
|
|
|
2018-08-30 23:14:06 +00:00
|
|
|
// NewDefaultClient initializes a client given a Config.
|
|
|
|
func NewDefaultClient(cfg *Config) (Client, error) {
|
2017-12-08 14:59:07 +00:00
|
|
|
ctx := context.Background()
|
2018-08-30 23:14:06 +00:00
|
|
|
client := &defaultClient{
|
2018-03-15 13:44:18 +00:00
|
|
|
ctx: ctx,
|
|
|
|
config: cfg,
|
|
|
|
}
|
|
|
|
|
2018-08-10 13:10:16 +00:00
|
|
|
if paddr := client.config.PeerAddr; paddr != nil {
|
|
|
|
client.config.APIAddr = paddr
|
|
|
|
}
|
|
|
|
|
|
|
|
if client.config.Port == "" {
|
|
|
|
client.config.Port = fmt.Sprintf("%d", DefaultPort)
|
|
|
|
}
|
|
|
|
|
|
|
|
err := client.setupAPIAddr()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = client.resolveAPIAddr()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = client.setupHTTPClient()
|
2018-03-15 13:44:18 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-12-05 20:44:22 +00:00
|
|
|
|
2018-03-15 13:44:18 +00:00
|
|
|
err = client.setupHostname()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-03-21 12:25:32 +00:00
|
|
|
err = client.setupProxy()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2018-03-15 13:44:18 +00:00
|
|
|
if lvl := cfg.LogLevel; lvl != "" {
|
|
|
|
logging.SetLogLevel(loggingFacility, lvl)
|
2017-12-05 20:44:22 +00:00
|
|
|
} else {
|
2018-03-15 13:44:18 +00:00
|
|
|
logging.SetLogLevel(loggingFacility, DefaultLogLevel)
|
|
|
|
}
|
|
|
|
|
|
|
|
return client, nil
|
|
|
|
}
|
|
|
|
|
2018-08-30 23:14:06 +00:00
|
|
|
func (c *defaultClient) setupAPIAddr() error {
|
2018-08-10 13:10:16 +00:00
|
|
|
var addr ma.Multiaddr
|
|
|
|
var err error
|
|
|
|
if c.config.APIAddr == nil {
|
|
|
|
if c.config.Host == "" { //default
|
|
|
|
addr, err = ma.NewMultiaddr(DefaultAPIAddr)
|
|
|
|
} else {
|
|
|
|
addrStr := fmt.Sprintf("/dns4/%s/tcp/%s", c.config.Host, c.config.Port)
|
|
|
|
addr, err = ma.NewMultiaddr(addrStr)
|
|
|
|
}
|
|
|
|
c.config.APIAddr = addr
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-08-30 23:14:06 +00:00
|
|
|
func (c *defaultClient) resolveAPIAddr() error {
|
2018-08-14 09:40:03 +00:00
|
|
|
resolveCtx, cancel := context.WithTimeout(c.ctx, ResolveTimeout)
|
2018-08-10 13:10:16 +00:00
|
|
|
defer cancel()
|
|
|
|
resolved, err := madns.Resolve(resolveCtx, c.config.APIAddr)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(resolved) == 0 {
|
|
|
|
return fmt.Errorf("resolving %s returned 0 results", c.config.APIAddr)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.config.APIAddr = resolved[0]
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-08-30 23:14:06 +00:00
|
|
|
func (c *defaultClient) setupHTTPClient() error {
|
2018-03-16 12:23:38 +00:00
|
|
|
var err error
|
2018-03-15 13:44:18 +00:00
|
|
|
|
|
|
|
switch {
|
2018-08-10 13:10:16 +00:00
|
|
|
case IsPeerAddress(c.config.APIAddr):
|
2018-03-16 12:23:38 +00:00
|
|
|
err = c.enableLibp2p()
|
2018-03-15 13:44:18 +00:00
|
|
|
case c.config.SSL:
|
2018-03-16 12:23:38 +00:00
|
|
|
err = c.enableTLS()
|
2018-03-15 13:44:18 +00:00
|
|
|
default:
|
2018-03-16 12:23:38 +00:00
|
|
|
c.defaultTransport()
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2017-12-05 20:44:22 +00:00
|
|
|
}
|
|
|
|
|
2018-03-15 13:44:18 +00:00
|
|
|
c.client = &http.Client{
|
2018-03-16 12:23:38 +00:00
|
|
|
Transport: c.transport,
|
2018-03-15 13:44:18 +00:00
|
|
|
Timeout: c.config.Timeout,
|
2017-12-08 14:38:30 +00:00
|
|
|
}
|
2018-03-15 13:44:18 +00:00
|
|
|
return nil
|
|
|
|
}
|
2017-12-08 14:38:30 +00:00
|
|
|
|
2018-08-30 23:14:06 +00:00
|
|
|
func (c *defaultClient) setupHostname() error {
|
2018-08-10 13:10:16 +00:00
|
|
|
// Extract host:port form APIAddr or use Host:Port.
|
|
|
|
// For libp2p, hostname is set in enableLibp2p()
|
|
|
|
if !IsPeerAddress(c.config.APIAddr) {
|
|
|
|
_, hostname, err := manet.DialArgs(c.config.APIAddr)
|
2018-03-20 18:35:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-08-10 13:10:16 +00:00
|
|
|
c.hostname = hostname
|
2017-12-05 20:44:22 +00:00
|
|
|
}
|
2018-03-15 13:44:18 +00:00
|
|
|
return nil
|
|
|
|
}
|
2018-03-21 12:25:32 +00:00
|
|
|
|
2018-08-30 23:14:06 +00:00
|
|
|
func (c *defaultClient) setupProxy() error {
|
2018-03-21 12:25:32 +00:00
|
|
|
if c.config.ProxyAddr != nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-08-30 23:14:06 +00:00
|
|
|
// Guess location from APIAddr
|
2018-03-21 12:25:32 +00:00
|
|
|
port, err := ma.NewMultiaddr(fmt.Sprintf("/tcp/%d", DefaultProxyPort))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-08-10 13:10:16 +00:00
|
|
|
c.config.ProxyAddr = ma.Split(c.config.APIAddr)[0].Encapsulate(port)
|
2018-03-21 12:25:32 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// IPFS returns an instance of go-ipfs-api's Shell, pointing to the
|
2018-10-01 18:09:26 +00:00
|
|
|
// configured ProxyAddr (or to the default Cluster's IPFS proxy port).
|
2018-03-21 12:25:32 +00:00
|
|
|
// It re-uses this Client's HTTP client, thus will be constrained by
|
|
|
|
// the same configurations affecting it (timeouts...).
|
2018-08-30 23:14:06 +00:00
|
|
|
func (c *defaultClient) IPFS() *shell.Shell {
|
2018-03-21 12:25:32 +00:00
|
|
|
return shell.NewShellWithClient(c.config.ProxyAddr.String(), c.client)
|
|
|
|
}
|
2018-08-10 13:10:16 +00:00
|
|
|
|
|
|
|
// IsPeerAddress detects if the given multiaddress identifies a libp2p peer,
|
|
|
|
// either because it has the /p2p/ protocol or because it uses /dnsaddr/
|
|
|
|
func IsPeerAddress(addr ma.Multiaddr) bool {
|
|
|
|
if addr == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
pid, err := addr.ValueForProtocol(ma.P_IPFS)
|
|
|
|
dnsaddr, err2 := addr.ValueForProtocol(madns.DnsaddrProtocol.Code)
|
|
|
|
return (pid != "" && err == nil) || (dnsaddr != "" && err2 == nil)
|
|
|
|
}
|