-
Notifications
You must be signed in to change notification settings - Fork 76
3/ boost: add api client for cli interaction #15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
nonsense
merged 1 commit into
nonsense/integrate-storage-provider
from
nonsense/boost-api-client
Oct 28, 2021
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package client | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
"net/url" | ||
"path" | ||
|
||
"github.com/filecoin-project/go-jsonrpc" | ||
|
||
"github.com/filecoin-project/boost/api" | ||
"github.com/filecoin-project/boost/lib/rpcenc" | ||
) | ||
|
||
func getPushUrl(addr string) (string, error) { | ||
pushUrl, err := url.Parse(addr) | ||
if err != nil { | ||
return "", err | ||
} | ||
switch pushUrl.Scheme { | ||
case "ws": | ||
pushUrl.Scheme = "http" | ||
case "wss": | ||
pushUrl.Scheme = "https" | ||
} | ||
///rpc/v0 -> /rpc/streams/v0/push | ||
|
||
pushUrl.Path = path.Join(pushUrl.Path, "../streams/v0/push") | ||
return pushUrl.String(), nil | ||
} | ||
|
||
// NewBoostRPCV0 creates a new http jsonrpc client for miner | ||
func NewBoostRPCV0(ctx context.Context, addr string, requestHeader http.Header, opts ...jsonrpc.Option) (api.Boost, jsonrpc.ClientCloser, error) { | ||
pushUrl, err := getPushUrl(addr) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
var res api.BoostStruct | ||
closer, err := jsonrpc.NewMergeClient(ctx, addr, "Filecoin", | ||
api.GetInternalStructs(&res), requestHeader, | ||
append([]jsonrpc.Option{ | ||
rpcenc.ReaderParamEncoder(pushUrl), | ||
}, opts...)...) | ||
|
||
return &res, closer, err | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package cli | ||
|
||
import ( | ||
cliutil "github.com/filecoin-project/boost/cli/util" | ||
) | ||
|
||
var GetBoostAPI = cliutil.GetBoostAPI | ||
|
||
var DaemonContext = cliutil.DaemonContext |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,250 @@ | ||
package cliutil | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"os/signal" | ||
"strings" | ||
"syscall" | ||
|
||
"github.com/mitchellh/go-homedir" | ||
"github.com/urfave/cli/v2" | ||
"golang.org/x/xerrors" | ||
|
||
"github.com/filecoin-project/go-jsonrpc" | ||
|
||
logging "github.com/ipfs/go-log/v2" | ||
|
||
"github.com/filecoin-project/boost/api" | ||
"github.com/filecoin-project/boost/api/client" | ||
"github.com/filecoin-project/boost/node/repo" | ||
) | ||
|
||
const ( | ||
metadataTraceContext = "traceContext" | ||
) | ||
|
||
var log = logging.Logger("cli") | ||
|
||
// flagsForAPI returns flags passed on the command line with the listen address | ||
// of the API server (only used by the tests), in the order of precedence they | ||
// should be applied for the requested kind of node. | ||
func flagsForAPI(t repo.RepoType) []string { | ||
switch t { | ||
case repo.Boost: | ||
return []string{"boost-api-url"} | ||
default: | ||
panic(fmt.Sprintf("Unknown repo type: %v", t)) | ||
} | ||
} | ||
|
||
func flagsForRepo(t repo.RepoType) []string { | ||
switch t { | ||
case repo.Boost: | ||
return []string{"boost-repo"} | ||
default: | ||
panic(fmt.Sprintf("Unknown repo type: %v", t)) | ||
} | ||
} | ||
|
||
// EnvsForAPIInfos returns the environment variables to use in order of precedence | ||
// to determine the API endpoint of the specified node type. | ||
// | ||
// It returns the current variables and deprecated ones separately, so that | ||
// the user can log a warning when deprecated ones are found to be in use. | ||
func EnvsForAPIInfos(t repo.RepoType) (primary string, fallbacks []string, deprecated []string) { | ||
switch t { | ||
case repo.Boost: | ||
return "BOOST_API_INFO", []string{"BOOST_API_INFO"}, nil | ||
default: | ||
panic(fmt.Sprintf("Unknown repo type: %v", t)) | ||
} | ||
} | ||
|
||
// GetAPIInfo returns the API endpoint to use for the specified kind of repo. | ||
// | ||
// The order of precedence is as follows: | ||
// | ||
// 1. *-api-url command line flags. | ||
// 2. *_API_INFO environment variables | ||
// 3. deprecated *_API_INFO environment variables | ||
// 4. *-repo command line flags. | ||
func GetAPIInfo(ctx *cli.Context, t repo.RepoType) (APIInfo, error) { | ||
// Check if there was a flag passed with the listen address of the API | ||
// server (only used by the tests) | ||
apiFlags := flagsForAPI(t) | ||
for _, f := range apiFlags { | ||
if !ctx.IsSet(f) { | ||
continue | ||
} | ||
strma := ctx.String(f) | ||
strma = strings.TrimSpace(strma) | ||
|
||
return APIInfo{Addr: strma}, nil | ||
} | ||
|
||
// | ||
// Note: it is not correct/intuitive to prefer environment variables over | ||
// CLI flags (repo flags below). | ||
// | ||
primaryEnv, fallbacksEnvs, deprecatedEnvs := EnvsForAPIInfos(t) | ||
env, ok := os.LookupEnv(primaryEnv) | ||
if ok { | ||
return ParseApiInfo(env), nil | ||
} | ||
|
||
for _, env := range deprecatedEnvs { | ||
env, ok := os.LookupEnv(env) | ||
if ok { | ||
log.Warnf("Using deprecated env(%s) value, please use env(%s) instead.", env, primaryEnv) | ||
return ParseApiInfo(env), nil | ||
} | ||
} | ||
|
||
repoFlags := flagsForRepo(t) | ||
for _, f := range repoFlags { | ||
// cannot use ctx.IsSet because it ignores default values | ||
path := ctx.String(f) | ||
if path == "" { | ||
continue | ||
} | ||
|
||
p, err := homedir.Expand(path) | ||
if err != nil { | ||
return APIInfo{}, xerrors.Errorf("could not expand home dir (%s): %w", f, err) | ||
} | ||
|
||
r, err := repo.NewFS(p) | ||
if err != nil { | ||
return APIInfo{}, xerrors.Errorf("could not open repo at path: %s; %w", p, err) | ||
} | ||
|
||
exists, err := r.Exists() | ||
if err != nil { | ||
return APIInfo{}, xerrors.Errorf("repo.Exists returned an error: %w", err) | ||
} | ||
|
||
if !exists { | ||
return APIInfo{}, errors.New("repo directory does not exist. Make sure your configuration is correct") | ||
} | ||
|
||
ma, err := r.APIEndpoint() | ||
if err != nil { | ||
return APIInfo{}, xerrors.Errorf("could not get api endpoint: %w", err) | ||
} | ||
|
||
token, err := r.APIToken() | ||
if err != nil { | ||
log.Warnf("Couldn't load CLI token, capabilities may be limited: %v", err) | ||
} | ||
|
||
return APIInfo{ | ||
Addr: ma.String(), | ||
Token: token, | ||
}, nil | ||
} | ||
|
||
for _, env := range fallbacksEnvs { | ||
env, ok := os.LookupEnv(env) | ||
if ok { | ||
return ParseApiInfo(env), nil | ||
} | ||
} | ||
|
||
return APIInfo{}, fmt.Errorf("could not determine API endpoint for node type: %v", t) | ||
} | ||
|
||
type GetBoostOptions struct { | ||
PreferHttp bool | ||
} | ||
|
||
type GetBoostOption func(*GetBoostOptions) | ||
|
||
func BoostUseHttp(opts *GetBoostOptions) { | ||
opts.PreferHttp = true | ||
} | ||
|
||
func GetBoostAPI(ctx *cli.Context, opts ...GetBoostOption) (api.Boost, jsonrpc.ClientCloser, error) { | ||
var options GetBoostOptions | ||
for _, opt := range opts { | ||
opt(&options) | ||
} | ||
|
||
if tn, ok := ctx.App.Metadata["testnode-boost"]; ok { | ||
return tn.(api.Boost), func() {}, nil | ||
} | ||
|
||
addr, headers, err := GetRawAPI(ctx, repo.Boost, "v0") | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
if options.PreferHttp { | ||
u, err := url.Parse(addr) | ||
if err != nil { | ||
return nil, nil, xerrors.Errorf("parsing miner api URL: %w", err) | ||
} | ||
|
||
switch u.Scheme { | ||
case "ws": | ||
u.Scheme = "http" | ||
case "wss": | ||
u.Scheme = "https" | ||
} | ||
|
||
addr = u.String() | ||
} | ||
|
||
if IsVeryVerbose { | ||
_, _ = fmt.Fprintln(ctx.App.Writer, "using miner API v0 endpoint:", addr) | ||
} | ||
|
||
return client.NewBoostRPCV0(ctx.Context, addr, headers) | ||
} | ||
|
||
func GetRawAPI(ctx *cli.Context, t repo.RepoType, version string) (string, http.Header, error) { | ||
ainfo, err := GetAPIInfo(ctx, t) | ||
if err != nil { | ||
return "", nil, xerrors.Errorf("could not get API info for %s: %w", t, err) | ||
} | ||
|
||
addr, err := ainfo.DialArgs(version) | ||
if err != nil { | ||
return "", nil, xerrors.Errorf("could not get DialArgs: %w", err) | ||
} | ||
|
||
if IsVeryVerbose { | ||
_, _ = fmt.Fprintf(ctx.App.Writer, "using raw API %s endpoint: %s\n", version, addr) | ||
} | ||
|
||
return addr, ainfo.AuthHeader(), nil | ||
} | ||
|
||
func DaemonContext(cctx *cli.Context) context.Context { | ||
if mtCtx, ok := cctx.App.Metadata[metadataTraceContext]; ok { | ||
return mtCtx.(context.Context) | ||
} | ||
|
||
return context.Background() | ||
} | ||
|
||
// ReqContext returns context for cli execution. Calling it for the first time | ||
// installs SIGTERM handler that will close returned context. | ||
// Not safe for concurrent execution. | ||
func ReqContext(cctx *cli.Context) context.Context { | ||
tCtx := DaemonContext(cctx) | ||
|
||
ctx, done := context.WithCancel(tCtx) | ||
sigChan := make(chan os.Signal, 2) | ||
go func() { | ||
<-sigChan | ||
done() | ||
}() | ||
signal.Notify(sigChan, syscall.SIGTERM, syscall.SIGINT, syscall.SIGHUP) | ||
|
||
return ctx | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package cliutil | ||
|
||
import ( | ||
"net/http" | ||
"net/url" | ||
"regexp" | ||
"strings" | ||
|
||
"github.com/multiformats/go-multiaddr" | ||
manet "github.com/multiformats/go-multiaddr/net" | ||
) | ||
|
||
var ( | ||
infoWithToken = regexp.MustCompile(`^[a-zA-Z0-9\-_]+?\.[a-zA-Z0-9\-_]+?\.([a-zA-Z0-9\-_]+)?:.+$`) | ||
) | ||
|
||
type APIInfo struct { | ||
Addr string | ||
Token []byte | ||
} | ||
|
||
func ParseApiInfo(s string) APIInfo { | ||
var tok []byte | ||
if infoWithToken.Match([]byte(s)) { | ||
sp := strings.SplitN(s, ":", 2) | ||
tok = []byte(sp[0]) | ||
s = sp[1] | ||
} | ||
|
||
return APIInfo{ | ||
Addr: s, | ||
Token: tok, | ||
} | ||
} | ||
|
||
func (a APIInfo) DialArgs(version string) (string, error) { | ||
ma, err := multiaddr.NewMultiaddr(a.Addr) | ||
if err == nil { | ||
_, addr, err := manet.DialArgs(ma) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return "ws://" + addr + "/rpc/" + version, nil | ||
} | ||
|
||
_, err = url.Parse(a.Addr) | ||
if err != nil { | ||
return "", err | ||
} | ||
return a.Addr + "/rpc/" + version, nil | ||
} | ||
|
||
func (a APIInfo) Host() (string, error) { | ||
ma, err := multiaddr.NewMultiaddr(a.Addr) | ||
if err == nil { | ||
_, addr, err := manet.DialArgs(ma) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return addr, nil | ||
} | ||
|
||
spec, err := url.Parse(a.Addr) | ||
if err != nil { | ||
return "", err | ||
} | ||
return spec.Host, nil | ||
} | ||
|
||
func (a APIInfo) AuthHeader() http.Header { | ||
if len(a.Token) != 0 { | ||
headers := http.Header{} | ||
headers.Add("Authorization", "Bearer "+string(a.Token)) | ||
return headers | ||
} | ||
log.Warn("API Token not set and requested, capabilities might be limited.") | ||
return nil | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need all of these? It might be simpler to just support one of them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need them in some form - we need to configure
boost
and give the location of the service when interacting with it.I think keeping
BOOST_API_INFO
with the Lotus convention is OK.