From 86b73f61fc9bc1b9ddd140e77ab890ee2a4e5092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 30 Mar 2023 19:11:25 +0200 Subject: [PATCH 1/7] feat: cmd/ipfs: Allow passing custom BuildEnv to main --- cmd/ipfs/main.go | 106 ++++++++++++++++++++++----------------- cmd/ipfs/runmain_test.go | 2 +- 2 files changed, 60 insertions(+), 48 deletions(-) diff --git a/cmd/ipfs/main.go b/cmd/ipfs/main.go index f135f28feb8..9448a5ca91d 100644 --- a/cmd/ipfs/main.go +++ b/cmd/ipfs/main.go @@ -60,12 +60,20 @@ const ( heapProfile = "ipfs.memprof" ) -func loadPlugins(repoPath string) (*loader.PluginLoader, error) { +type PluginPreloader func(*loader.PluginLoader) error + +func LoadPlugins(repoPath string, preload PluginPreloader) (*loader.PluginLoader, error) { plugins, err := loader.NewPluginLoader(repoPath) if err != nil { return nil, fmt.Errorf("error loading plugins: %s", err) } + if preload != nil { + if err := preload(plugins); err != nil { + return nil, fmt.Errorf("error loading plugins (preload): %s", err) + } + } + if err := plugins.Initialize(); err != nil { return nil, fmt.Errorf("error initializing plugins: %s", err) } @@ -83,7 +91,7 @@ func loadPlugins(repoPath string) (*loader.PluginLoader, error) { // - output the response // - if anything fails, print error, maybe with help. func main() { - os.Exit(mainRet()) + os.Exit(Start(BuildDefaultEnv)) } func printErr(err error) int { @@ -101,7 +109,54 @@ func newUUID(key string) logging.Metadata { } } -func mainRet() (exitCode int) { +func BuildDefaultEnv(ctx context.Context, req *cmds.Request) (cmds.Environment, error) { + return BuildEnv(ctx, req, nil) +} + +func BuildEnv(ctx context.Context, req *cmds.Request, pl PluginPreloader) (cmds.Environment, error) { + checkDebug(req) + repoPath, err := GetRepoPath(req) + if err != nil { + return nil, err + } + log.Debugf("config path is %s", repoPath) + + plugins, err := LoadPlugins(repoPath, pl) + if err != nil { + return nil, err + } + + // this sets up the function that will initialize the node + // this is so that we can construct the node lazily. + return &oldcmds.Context{ + ConfigRoot: repoPath, + ReqLog: &oldcmds.ReqLog{}, + Plugins: plugins, + ConstructNode: func() (n *core.IpfsNode, err error) { + if req == nil { + return nil, errors.New("constructing node without a request") + } + + r, err := fsrepo.Open(repoPath) + if err != nil { // repo is owned by the node + return nil, err + } + + // ok everything is good. set it on the invocation (for ownership) + // and return it. + n, err = core.NewNode(ctx, &core.BuildCfg{ + Repo: r, + }) + if err != nil { + return nil, err + } + + return n, nil + }, + }, nil +} + +func Start(buildEnv func(ctx context.Context, req *cmds.Request) (cmds.Environment, error)) (exitCode int) { ctx := logging.ContextWithLoggable(context.Background(), newUUID("session")) tp, err := tracing.NewTracerProvider(ctx) @@ -155,49 +210,6 @@ func mainRet() (exitCode int) { // so we need to make sure it's stable os.Args[0] = "ipfs" - buildEnv := func(ctx context.Context, req *cmds.Request) (cmds.Environment, error) { - checkDebug(req) - repoPath, err := getRepoPath(req) - if err != nil { - return nil, err - } - log.Debugf("config path is %s", repoPath) - - plugins, err := loadPlugins(repoPath) - if err != nil { - return nil, err - } - - // this sets up the function that will initialize the node - // this is so that we can construct the node lazily. - return &oldcmds.Context{ - ConfigRoot: repoPath, - ReqLog: &oldcmds.ReqLog{}, - Plugins: plugins, - ConstructNode: func() (n *core.IpfsNode, err error) { - if req == nil { - return nil, errors.New("constructing node without a request") - } - - r, err := fsrepo.Open(repoPath) - if err != nil { // repo is owned by the node - return nil, err - } - - // ok everything is good. set it on the invocation (for ownership) - // and return it. - n, err = core.NewNode(ctx, &core.BuildCfg{ - Repo: r, - }) - if err != nil { - return nil, err - } - - return n, nil - }, - }, nil - } - err = cli.Run(ctx, Root, os.Args, os.Stdin, os.Stdout, os.Stderr, buildEnv, makeExecutor) if err != nil { return 1 @@ -364,7 +376,7 @@ func (twe tracingWrappedExecutor) Execute(req *cmds.Request, re cmds.ResponseEmi return err } -func getRepoPath(req *cmds.Request) (string, error) { +func GetRepoPath(req *cmds.Request) (string, error) { repoOpt, found := req.Options[corecmds.RepoDirOption].(string) if found && repoOpt != "" { return repoOpt, nil diff --git a/cmd/ipfs/runmain_test.go b/cmd/ipfs/runmain_test.go index c9f3f01982e..b09a2522640 100644 --- a/cmd/ipfs/runmain_test.go +++ b/cmd/ipfs/runmain_test.go @@ -16,7 +16,7 @@ import ( func TestRunMain(t *testing.T) { args := flag.Args() os.Args = append([]string{os.Args[0]}, args...) - ret := mainRet() + ret := Start() p := os.Getenv("IPFS_COVER_RET_FILE") if len(p) != 0 { From b5dddf67a23d597c63863e3401b31b0f8f31a047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 30 Mar 2023 19:25:56 +0200 Subject: [PATCH 2/7] feat: cmd/ipfs: Make it possible to depend on cmd/ipfs --- cmd/ipfs/{ => kubo}/add_migrations.go | 2 +- cmd/ipfs/{ => kubo}/daemon.go | 2 +- cmd/ipfs/{ => kubo}/daemon_linux.go | 2 +- cmd/ipfs/{ => kubo}/daemon_other.go | 2 +- cmd/ipfs/{ => kubo}/debug.go | 2 +- cmd/ipfs/{ => kubo}/dnsresolve_test.go | 2 +- cmd/ipfs/{ => kubo}/init.go | 2 +- cmd/ipfs/{ => kubo}/ipfs.go | 2 +- cmd/ipfs/{ => kubo}/pinmfs.go | 2 +- cmd/ipfs/{ => kubo}/pinmfs_test.go | 2 +- cmd/ipfs/kubo/start.go | 494 +++++++++++++++++++++++++ cmd/ipfs/main.go | 487 +----------------------- cmd/ipfs/runmain_test.go | 7 +- 13 files changed, 511 insertions(+), 497 deletions(-) rename cmd/ipfs/{ => kubo}/add_migrations.go (99%) rename cmd/ipfs/{ => kubo}/daemon.go (99%) rename cmd/ipfs/{ => kubo}/daemon_linux.go (95%) rename cmd/ipfs/{ => kubo}/daemon_other.go (86%) rename cmd/ipfs/{ => kubo}/debug.go (94%) rename cmd/ipfs/{ => kubo}/dnsresolve_test.go (99%) rename cmd/ipfs/{ => kubo}/init.go (99%) rename cmd/ipfs/{ => kubo}/ipfs.go (98%) rename cmd/ipfs/{ => kubo}/pinmfs.go (99%) rename cmd/ipfs/{ => kubo}/pinmfs_test.go (99%) create mode 100644 cmd/ipfs/kubo/start.go diff --git a/cmd/ipfs/add_migrations.go b/cmd/ipfs/kubo/add_migrations.go similarity index 99% rename from cmd/ipfs/add_migrations.go rename to cmd/ipfs/kubo/add_migrations.go index 14a98e04c6b..557a8de8441 100644 --- a/cmd/ipfs/add_migrations.go +++ b/cmd/ipfs/kubo/add_migrations.go @@ -1,4 +1,4 @@ -package main +package kubo import ( "context" diff --git a/cmd/ipfs/daemon.go b/cmd/ipfs/kubo/daemon.go similarity index 99% rename from cmd/ipfs/daemon.go rename to cmd/ipfs/kubo/daemon.go index 4ad9b629ce0..31e5fe28d8f 100644 --- a/cmd/ipfs/daemon.go +++ b/cmd/ipfs/kubo/daemon.go @@ -1,4 +1,4 @@ -package main +package kubo import ( "errors" diff --git a/cmd/ipfs/daemon_linux.go b/cmd/ipfs/kubo/daemon_linux.go similarity index 95% rename from cmd/ipfs/daemon_linux.go rename to cmd/ipfs/kubo/daemon_linux.go index d06baf286b0..b612738a275 100644 --- a/cmd/ipfs/daemon_linux.go +++ b/cmd/ipfs/kubo/daemon_linux.go @@ -1,7 +1,7 @@ //go:build linux // +build linux -package main +package kubo import ( daemon "github.com/coreos/go-systemd/v22/daemon" diff --git a/cmd/ipfs/daemon_other.go b/cmd/ipfs/kubo/daemon_other.go similarity index 86% rename from cmd/ipfs/daemon_other.go rename to cmd/ipfs/kubo/daemon_other.go index cb96ce1b90c..c5b24053d94 100644 --- a/cmd/ipfs/daemon_other.go +++ b/cmd/ipfs/kubo/daemon_other.go @@ -1,7 +1,7 @@ //go:build !linux // +build !linux -package main +package kubo func notifyReady() {} diff --git a/cmd/ipfs/debug.go b/cmd/ipfs/kubo/debug.go similarity index 94% rename from cmd/ipfs/debug.go rename to cmd/ipfs/kubo/debug.go index f1b2683d188..ce07ca8e922 100644 --- a/cmd/ipfs/debug.go +++ b/cmd/ipfs/kubo/debug.go @@ -1,4 +1,4 @@ -package main +package kubo import ( "net/http" diff --git a/cmd/ipfs/dnsresolve_test.go b/cmd/ipfs/kubo/dnsresolve_test.go similarity index 99% rename from cmd/ipfs/dnsresolve_test.go rename to cmd/ipfs/kubo/dnsresolve_test.go index 8ffa0ebdeb3..82e4e62f55a 100644 --- a/cmd/ipfs/dnsresolve_test.go +++ b/cmd/ipfs/kubo/dnsresolve_test.go @@ -1,4 +1,4 @@ -package main +package kubo import ( "context" diff --git a/cmd/ipfs/init.go b/cmd/ipfs/kubo/init.go similarity index 99% rename from cmd/ipfs/init.go rename to cmd/ipfs/kubo/init.go index 82c622ab5d0..47aee7aeb0f 100644 --- a/cmd/ipfs/init.go +++ b/cmd/ipfs/kubo/init.go @@ -1,4 +1,4 @@ -package main +package kubo import ( "context" diff --git a/cmd/ipfs/ipfs.go b/cmd/ipfs/kubo/ipfs.go similarity index 98% rename from cmd/ipfs/ipfs.go rename to cmd/ipfs/kubo/ipfs.go index 3b953c40c88..58310865e0e 100644 --- a/cmd/ipfs/ipfs.go +++ b/cmd/ipfs/kubo/ipfs.go @@ -1,4 +1,4 @@ -package main +package kubo import ( commands "github.com/ipfs/kubo/core/commands" diff --git a/cmd/ipfs/pinmfs.go b/cmd/ipfs/kubo/pinmfs.go similarity index 99% rename from cmd/ipfs/pinmfs.go rename to cmd/ipfs/kubo/pinmfs.go index 977d96ba0c4..846ee8a77af 100644 --- a/cmd/ipfs/pinmfs.go +++ b/cmd/ipfs/kubo/pinmfs.go @@ -1,4 +1,4 @@ -package main +package kubo import ( "context" diff --git a/cmd/ipfs/pinmfs_test.go b/cmd/ipfs/kubo/pinmfs_test.go similarity index 99% rename from cmd/ipfs/pinmfs_test.go rename to cmd/ipfs/kubo/pinmfs_test.go index 7f1ac869672..da71d362cdb 100644 --- a/cmd/ipfs/pinmfs_test.go +++ b/cmd/ipfs/kubo/pinmfs_test.go @@ -1,4 +1,4 @@ -package main +package kubo import ( "context" diff --git a/cmd/ipfs/kubo/start.go b/cmd/ipfs/kubo/start.go new file mode 100644 index 00000000000..2a97fd49077 --- /dev/null +++ b/cmd/ipfs/kubo/start.go @@ -0,0 +1,494 @@ +// cmd/ipfs implements the primary CLI binary for ipfs +package kubo + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "net/http" + "os" + "runtime/pprof" + "strings" + "time" + + "github.com/blang/semver/v4" + "github.com/google/uuid" + u "github.com/ipfs/boxo/util" + cmds "github.com/ipfs/go-ipfs-cmds" + "github.com/ipfs/go-ipfs-cmds/cli" + cmdhttp "github.com/ipfs/go-ipfs-cmds/http" + logging "github.com/ipfs/go-log" + ipfs "github.com/ipfs/kubo" + "github.com/ipfs/kubo/client/rpc/auth" + "github.com/ipfs/kubo/cmd/ipfs/util" + oldcmds "github.com/ipfs/kubo/commands" + config "github.com/ipfs/kubo/config" + "github.com/ipfs/kubo/core" + corecmds "github.com/ipfs/kubo/core/commands" + "github.com/ipfs/kubo/core/corehttp" + "github.com/ipfs/kubo/plugin/loader" + "github.com/ipfs/kubo/repo" + "github.com/ipfs/kubo/repo/fsrepo" + "github.com/ipfs/kubo/tracing" + ma "github.com/multiformats/go-multiaddr" + madns "github.com/multiformats/go-multiaddr-dns" + manet "github.com/multiformats/go-multiaddr/net" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/contrib/propagators/autoprop" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/codes" + "go.opentelemetry.io/otel/trace" +) + +// log is the command logger. +var ( + log = logging.Logger("cmd/ipfs") + tracer trace.Tracer +) + +// declared as a var for testing purposes. +var dnsResolver = madns.DefaultResolver + +const ( + EnvEnableProfiling = "IPFS_PROF" + cpuProfile = "ipfs.cpuprof" + heapProfile = "ipfs.memprof" +) + +type PluginPreloader func(*loader.PluginLoader) error + +func LoadPlugins(repoPath string, preload PluginPreloader) (*loader.PluginLoader, error) { + plugins, err := loader.NewPluginLoader(repoPath) + if err != nil { + return nil, fmt.Errorf("error loading plugins: %s", err) + } + + if preload != nil { + if err := preload(plugins); err != nil { + return nil, fmt.Errorf("error loading plugins (preload): %s", err) + } + } + + if err := plugins.Initialize(); err != nil { + return nil, fmt.Errorf("error initializing plugins: %s", err) + } + + if err := plugins.Inject(); err != nil { + return nil, fmt.Errorf("error initializing plugins: %s", err) + } + return plugins, nil +} + +// main roadmap: +// - parse the commandline to get a cmdInvocation +// - if user requests help, print it and exit. +// - run the command invocation +// - output the response +// - if anything fails, print error, maybe with help. +func main() { + os.Exit(Start(BuildDefaultEnv)) +} + +func printErr(err error) int { + fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) + return 1 +} + +func newUUID(key string) logging.Metadata { + ids := "#UUID-ERROR#" + if id, err := uuid.NewRandom(); err == nil { + ids = id.String() + } + return logging.Metadata{ + key: ids, + } +} + +func BuildDefaultEnv(ctx context.Context, req *cmds.Request) (cmds.Environment, error) { + return BuildEnv(ctx, req, nil) +} + +func BuildEnv(ctx context.Context, req *cmds.Request, pl PluginPreloader) (cmds.Environment, error) { + checkDebug(req) + repoPath, err := GetRepoPath(req) + if err != nil { + return nil, err + } + log.Debugf("config path is %s", repoPath) + + plugins, err := LoadPlugins(repoPath, pl) + if err != nil { + return nil, err + } + + // this sets up the function that will initialize the node + // this is so that we can construct the node lazily. + return &oldcmds.Context{ + ConfigRoot: repoPath, + ReqLog: &oldcmds.ReqLog{}, + Plugins: plugins, + ConstructNode: func() (n *core.IpfsNode, err error) { + if req == nil { + return nil, errors.New("constructing node without a request") + } + + r, err := fsrepo.Open(repoPath) + if err != nil { // repo is owned by the node + return nil, err + } + + // ok everything is good. set it on the invocation (for ownership) + // and return it. + n, err = core.NewNode(ctx, &core.BuildCfg{ + Repo: r, + }) + if err != nil { + return nil, err + } + + return n, nil + }, + }, nil +} + +func Start(buildEnv func(ctx context.Context, req *cmds.Request) (cmds.Environment, error)) (exitCode int) { + ctx := logging.ContextWithLoggable(context.Background(), newUUID("session")) + + tp, err := tracing.NewTracerProvider(ctx) + if err != nil { + return printErr(err) + } + defer func() { + if err := tp.Shutdown(ctx); err != nil { + exitCode = printErr(err) + } + }() + otel.SetTracerProvider(tp) + otel.SetTextMapPropagator(autoprop.NewTextMapPropagator()) + tracer = tp.Tracer("Kubo-cli") + + stopFunc, err := profileIfEnabled() + if err != nil { + return printErr(err) + } + defer stopFunc() // to be executed as late as possible + + intrh, ctx := util.SetupInterruptHandler(ctx) + defer intrh.Close() + + // Handle `ipfs version` or `ipfs help` + if len(os.Args) > 1 { + // Handle `ipfs --version' + if os.Args[1] == "--version" { + os.Args[1] = "version" + } + + // Handle `ipfs help` and `ipfs help ` + if os.Args[1] == "help" { + if len(os.Args) > 2 { + os.Args = append(os.Args[:1], os.Args[2:]...) + // Handle `ipfs help --help` + // append `--help`,when the command is not `ipfs help --help` + if os.Args[1] != "--help" { + os.Args = append(os.Args, "--help") + } + } else { + os.Args[1] = "--help" + } + } + } else if insideGUI() { // if no args were passed, and we're in a GUI environment + // launch the daemon instead of launching a ghost window + os.Args = append(os.Args, "daemon", "--init") + } + + // output depends on executable name passed in os.Args + // so we need to make sure it's stable + os.Args[0] = "ipfs" + + err = cli.Run(ctx, Root, os.Args, os.Stdin, os.Stdout, os.Stderr, buildEnv, makeExecutor) + if err != nil { + return 1 + } + + // everything went better than expected :) + return 0 +} + +func insideGUI() bool { + return util.InsideGUI() +} + +func checkDebug(req *cmds.Request) { + // check if user wants to debug. option OR env var. + debug, _ := req.Options["debug"].(bool) + if debug || os.Getenv("IPFS_LOGGING") == "debug" { + u.Debug = true + logging.SetDebugLogging() + } + if u.GetenvBool("DEBUG") { + u.Debug = true + } +} + +func apiAddrOption(req *cmds.Request) (ma.Multiaddr, error) { + apiAddrStr, apiSpecified := req.Options[corecmds.ApiOption].(string) + if !apiSpecified { + return nil, nil + } + return ma.NewMultiaddr(apiAddrStr) +} + +// encodedAbsolutePathVersion is the version from which the absolute path header in +// multipart requests is %-encoded. Before this version, its sent raw. +var encodedAbsolutePathVersion = semver.MustParse("0.23.0-dev") + +func makeExecutor(req *cmds.Request, env interface{}) (cmds.Executor, error) { + exe := tracingWrappedExecutor{cmds.NewExecutor(req.Root)} + cctx := env.(*oldcmds.Context) + + // Check if the command is disabled. + if req.Command.NoLocal && req.Command.NoRemote { + return nil, fmt.Errorf("command disabled: %v", req.Path) + } + + // Can we just run this locally? + if !req.Command.NoLocal { + if doesNotUseRepo, ok := corecmds.GetDoesNotUseRepo(req.Command.Extra); doesNotUseRepo && ok { + return exe, nil + } + } + + // Get the API option from the commandline. + apiAddr, err := apiAddrOption(req) + if err != nil { + return nil, err + } + + // Require that the command be run on the daemon when the API flag is + // passed (unless we're trying to _run_ the daemon). + daemonRequested := apiAddr != nil && req.Command != daemonCmd + + // Run this on the client if required. + if req.Command.NoRemote { + if daemonRequested { + // User requested that the command be run on the daemon but we can't. + // NOTE: We drop this check for the `ipfs daemon` command. + return nil, errors.New("api flag specified but command cannot be run on the daemon") + } + return exe, nil + } + + // Finally, look in the repo for an API file. + if apiAddr == nil { + var err error + apiAddr, err = fsrepo.APIAddr(cctx.ConfigRoot) + switch err { + case nil, repo.ErrApiNotRunning: + default: + return nil, err + } + } + + // Still no api specified? Run it on the client or fail. + if apiAddr == nil { + if req.Command.NoLocal { + return nil, fmt.Errorf("command must be run on the daemon: %v", req.Path) + } + return exe, nil + } + + // Resolve the API addr. + apiAddr, err = resolveAddr(req.Context, apiAddr) + if err != nil { + return nil, err + } + network, host, err := manet.DialArgs(apiAddr) + if err != nil { + return nil, err + } + + // Construct the executor. + opts := []cmdhttp.ClientOpt{ + cmdhttp.ClientWithAPIPrefix(corehttp.APIPath), + } + + // Fallback on a local executor if we (a) have a repo and (b) aren't + // forcing a daemon. + if !daemonRequested && fsrepo.IsInitialized(cctx.ConfigRoot) { + opts = append(opts, cmdhttp.ClientWithFallback(exe)) + } + + var tpt http.RoundTripper + switch network { + case "tcp", "tcp4", "tcp6": + tpt = http.DefaultTransport + case "unix": + path := host + host = "unix" + tpt = &http.Transport{ + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", path) + }, + } + default: + return nil, fmt.Errorf("unsupported API address: %s", apiAddr) + } + + apiAuth, specified := req.Options[corecmds.ApiAuthOption].(string) + if specified { + authorization := config.ConvertAuthSecret(apiAuth) + tpt = auth.NewAuthorizedRoundTripper(authorization, tpt) + } + + httpClient := &http.Client{ + Transport: otelhttp.NewTransport(tpt), + } + opts = append(opts, cmdhttp.ClientWithHTTPClient(httpClient)) + + // Fetch remove version, as some feature compatibility might change depending on it. + remoteVersion, err := getRemoteVersion(tracingWrappedExecutor{cmdhttp.NewClient(host, opts...)}) + if err != nil { + return nil, err + } + opts = append(opts, cmdhttp.ClientWithRawAbsPath(remoteVersion.LT(encodedAbsolutePathVersion))) + + return tracingWrappedExecutor{cmdhttp.NewClient(host, opts...)}, nil +} + +type tracingWrappedExecutor struct { + exec cmds.Executor +} + +func (twe tracingWrappedExecutor) Execute(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) error { + ctx, span := tracer.Start(req.Context, "cmds."+strings.Join(req.Path, "."), trace.WithAttributes(attribute.StringSlice("Arguments", req.Arguments))) + defer span.End() + req.Context = ctx + + err := twe.exec.Execute(req, re, env) + if err != nil { + span.SetStatus(codes.Error, err.Error()) + } + return err +} + +func GetRepoPath(req *cmds.Request) (string, error) { + repoOpt, found := req.Options[corecmds.RepoDirOption].(string) + if found && repoOpt != "" { + return repoOpt, nil + } + + repoPath, err := fsrepo.BestKnownPath() + if err != nil { + return "", err + } + return repoPath, nil +} + +// startProfiling begins CPU profiling and returns a `stop` function to be +// executed as late as possible. The stop function captures the memprofile. +func startProfiling() (func(), error) { + // start CPU profiling as early as possible + ofi, err := os.Create(cpuProfile) + if err != nil { + return nil, err + } + err = pprof.StartCPUProfile(ofi) + if err != nil { + ofi.Close() + return nil, err + } + go func() { + for range time.NewTicker(time.Second * 30).C { + err := writeHeapProfileToFile() + if err != nil { + log.Error(err) + } + } + }() + + stopProfiling := func() { + pprof.StopCPUProfile() + ofi.Close() // captured by the closure + } + return stopProfiling, nil +} + +func writeHeapProfileToFile() error { + mprof, err := os.Create(heapProfile) + if err != nil { + return err + } + defer mprof.Close() // _after_ writing the heap profile + return pprof.WriteHeapProfile(mprof) +} + +func profileIfEnabled() (func(), error) { + // FIXME this is a temporary hack so profiling of asynchronous operations + // works as intended. + if os.Getenv(EnvEnableProfiling) != "" { + stopProfilingFunc, err := startProfiling() // TODO maybe change this to its own option... profiling makes it slower. + if err != nil { + return nil, err + } + return stopProfilingFunc, nil + } + return func() {}, nil +} + +func resolveAddr(ctx context.Context, addr ma.Multiaddr) (ma.Multiaddr, error) { + ctx, cancelFunc := context.WithTimeout(ctx, 10*time.Second) + defer cancelFunc() + + addrs, err := dnsResolver.Resolve(ctx, addr) + if err != nil { + return nil, err + } + + if len(addrs) == 0 { + return nil, errors.New("non-resolvable API endpoint") + } + + return addrs[0], nil +} + +type nopWriter struct { + io.Writer +} + +func (nw nopWriter) Close() error { + return nil +} + +func getRemoteVersion(exe cmds.Executor) (*semver.Version, error) { + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*30)) + defer cancel() + + req, err := cmds.NewRequest(ctx, []string{"version"}, nil, nil, nil, Root) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + re, err := cmds.NewWriterResponseEmitter(nopWriter{&buf}, req) + if err != nil { + return nil, err + } + + err = exe.Execute(req, re, nil) + if err != nil { + return nil, err + } + + var out ipfs.VersionInfo + dec := json.NewDecoder(&buf) + if err := dec.Decode(&out); err != nil { + return nil, err + } + + return semver.New(out.Version) +} diff --git a/cmd/ipfs/main.go b/cmd/ipfs/main.go index 9448a5ca91d..ae7eedc0f2b 100644 --- a/cmd/ipfs/main.go +++ b/cmd/ipfs/main.go @@ -1,494 +1,11 @@ -// cmd/ipfs implements the primary CLI binary for ipfs package main import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net" - "net/http" "os" - "runtime/pprof" - "strings" - "time" - "github.com/blang/semver/v4" - "github.com/google/uuid" - u "github.com/ipfs/boxo/util" - cmds "github.com/ipfs/go-ipfs-cmds" - "github.com/ipfs/go-ipfs-cmds/cli" - cmdhttp "github.com/ipfs/go-ipfs-cmds/http" - logging "github.com/ipfs/go-log" - ipfs "github.com/ipfs/kubo" - "github.com/ipfs/kubo/client/rpc/auth" - "github.com/ipfs/kubo/cmd/ipfs/util" - oldcmds "github.com/ipfs/kubo/commands" - config "github.com/ipfs/kubo/config" - "github.com/ipfs/kubo/core" - corecmds "github.com/ipfs/kubo/core/commands" - "github.com/ipfs/kubo/core/corehttp" - "github.com/ipfs/kubo/plugin/loader" - "github.com/ipfs/kubo/repo" - "github.com/ipfs/kubo/repo/fsrepo" - "github.com/ipfs/kubo/tracing" - ma "github.com/multiformats/go-multiaddr" - madns "github.com/multiformats/go-multiaddr-dns" - manet "github.com/multiformats/go-multiaddr/net" - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" - "go.opentelemetry.io/contrib/propagators/autoprop" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/trace" + "github.com/ipfs/kubo/cmd/ipfs/kubo" ) -// log is the command logger. -var ( - log = logging.Logger("cmd/ipfs") - tracer trace.Tracer -) - -// declared as a var for testing purposes. -var dnsResolver = madns.DefaultResolver - -const ( - EnvEnableProfiling = "IPFS_PROF" - cpuProfile = "ipfs.cpuprof" - heapProfile = "ipfs.memprof" -) - -type PluginPreloader func(*loader.PluginLoader) error - -func LoadPlugins(repoPath string, preload PluginPreloader) (*loader.PluginLoader, error) { - plugins, err := loader.NewPluginLoader(repoPath) - if err != nil { - return nil, fmt.Errorf("error loading plugins: %s", err) - } - - if preload != nil { - if err := preload(plugins); err != nil { - return nil, fmt.Errorf("error loading plugins (preload): %s", err) - } - } - - if err := plugins.Initialize(); err != nil { - return nil, fmt.Errorf("error initializing plugins: %s", err) - } - - if err := plugins.Inject(); err != nil { - return nil, fmt.Errorf("error initializing plugins: %s", err) - } - return plugins, nil -} - -// main roadmap: -// - parse the commandline to get a cmdInvocation -// - if user requests help, print it and exit. -// - run the command invocation -// - output the response -// - if anything fails, print error, maybe with help. func main() { - os.Exit(Start(BuildDefaultEnv)) -} - -func printErr(err error) int { - fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) - return 1 -} - -func newUUID(key string) logging.Metadata { - ids := "#UUID-ERROR#" - if id, err := uuid.NewRandom(); err == nil { - ids = id.String() - } - return logging.Metadata{ - key: ids, - } -} - -func BuildDefaultEnv(ctx context.Context, req *cmds.Request) (cmds.Environment, error) { - return BuildEnv(ctx, req, nil) -} - -func BuildEnv(ctx context.Context, req *cmds.Request, pl PluginPreloader) (cmds.Environment, error) { - checkDebug(req) - repoPath, err := GetRepoPath(req) - if err != nil { - return nil, err - } - log.Debugf("config path is %s", repoPath) - - plugins, err := LoadPlugins(repoPath, pl) - if err != nil { - return nil, err - } - - // this sets up the function that will initialize the node - // this is so that we can construct the node lazily. - return &oldcmds.Context{ - ConfigRoot: repoPath, - ReqLog: &oldcmds.ReqLog{}, - Plugins: plugins, - ConstructNode: func() (n *core.IpfsNode, err error) { - if req == nil { - return nil, errors.New("constructing node without a request") - } - - r, err := fsrepo.Open(repoPath) - if err != nil { // repo is owned by the node - return nil, err - } - - // ok everything is good. set it on the invocation (for ownership) - // and return it. - n, err = core.NewNode(ctx, &core.BuildCfg{ - Repo: r, - }) - if err != nil { - return nil, err - } - - return n, nil - }, - }, nil -} - -func Start(buildEnv func(ctx context.Context, req *cmds.Request) (cmds.Environment, error)) (exitCode int) { - ctx := logging.ContextWithLoggable(context.Background(), newUUID("session")) - - tp, err := tracing.NewTracerProvider(ctx) - if err != nil { - return printErr(err) - } - defer func() { - if err := tp.Shutdown(ctx); err != nil { - exitCode = printErr(err) - } - }() - otel.SetTracerProvider(tp) - otel.SetTextMapPropagator(autoprop.NewTextMapPropagator()) - tracer = tp.Tracer("Kubo-cli") - - stopFunc, err := profileIfEnabled() - if err != nil { - return printErr(err) - } - defer stopFunc() // to be executed as late as possible - - intrh, ctx := util.SetupInterruptHandler(ctx) - defer intrh.Close() - - // Handle `ipfs version` or `ipfs help` - if len(os.Args) > 1 { - // Handle `ipfs --version' - if os.Args[1] == "--version" { - os.Args[1] = "version" - } - - // Handle `ipfs help` and `ipfs help ` - if os.Args[1] == "help" { - if len(os.Args) > 2 { - os.Args = append(os.Args[:1], os.Args[2:]...) - // Handle `ipfs help --help` - // append `--help`,when the command is not `ipfs help --help` - if os.Args[1] != "--help" { - os.Args = append(os.Args, "--help") - } - } else { - os.Args[1] = "--help" - } - } - } else if insideGUI() { // if no args were passed, and we're in a GUI environment - // launch the daemon instead of launching a ghost window - os.Args = append(os.Args, "daemon", "--init") - } - - // output depends on executable name passed in os.Args - // so we need to make sure it's stable - os.Args[0] = "ipfs" - - err = cli.Run(ctx, Root, os.Args, os.Stdin, os.Stdout, os.Stderr, buildEnv, makeExecutor) - if err != nil { - return 1 - } - - // everything went better than expected :) - return 0 -} - -func insideGUI() bool { - return util.InsideGUI() -} - -func checkDebug(req *cmds.Request) { - // check if user wants to debug. option OR env var. - debug, _ := req.Options["debug"].(bool) - if debug || os.Getenv("IPFS_LOGGING") == "debug" { - u.Debug = true - logging.SetDebugLogging() - } - if u.GetenvBool("DEBUG") { - u.Debug = true - } -} - -func apiAddrOption(req *cmds.Request) (ma.Multiaddr, error) { - apiAddrStr, apiSpecified := req.Options[corecmds.ApiOption].(string) - if !apiSpecified { - return nil, nil - } - return ma.NewMultiaddr(apiAddrStr) -} - -// encodedAbsolutePathVersion is the version from which the absolute path header in -// multipart requests is %-encoded. Before this version, its sent raw. -var encodedAbsolutePathVersion = semver.MustParse("0.23.0-dev") - -func makeExecutor(req *cmds.Request, env interface{}) (cmds.Executor, error) { - exe := tracingWrappedExecutor{cmds.NewExecutor(req.Root)} - cctx := env.(*oldcmds.Context) - - // Check if the command is disabled. - if req.Command.NoLocal && req.Command.NoRemote { - return nil, fmt.Errorf("command disabled: %v", req.Path) - } - - // Can we just run this locally? - if !req.Command.NoLocal { - if doesNotUseRepo, ok := corecmds.GetDoesNotUseRepo(req.Command.Extra); doesNotUseRepo && ok { - return exe, nil - } - } - - // Get the API option from the commandline. - apiAddr, err := apiAddrOption(req) - if err != nil { - return nil, err - } - - // Require that the command be run on the daemon when the API flag is - // passed (unless we're trying to _run_ the daemon). - daemonRequested := apiAddr != nil && req.Command != daemonCmd - - // Run this on the client if required. - if req.Command.NoRemote { - if daemonRequested { - // User requested that the command be run on the daemon but we can't. - // NOTE: We drop this check for the `ipfs daemon` command. - return nil, errors.New("api flag specified but command cannot be run on the daemon") - } - return exe, nil - } - - // Finally, look in the repo for an API file. - if apiAddr == nil { - var err error - apiAddr, err = fsrepo.APIAddr(cctx.ConfigRoot) - switch err { - case nil, repo.ErrApiNotRunning: - default: - return nil, err - } - } - - // Still no api specified? Run it on the client or fail. - if apiAddr == nil { - if req.Command.NoLocal { - return nil, fmt.Errorf("command must be run on the daemon: %v", req.Path) - } - return exe, nil - } - - // Resolve the API addr. - apiAddr, err = resolveAddr(req.Context, apiAddr) - if err != nil { - return nil, err - } - network, host, err := manet.DialArgs(apiAddr) - if err != nil { - return nil, err - } - - // Construct the executor. - opts := []cmdhttp.ClientOpt{ - cmdhttp.ClientWithAPIPrefix(corehttp.APIPath), - } - - // Fallback on a local executor if we (a) have a repo and (b) aren't - // forcing a daemon. - if !daemonRequested && fsrepo.IsInitialized(cctx.ConfigRoot) { - opts = append(opts, cmdhttp.ClientWithFallback(exe)) - } - - var tpt http.RoundTripper - switch network { - case "tcp", "tcp4", "tcp6": - tpt = http.DefaultTransport - case "unix": - path := host - host = "unix" - tpt = &http.Transport{ - DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { - return net.Dial("unix", path) - }, - } - default: - return nil, fmt.Errorf("unsupported API address: %s", apiAddr) - } - - apiAuth, specified := req.Options[corecmds.ApiAuthOption].(string) - if specified { - authorization := config.ConvertAuthSecret(apiAuth) - tpt = auth.NewAuthorizedRoundTripper(authorization, tpt) - } - - httpClient := &http.Client{ - Transport: otelhttp.NewTransport(tpt), - } - opts = append(opts, cmdhttp.ClientWithHTTPClient(httpClient)) - - // Fetch remove version, as some feature compatibility might change depending on it. - remoteVersion, err := getRemoteVersion(tracingWrappedExecutor{cmdhttp.NewClient(host, opts...)}) - if err != nil { - return nil, err - } - opts = append(opts, cmdhttp.ClientWithRawAbsPath(remoteVersion.LT(encodedAbsolutePathVersion))) - - return tracingWrappedExecutor{cmdhttp.NewClient(host, opts...)}, nil -} - -type tracingWrappedExecutor struct { - exec cmds.Executor -} - -func (twe tracingWrappedExecutor) Execute(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) error { - ctx, span := tracer.Start(req.Context, "cmds."+strings.Join(req.Path, "."), trace.WithAttributes(attribute.StringSlice("Arguments", req.Arguments))) - defer span.End() - req.Context = ctx - - err := twe.exec.Execute(req, re, env) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - } - return err -} - -func GetRepoPath(req *cmds.Request) (string, error) { - repoOpt, found := req.Options[corecmds.RepoDirOption].(string) - if found && repoOpt != "" { - return repoOpt, nil - } - - repoPath, err := fsrepo.BestKnownPath() - if err != nil { - return "", err - } - return repoPath, nil -} - -// startProfiling begins CPU profiling and returns a `stop` function to be -// executed as late as possible. The stop function captures the memprofile. -func startProfiling() (func(), error) { - // start CPU profiling as early as possible - ofi, err := os.Create(cpuProfile) - if err != nil { - return nil, err - } - err = pprof.StartCPUProfile(ofi) - if err != nil { - ofi.Close() - return nil, err - } - go func() { - for range time.NewTicker(time.Second * 30).C { - err := writeHeapProfileToFile() - if err != nil { - log.Error(err) - } - } - }() - - stopProfiling := func() { - pprof.StopCPUProfile() - ofi.Close() // captured by the closure - } - return stopProfiling, nil -} - -func writeHeapProfileToFile() error { - mprof, err := os.Create(heapProfile) - if err != nil { - return err - } - defer mprof.Close() // _after_ writing the heap profile - return pprof.WriteHeapProfile(mprof) -} - -func profileIfEnabled() (func(), error) { - // FIXME this is a temporary hack so profiling of asynchronous operations - // works as intended. - if os.Getenv(EnvEnableProfiling) != "" { - stopProfilingFunc, err := startProfiling() // TODO maybe change this to its own option... profiling makes it slower. - if err != nil { - return nil, err - } - return stopProfilingFunc, nil - } - return func() {}, nil -} - -func resolveAddr(ctx context.Context, addr ma.Multiaddr) (ma.Multiaddr, error) { - ctx, cancelFunc := context.WithTimeout(ctx, 10*time.Second) - defer cancelFunc() - - addrs, err := dnsResolver.Resolve(ctx, addr) - if err != nil { - return nil, err - } - - if len(addrs) == 0 { - return nil, errors.New("non-resolvable API endpoint") - } - - return addrs[0], nil -} - -type nopWriter struct { - io.Writer -} - -func (nw nopWriter) Close() error { - return nil -} - -func getRemoteVersion(exe cmds.Executor) (*semver.Version, error) { - ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*30)) - defer cancel() - - req, err := cmds.NewRequest(ctx, []string{"version"}, nil, nil, nil, Root) - if err != nil { - return nil, err - } - - var buf bytes.Buffer - re, err := cmds.NewWriterResponseEmitter(nopWriter{&buf}, req) - if err != nil { - return nil, err - } - - err = exe.Execute(req, re, nil) - if err != nil { - return nil, err - } - - var out ipfs.VersionInfo - dec := json.NewDecoder(&buf) - if err := dec.Decode(&out); err != nil { - return nil, err - } - - return semver.New(out.Version) + os.Exit(kubo.Start(kubo.BuildDefaultEnv)) } diff --git a/cmd/ipfs/runmain_test.go b/cmd/ipfs/runmain_test.go index b09a2522640..a37ec194c74 100644 --- a/cmd/ipfs/runmain_test.go +++ b/cmd/ipfs/runmain_test.go @@ -1,13 +1,15 @@ //go:build testrunmain // +build testrunmain -package main +package main_test import ( "flag" "fmt" "os" "testing" + + "github.com/ipfs/kubo/cmd/ipfs/kubo" ) // this abuses go so much that I felt dirty writing this code @@ -16,7 +18,8 @@ import ( func TestRunMain(t *testing.T) { args := flag.Args() os.Args = append([]string{os.Args[0]}, args...) - ret := Start() + + ret := kubo.Start(kubo.BuildDefaultEnv) p := os.Getenv("IPFS_COVER_RET_FILE") if len(p) != 0 { From a8a12789f2f53849950ddcc1c28b940d84a01341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 30 Mar 2023 19:36:33 +0200 Subject: [PATCH 3/7] feat: cmd/ipfs: Nicer to use BuildEnv --- cmd/ipfs/kubo/start.go | 76 ++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 37 deletions(-) diff --git a/cmd/ipfs/kubo/start.go b/cmd/ipfs/kubo/start.go index 2a97fd49077..13fad8387ae 100644 --- a/cmd/ipfs/kubo/start.go +++ b/cmd/ipfs/kubo/start.go @@ -110,50 +110,52 @@ func newUUID(key string) logging.Metadata { } func BuildDefaultEnv(ctx context.Context, req *cmds.Request) (cmds.Environment, error) { - return BuildEnv(ctx, req, nil) + return BuildEnv(nil)(ctx, req) } -func BuildEnv(ctx context.Context, req *cmds.Request, pl PluginPreloader) (cmds.Environment, error) { - checkDebug(req) - repoPath, err := GetRepoPath(req) - if err != nil { - return nil, err - } - log.Debugf("config path is %s", repoPath) +func BuildEnv(pl PluginPreloader) func(ctx context.Context, req *cmds.Request) (cmds.Environment, error) { + return func(ctx context.Context, req *cmds.Request) (cmds.Environment, error) { + checkDebug(req) + repoPath, err := GetRepoPath(req) + if err != nil { + return nil, err + } + log.Debugf("config path is %s", repoPath) - plugins, err := LoadPlugins(repoPath, pl) - if err != nil { - return nil, err - } + plugins, err := LoadPlugins(repoPath, pl) + if err != nil { + return nil, err + } - // this sets up the function that will initialize the node - // this is so that we can construct the node lazily. - return &oldcmds.Context{ - ConfigRoot: repoPath, - ReqLog: &oldcmds.ReqLog{}, - Plugins: plugins, - ConstructNode: func() (n *core.IpfsNode, err error) { - if req == nil { - return nil, errors.New("constructing node without a request") - } + // this sets up the function that will initialize the node + // this is so that we can construct the node lazily. + return &oldcmds.Context{ + ConfigRoot: repoPath, + ReqLog: &oldcmds.ReqLog{}, + Plugins: plugins, + ConstructNode: func() (n *core.IpfsNode, err error) { + if req == nil { + return nil, errors.New("constructing node without a request") + } - r, err := fsrepo.Open(repoPath) - if err != nil { // repo is owned by the node - return nil, err - } + r, err := fsrepo.Open(repoPath) + if err != nil { // repo is owned by the node + return nil, err + } - // ok everything is good. set it on the invocation (for ownership) - // and return it. - n, err = core.NewNode(ctx, &core.BuildCfg{ - Repo: r, - }) - if err != nil { - return nil, err - } + // ok everything is good. set it on the invocation (for ownership) + // and return it. + n, err = core.NewNode(ctx, &core.BuildCfg{ + Repo: r, + }) + if err != nil { + return nil, err + } - return n, nil - }, - }, nil + return n, nil + }, + }, nil + } } func Start(buildEnv func(ctx context.Context, req *cmds.Request) (cmds.Environment, error)) (exitCode int) { From 0e83c3f2f964e9f26f25e77cb2fbf648c0fce5fa Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Tue, 14 Nov 2023 15:54:19 +0100 Subject: [PATCH 4/7] remove old main function --- cmd/ipfs/kubo/start.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/cmd/ipfs/kubo/start.go b/cmd/ipfs/kubo/start.go index 13fad8387ae..c60850d4bb2 100644 --- a/cmd/ipfs/kubo/start.go +++ b/cmd/ipfs/kubo/start.go @@ -84,16 +84,6 @@ func LoadPlugins(repoPath string, preload PluginPreloader) (*loader.PluginLoader return plugins, nil } -// main roadmap: -// - parse the commandline to get a cmdInvocation -// - if user requests help, print it and exit. -// - run the command invocation -// - output the response -// - if anything fails, print error, maybe with help. -func main() { - os.Exit(Start(BuildDefaultEnv)) -} - func printErr(err error) int { fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error()) return 1 @@ -158,6 +148,12 @@ func BuildEnv(pl PluginPreloader) func(ctx context.Context, req *cmds.Request) ( } } +// Start roadmap: +// - parse the commandline to get a cmdInvocation +// - if user requests help, print it and exit. +// - run the command invocation +// - output the response +// - if anything fails, print error, maybe with help. func Start(buildEnv func(ctx context.Context, req *cmds.Request) (cmds.Environment, error)) (exitCode int) { ctx := logging.ContextWithLoggable(context.Background(), newUUID("session")) From e503c843b8174382e69cdfdcca25f7caef9f462b Mon Sep 17 00:00:00 2001 From: Adin Schmahmann Date: Tue, 19 Dec 2023 19:52:02 -0500 Subject: [PATCH 5/7] feat: unexport unneeded functions and add comments --- cmd/ipfs/kubo/start.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cmd/ipfs/kubo/start.go b/cmd/ipfs/kubo/start.go index c60850d4bb2..cae1e2c1b70 100644 --- a/cmd/ipfs/kubo/start.go +++ b/cmd/ipfs/kubo/start.go @@ -1,4 +1,4 @@ -// cmd/ipfs implements the primary CLI binary for ipfs +// cmd/ipfs/kubo implements the primary CLI binary for kubo package kubo import ( @@ -62,7 +62,7 @@ const ( type PluginPreloader func(*loader.PluginLoader) error -func LoadPlugins(repoPath string, preload PluginPreloader) (*loader.PluginLoader, error) { +func loadPlugins(repoPath string, preload PluginPreloader) (*loader.PluginLoader, error) { plugins, err := loader.NewPluginLoader(repoPath) if err != nil { return nil, fmt.Errorf("error loading plugins: %s", err) @@ -103,16 +103,18 @@ func BuildDefaultEnv(ctx context.Context, req *cmds.Request) (cmds.Environment, return BuildEnv(nil)(ctx, req) } +// BuildEnv creates an environment to be used with the kubo CLI. Note: the plugin preloader should only call functions +// associated with preloaded plugins (i.e. Load). func BuildEnv(pl PluginPreloader) func(ctx context.Context, req *cmds.Request) (cmds.Environment, error) { return func(ctx context.Context, req *cmds.Request) (cmds.Environment, error) { checkDebug(req) - repoPath, err := GetRepoPath(req) + repoPath, err := getRepoPath(req) if err != nil { return nil, err } log.Debugf("config path is %s", repoPath) - plugins, err := LoadPlugins(repoPath, pl) + plugins, err := loadPlugins(repoPath, pl) if err != nil { return nil, err } @@ -374,7 +376,7 @@ func (twe tracingWrappedExecutor) Execute(req *cmds.Request, re cmds.ResponseEmi return err } -func GetRepoPath(req *cmds.Request) (string, error) { +func getRepoPath(req *cmds.Request) (string, error) { repoOpt, found := req.Options[corecmds.RepoDirOption].(string) if found && repoOpt != "" { return repoOpt, nil From 33b785ebd9f5c224f5c14b386800043f9549ccd2 Mon Sep 17 00:00:00 2001 From: Adin Schmahmann Date: Tue, 19 Dec 2023 16:52:07 -0500 Subject: [PATCH 6/7] docs(customizing.md): add kubo binary imports description --- docs/customizing.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/customizing.md b/docs/customizing.md index 4db3c3431ad..0f078999feb 100644 --- a/docs/customizing.md +++ b/docs/customizing.md @@ -36,6 +36,12 @@ For more information about the different types of Kubo plugins, see [plugins.md] Kubo plugins can also be injected at runtime using Go plugins (see below), but these are hard to use and not well supported by Go, so we don't recommend them. +### Kubo binary imports + +It is possible to depend on the package `cmd/ipfs/kubo` as a way of using Kubo plugins that is an alternative to recompiling Kubo with additional preloaded plugins. + +This gives a more Go-centric dependency updating flow to building a new binary with preloaded plugins by simply requiring updating a Kubo dependency rather than needing to update Kubo source code and recompile. + ## Bespoke Extension Points Certain Kubo functionality may have their own extension points. For example: From 287444bc6e56ac9bdcbb797c75ce51d0e584aea3 Mon Sep 17 00:00:00 2001 From: Adin Schmahmann Date: Tue, 19 Dec 2023 15:43:03 -0500 Subject: [PATCH 7/7] chore: update changelog --- docs/changelogs/v0.26.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/changelogs/v0.26.md b/docs/changelogs/v0.26.md index a5d02df639c..3a9f088cac6 100644 --- a/docs/changelogs/v0.26.md +++ b/docs/changelogs/v0.26.md @@ -14,6 +14,12 @@ ### ๐Ÿ”ฆ Highlights +#### Kubo binary imports + +For users of [Kubo preloaded plugins](https://github.com/ipfs/kubo/blob/master/docs/plugins.md#preloaded-plugins) there is now a way to create a kubo instance with your plugins by depending on the `cmd/ipfs/kubo` package rather than rebuilding kubo with the included plugins. + +See the [customization docs](https://github.com/ipfs/kubo/blob/master/docs/customizing.md) for more information. + #### Several deprecated commands have been removed Several deprecated commands have been removed: @@ -26,4 +32,6 @@ Several deprecated commands have been removed: ### ๐Ÿ“ Changelog +- Export a `kubo.Start` function so users can programmatically start Kubo from within a go program. + ### ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Contributors