@cryptotaxi247 / kubo / commits / b5dddf67a

feat: cmd/ipfs: Make it possible to depend on cmd/ipfs

Łukasz Magiera committed Mar 30, 2023 at 19:25 UTC b5dddf67a23d597c63863e3401b31b0f8f31a047
13 files changed +511 -497
cmd/ipfs/kubo/add_migrations.go renamed
+1 -1
@@ -1,4 +1,4 @@
1 -package main
1 +package kubo
2
3 import (
4 "context"
cmd/ipfs/kubo/daemon.go renamed
+1 -1
@@ -1,4 +1,4 @@
1 -package main
1 +package kubo
2
3 import (
4 "errors"
cmd/ipfs/kubo/daemon_linux.go renamed
+1 -1
@@ -1,7 +1,7 @@
1 //go:build linux
2 // +build linux
3
4 -package main
4 +package kubo
5
6 import (
7 daemon "github.com/coreos/go-systemd/v22/daemon"
cmd/ipfs/kubo/daemon_other.go renamed
+1 -1
@@ -1,7 +1,7 @@
1 //go:build !linux
2 // +build !linux
3
4 -package main
4 +package kubo
5
6 func notifyReady() {}
7
cmd/ipfs/kubo/debug.go renamed
+1 -1
@@ -1,4 +1,4 @@
1 -package main
1 +package kubo
2
3 import (
4 "net/http"
cmd/ipfs/kubo/dnsresolve_test.go renamed
+1 -1
@@ -1,4 +1,4 @@
1 -package main
1 +package kubo
2
3 import (
4 "context"
cmd/ipfs/kubo/init.go renamed
+1 -1
@@ -1,4 +1,4 @@
1 -package main
1 +package kubo
2
3 import (
4 "context"
cmd/ipfs/kubo/ipfs.go renamed
+1 -1
@@ -1,4 +1,4 @@
1 -package main
1 +package kubo
2
3 import (
4 commands "github.com/ipfs/kubo/core/commands"
cmd/ipfs/kubo/pinmfs.go renamed
+1 -1
@@ -1,4 +1,4 @@
1 -package main
1 +package kubo
2
3 import (
4 "context"
cmd/ipfs/kubo/pinmfs_test.go renamed
+1 -1
@@ -1,4 +1,4 @@
1 -package main
1 +package kubo
2
3 import (
4 "context"
cmd/ipfs/kubo/start.go new
+494
@@ -0,0 +1,494 @@
1 +// cmd/ipfs implements the primary CLI binary for ipfs
2 +package kubo
3 +
4 +import (
5 + "bytes"
6 + "context"
7 + "encoding/json"
8 + "errors"
9 + "fmt"
10 + "io"
11 + "net"
12 + "net/http"
13 + "os"
14 + "runtime/pprof"
15 + "strings"
16 + "time"
17 +
18 + "github.com/blang/semver/v4"
19 + "github.com/google/uuid"
20 + u "github.com/ipfs/boxo/util"
21 + cmds "github.com/ipfs/go-ipfs-cmds"
22 + "github.com/ipfs/go-ipfs-cmds/cli"
23 + cmdhttp "github.com/ipfs/go-ipfs-cmds/http"
24 + logging "github.com/ipfs/go-log"
25 + ipfs "github.com/ipfs/kubo"
26 + "github.com/ipfs/kubo/client/rpc/auth"
27 + "github.com/ipfs/kubo/cmd/ipfs/util"
28 + oldcmds "github.com/ipfs/kubo/commands"
29 + config "github.com/ipfs/kubo/config"
30 + "github.com/ipfs/kubo/core"
31 + corecmds "github.com/ipfs/kubo/core/commands"
32 + "github.com/ipfs/kubo/core/corehttp"
33 + "github.com/ipfs/kubo/plugin/loader"
34 + "github.com/ipfs/kubo/repo"
35 + "github.com/ipfs/kubo/repo/fsrepo"
36 + "github.com/ipfs/kubo/tracing"
37 + ma "github.com/multiformats/go-multiaddr"
38 + madns "github.com/multiformats/go-multiaddr-dns"
39 + manet "github.com/multiformats/go-multiaddr/net"
40 + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
41 + "go.opentelemetry.io/contrib/propagators/autoprop"
42 + "go.opentelemetry.io/otel"
43 + "go.opentelemetry.io/otel/attribute"
44 + "go.opentelemetry.io/otel/codes"
45 + "go.opentelemetry.io/otel/trace"
46 +)
47 +
48 +// log is the command logger.
49 +var (
50 + log = logging.Logger("cmd/ipfs")
51 + tracer trace.Tracer
52 +)
53 +
54 +// declared as a var for testing purposes.
55 +var dnsResolver = madns.DefaultResolver
56 +
57 +const (
58 + EnvEnableProfiling = "IPFS_PROF"
59 + cpuProfile = "ipfs.cpuprof"
60 + heapProfile = "ipfs.memprof"
61 +)
62 +
63 +type PluginPreloader func(*loader.PluginLoader) error
64 +
65 +func LoadPlugins(repoPath string, preload PluginPreloader) (*loader.PluginLoader, error) {
66 + plugins, err := loader.NewPluginLoader(repoPath)
67 + if err != nil {
68 + return nil, fmt.Errorf("error loading plugins: %s", err)
69 + }
70 +
71 + if preload != nil {
72 + if err := preload(plugins); err != nil {
73 + return nil, fmt.Errorf("error loading plugins (preload): %s", err)
74 + }
75 + }
76 +
77 + if err := plugins.Initialize(); err != nil {
78 + return nil, fmt.Errorf("error initializing plugins: %s", err)
79 + }
80 +
81 + if err := plugins.Inject(); err != nil {
82 + return nil, fmt.Errorf("error initializing plugins: %s", err)
83 + }
84 + return plugins, nil
85 +}
86 +
87 +// main roadmap:
88 +// - parse the commandline to get a cmdInvocation
89 +// - if user requests help, print it and exit.
90 +// - run the command invocation
91 +// - output the response
92 +// - if anything fails, print error, maybe with help.
93 +func main() {
94 + os.Exit(Start(BuildDefaultEnv))
95 +}
96 +
97 +func printErr(err error) int {
98 + fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
99 + return 1
100 +}
101 +
102 +func newUUID(key string) logging.Metadata {
103 + ids := "#UUID-ERROR#"
104 + if id, err := uuid.NewRandom(); err == nil {
105 + ids = id.String()
106 + }
107 + return logging.Metadata{
108 + key: ids,
109 + }
110 +}
111 +
112 +func BuildDefaultEnv(ctx context.Context, req *cmds.Request) (cmds.Environment, error) {
113 + return BuildEnv(ctx, req, nil)
114 +}
115 +
116 +func BuildEnv(ctx context.Context, req *cmds.Request, pl PluginPreloader) (cmds.Environment, error) {
117 + checkDebug(req)
118 + repoPath, err := GetRepoPath(req)
119 + if err != nil {
120 + return nil, err
121 + }
122 + log.Debugf("config path is %s", repoPath)
123 +
124 + plugins, err := LoadPlugins(repoPath, pl)
125 + if err != nil {
126 + return nil, err
127 + }
128 +
129 + // this sets up the function that will initialize the node
130 + // this is so that we can construct the node lazily.
131 + return &oldcmds.Context{
132 + ConfigRoot: repoPath,
133 + ReqLog: &oldcmds.ReqLog{},
134 + Plugins: plugins,
135 + ConstructNode: func() (n *core.IpfsNode, err error) {
136 + if req == nil {
137 + return nil, errors.New("constructing node without a request")
138 + }
139 +
140 + r, err := fsrepo.Open(repoPath)
141 + if err != nil { // repo is owned by the node
142 + return nil, err
143 + }
144 +
145 + // ok everything is good. set it on the invocation (for ownership)
146 + // and return it.
147 + n, err = core.NewNode(ctx, &core.BuildCfg{
148 + Repo: r,
149 + })
150 + if err != nil {
151 + return nil, err
152 + }
153 +
154 + return n, nil
155 + },
156 + }, nil
157 +}
158 +
159 +func Start(buildEnv func(ctx context.Context, req *cmds.Request) (cmds.Environment, error)) (exitCode int) {
160 + ctx := logging.ContextWithLoggable(context.Background(), newUUID("session"))
161 +
162 + tp, err := tracing.NewTracerProvider(ctx)
163 + if err != nil {
164 + return printErr(err)
165 + }
166 + defer func() {
167 + if err := tp.Shutdown(ctx); err != nil {
168 + exitCode = printErr(err)
169 + }
170 + }()
171 + otel.SetTracerProvider(tp)
172 + otel.SetTextMapPropagator(autoprop.NewTextMapPropagator())
173 + tracer = tp.Tracer("Kubo-cli")
174 +
175 + stopFunc, err := profileIfEnabled()
176 + if err != nil {
177 + return printErr(err)
178 + }
179 + defer stopFunc() // to be executed as late as possible
180 +
181 + intrh, ctx := util.SetupInterruptHandler(ctx)
182 + defer intrh.Close()
183 +
184 + // Handle `ipfs version` or `ipfs help`
185 + if len(os.Args) > 1 {
186 + // Handle `ipfs --version'
187 + if os.Args[1] == "--version" {
188 + os.Args[1] = "version"
189 + }
190 +
191 + // Handle `ipfs help` and `ipfs help <sub-command>`
192 + if os.Args[1] == "help" {
193 + if len(os.Args) > 2 {
194 + os.Args = append(os.Args[:1], os.Args[2:]...)
195 + // Handle `ipfs help --help`
196 + // append `--help`,when the command is not `ipfs help --help`
197 + if os.Args[1] != "--help" {
198 + os.Args = append(os.Args, "--help")
199 + }
200 + } else {
201 + os.Args[1] = "--help"
202 + }
203 + }
204 + } else if insideGUI() { // if no args were passed, and we're in a GUI environment
205 + // launch the daemon instead of launching a ghost window
206 + os.Args = append(os.Args, "daemon", "--init")
207 + }
208 +
209 + // output depends on executable name passed in os.Args
210 + // so we need to make sure it's stable
211 + os.Args[0] = "ipfs"
212 +
213 + err = cli.Run(ctx, Root, os.Args, os.Stdin, os.Stdout, os.Stderr, buildEnv, makeExecutor)
214 + if err != nil {
215 + return 1
216 + }
217 +
218 + // everything went better than expected :)
219 + return 0
220 +}
221 +
222 +func insideGUI() bool {
223 + return util.InsideGUI()
224 +}
225 +
226 +func checkDebug(req *cmds.Request) {
227 + // check if user wants to debug. option OR env var.
228 + debug, _ := req.Options["debug"].(bool)
229 + if debug || os.Getenv("IPFS_LOGGING") == "debug" {
230 + u.Debug = true
231 + logging.SetDebugLogging()
232 + }
233 + if u.GetenvBool("DEBUG") {
234 + u.Debug = true
235 + }
236 +}
237 +
238 +func apiAddrOption(req *cmds.Request) (ma.Multiaddr, error) {
239 + apiAddrStr, apiSpecified := req.Options[corecmds.ApiOption].(string)
240 + if !apiSpecified {
241 + return nil, nil
242 + }
243 + return ma.NewMultiaddr(apiAddrStr)
244 +}
245 +
246 +// encodedAbsolutePathVersion is the version from which the absolute path header in
247 +// multipart requests is %-encoded. Before this version, its sent raw.
248 +var encodedAbsolutePathVersion = semver.MustParse("0.23.0-dev")
249 +
250 +func makeExecutor(req *cmds.Request, env interface{}) (cmds.Executor, error) {
251 + exe := tracingWrappedExecutor{cmds.NewExecutor(req.Root)}
252 + cctx := env.(*oldcmds.Context)
253 +
254 + // Check if the command is disabled.
255 + if req.Command.NoLocal && req.Command.NoRemote {
256 + return nil, fmt.Errorf("command disabled: %v", req.Path)
257 + }
258 +
259 + // Can we just run this locally?
260 + if !req.Command.NoLocal {
261 + if doesNotUseRepo, ok := corecmds.GetDoesNotUseRepo(req.Command.Extra); doesNotUseRepo && ok {
262 + return exe, nil
263 + }
264 + }
265 +
266 + // Get the API option from the commandline.
267 + apiAddr, err := apiAddrOption(req)
268 + if err != nil {
269 + return nil, err
270 + }
271 +
272 + // Require that the command be run on the daemon when the API flag is
273 + // passed (unless we're trying to _run_ the daemon).
274 + daemonRequested := apiAddr != nil && req.Command != daemonCmd
275 +
276 + // Run this on the client if required.
277 + if req.Command.NoRemote {
278 + if daemonRequested {
279 + // User requested that the command be run on the daemon but we can't.
280 + // NOTE: We drop this check for the `ipfs daemon` command.
281 + return nil, errors.New("api flag specified but command cannot be run on the daemon")
282 + }
283 + return exe, nil
284 + }
285 +
286 + // Finally, look in the repo for an API file.
287 + if apiAddr == nil {
288 + var err error
289 + apiAddr, err = fsrepo.APIAddr(cctx.ConfigRoot)
290 + switch err {
291 + case nil, repo.ErrApiNotRunning:
292 + default:
293 + return nil, err
294 + }
295 + }
296 +
297 + // Still no api specified? Run it on the client or fail.
298 + if apiAddr == nil {
299 + if req.Command.NoLocal {
300 + return nil, fmt.Errorf("command must be run on the daemon: %v", req.Path)
301 + }
302 + return exe, nil
303 + }
304 +
305 + // Resolve the API addr.
306 + apiAddr, err = resolveAddr(req.Context, apiAddr)
307 + if err != nil {
308 + return nil, err
309 + }
310 + network, host, err := manet.DialArgs(apiAddr)
311 + if err != nil {
312 + return nil, err
313 + }
314 +
315 + // Construct the executor.
316 + opts := []cmdhttp.ClientOpt{
317 + cmdhttp.ClientWithAPIPrefix(corehttp.APIPath),
318 + }
319 +
320 + // Fallback on a local executor if we (a) have a repo and (b) aren't
321 + // forcing a daemon.
322 + if !daemonRequested && fsrepo.IsInitialized(cctx.ConfigRoot) {
323 + opts = append(opts, cmdhttp.ClientWithFallback(exe))
324 + }
325 +
326 + var tpt http.RoundTripper
327 + switch network {
328 + case "tcp", "tcp4", "tcp6":
329 + tpt = http.DefaultTransport
330 + case "unix":
331 + path := host
332 + host = "unix"
333 + tpt = &http.Transport{
334 + DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
335 + return net.Dial("unix", path)
336 + },
337 + }
338 + default:
339 + return nil, fmt.Errorf("unsupported API address: %s", apiAddr)
340 + }
341 +
342 + apiAuth, specified := req.Options[corecmds.ApiAuthOption].(string)
343 + if specified {
344 + authorization := config.ConvertAuthSecret(apiAuth)
345 + tpt = auth.NewAuthorizedRoundTripper(authorization, tpt)
346 + }
347 +
348 + httpClient := &http.Client{
349 + Transport: otelhttp.NewTransport(tpt),
350 + }
351 + opts = append(opts, cmdhttp.ClientWithHTTPClient(httpClient))
352 +
353 + // Fetch remove version, as some feature compatibility might change depending on it.
354 + remoteVersion, err := getRemoteVersion(tracingWrappedExecutor{cmdhttp.NewClient(host, opts...)})
355 + if err != nil {
356 + return nil, err
357 + }
358 + opts = append(opts, cmdhttp.ClientWithRawAbsPath(remoteVersion.LT(encodedAbsolutePathVersion)))
359 +
360 + return tracingWrappedExecutor{cmdhttp.NewClient(host, opts...)}, nil
361 +}
362 +
363 +type tracingWrappedExecutor struct {
364 + exec cmds.Executor
365 +}
366 +
367 +func (twe tracingWrappedExecutor) Execute(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) error {
368 + ctx, span := tracer.Start(req.Context, "cmds."+strings.Join(req.Path, "."), trace.WithAttributes(attribute.StringSlice("Arguments", req.Arguments)))
369 + defer span.End()
370 + req.Context = ctx
371 +
372 + err := twe.exec.Execute(req, re, env)
373 + if err != nil {
374 + span.SetStatus(codes.Error, err.Error())
375 + }
376 + return err
377 +}
378 +
379 +func GetRepoPath(req *cmds.Request) (string, error) {
380 + repoOpt, found := req.Options[corecmds.RepoDirOption].(string)
381 + if found && repoOpt != "" {
382 + return repoOpt, nil
383 + }
384 +
385 + repoPath, err := fsrepo.BestKnownPath()
386 + if err != nil {
387 + return "", err
388 + }
389 + return repoPath, nil
390 +}
391 +
392 +// startProfiling begins CPU profiling and returns a `stop` function to be
393 +// executed as late as possible. The stop function captures the memprofile.
394 +func startProfiling() (func(), error) {
395 + // start CPU profiling as early as possible
396 + ofi, err := os.Create(cpuProfile)
397 + if err != nil {
398 + return nil, err
399 + }
400 + err = pprof.StartCPUProfile(ofi)
401 + if err != nil {
402 + ofi.Close()
403 + return nil, err
404 + }
405 + go func() {
406 + for range time.NewTicker(time.Second * 30).C {
407 + err := writeHeapProfileToFile()
408 + if err != nil {
409 + log.Error(err)
410 + }
411 + }
412 + }()
413 +
414 + stopProfiling := func() {
415 + pprof.StopCPUProfile()
416 + ofi.Close() // captured by the closure
417 + }
418 + return stopProfiling, nil
419 +}
420 +
421 +func writeHeapProfileToFile() error {
422 + mprof, err := os.Create(heapProfile)
423 + if err != nil {
424 + return err
425 + }
426 + defer mprof.Close() // _after_ writing the heap profile
427 + return pprof.WriteHeapProfile(mprof)
428 +}
429 +
430 +func profileIfEnabled() (func(), error) {
431 + // FIXME this is a temporary hack so profiling of asynchronous operations
432 + // works as intended.
433 + if os.Getenv(EnvEnableProfiling) != "" {
434 + stopProfilingFunc, err := startProfiling() // TODO maybe change this to its own option... profiling makes it slower.
435 + if err != nil {
436 + return nil, err
437 + }
438 + return stopProfilingFunc, nil
439 + }
440 + return func() {}, nil
441 +}
442 +
443 +func resolveAddr(ctx context.Context, addr ma.Multiaddr) (ma.Multiaddr, error) {
444 + ctx, cancelFunc := context.WithTimeout(ctx, 10*time.Second)
445 + defer cancelFunc()
446 +
447 + addrs, err := dnsResolver.Resolve(ctx, addr)
448 + if err != nil {
449 + return nil, err
450 + }
451 +
452 + if len(addrs) == 0 {
453 + return nil, errors.New("non-resolvable API endpoint")
454 + }
455 +
456 + return addrs[0], nil
457 +}
458 +
459 +type nopWriter struct {
460 + io.Writer
461 +}
462 +
463 +func (nw nopWriter) Close() error {
464 + return nil
465 +}
466 +
467 +func getRemoteVersion(exe cmds.Executor) (*semver.Version, error) {
468 + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*30))
469 + defer cancel()
470 +
471 + req, err := cmds.NewRequest(ctx, []string{"version"}, nil, nil, nil, Root)
472 + if err != nil {
473 + return nil, err
474 + }
475 +
476 + var buf bytes.Buffer
477 + re, err := cmds.NewWriterResponseEmitter(nopWriter{&buf}, req)
478 + if err != nil {
479 + return nil, err
480 + }
481 +
482 + err = exe.Execute(req, re, nil)
483 + if err != nil {
484 + return nil, err
485 + }
486 +
487 + var out ipfs.VersionInfo
488 + dec := json.NewDecoder(&buf)
489 + if err := dec.Decode(&out); err != nil {
490 + return nil, err
491 + }
492 +
493 + return semver.New(out.Version)
494 +}
cmd/ipfs/main.go
+2 -485
@@ -1,494 +1,11 @@
1 -// cmd/ipfs implements the primary CLI binary for ipfs
1 package main
2
3 import (
5 - "bytes"
6 - "context"
7 - "encoding/json"
8 - "errors"
9 - "fmt"
10 - "io"
11 - "net"
12 - "net/http"
4 "os"
14 - "runtime/pprof"
15 - "strings"
16 - "time"
5
18 - "github.com/blang/semver/v4"
19 - "github.com/google/uuid"
20 - u "github.com/ipfs/boxo/util"
21 - cmds "github.com/ipfs/go-ipfs-cmds"
22 - "github.com/ipfs/go-ipfs-cmds/cli"
23 - cmdhttp "github.com/ipfs/go-ipfs-cmds/http"
24 - logging "github.com/ipfs/go-log"
25 - ipfs "github.com/ipfs/kubo"
26 - "github.com/ipfs/kubo/client/rpc/auth"
27 - "github.com/ipfs/kubo/cmd/ipfs/util"
28 - oldcmds "github.com/ipfs/kubo/commands"
29 - config "github.com/ipfs/kubo/config"
30 - "github.com/ipfs/kubo/core"
31 - corecmds "github.com/ipfs/kubo/core/commands"
32 - "github.com/ipfs/kubo/core/corehttp"
33 - "github.com/ipfs/kubo/plugin/loader"
34 - "github.com/ipfs/kubo/repo"
35 - "github.com/ipfs/kubo/repo/fsrepo"
36 - "github.com/ipfs/kubo/tracing"
37 - ma "github.com/multiformats/go-multiaddr"
38 - madns "github.com/multiformats/go-multiaddr-dns"
39 - manet "github.com/multiformats/go-multiaddr/net"
40 - "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
41 - "go.opentelemetry.io/contrib/propagators/autoprop"
42 - "go.opentelemetry.io/otel"
43 - "go.opentelemetry.io/otel/attribute"
44 - "go.opentelemetry.io/otel/codes"
45 - "go.opentelemetry.io/otel/trace"
6 + "github.com/ipfs/kubo/cmd/ipfs/kubo"
7 )
8
48 -// log is the command logger.
49 -var (
50 - log = logging.Logger("cmd/ipfs")
51 - tracer trace.Tracer
52 -)
53 -
54 -// declared as a var for testing purposes.
55 -var dnsResolver = madns.DefaultResolver
56 -
57 -const (
58 - EnvEnableProfiling = "IPFS_PROF"
59 - cpuProfile = "ipfs.cpuprof"
60 - heapProfile = "ipfs.memprof"
61 -)
62 -
63 -type PluginPreloader func(*loader.PluginLoader) error
64 -
65 -func LoadPlugins(repoPath string, preload PluginPreloader) (*loader.PluginLoader, error) {
66 - plugins, err := loader.NewPluginLoader(repoPath)
67 - if err != nil {
68 - return nil, fmt.Errorf("error loading plugins: %s", err)
69 - }
70 -
71 - if preload != nil {
72 - if err := preload(plugins); err != nil {
73 - return nil, fmt.Errorf("error loading plugins (preload): %s", err)
74 - }
75 - }
76 -
77 - if err := plugins.Initialize(); err != nil {
78 - return nil, fmt.Errorf("error initializing plugins: %s", err)
79 - }
80 -
81 - if err := plugins.Inject(); err != nil {
82 - return nil, fmt.Errorf("error initializing plugins: %s", err)
83 - }
84 - return plugins, nil
85 -}
86 -
87 -// main roadmap:
88 -// - parse the commandline to get a cmdInvocation
89 -// - if user requests help, print it and exit.
90 -// - run the command invocation
91 -// - output the response
92 -// - if anything fails, print error, maybe with help.
9 func main() {
94 - os.Exit(Start(BuildDefaultEnv))
95 -}
96 -
97 -func printErr(err error) int {
98 - fmt.Fprintf(os.Stderr, "Error: %s\n", err.Error())
99 - return 1
100 -}
101 -
102 -func newUUID(key string) logging.Metadata {
103 - ids := "#UUID-ERROR#"
104 - if id, err := uuid.NewRandom(); err == nil {
105 - ids = id.String()
106 - }
107 - return logging.Metadata{
108 - key: ids,
109 - }
110 -}
111 -
112 -func BuildDefaultEnv(ctx context.Context, req *cmds.Request) (cmds.Environment, error) {
113 - return BuildEnv(ctx, req, nil)
114 -}
115 -
116 -func BuildEnv(ctx context.Context, req *cmds.Request, pl PluginPreloader) (cmds.Environment, error) {
117 - checkDebug(req)
118 - repoPath, err := GetRepoPath(req)
119 - if err != nil {
120 - return nil, err
121 - }
122 - log.Debugf("config path is %s", repoPath)
123 -
124 - plugins, err := LoadPlugins(repoPath, pl)
125 - if err != nil {
126 - return nil, err
127 - }
128 -
129 - // this sets up the function that will initialize the node
130 - // this is so that we can construct the node lazily.
131 - return &oldcmds.Context{
132 - ConfigRoot: repoPath,
133 - ReqLog: &oldcmds.ReqLog{},
134 - Plugins: plugins,
135 - ConstructNode: func() (n *core.IpfsNode, err error) {
136 - if req == nil {
137 - return nil, errors.New("constructing node without a request")
138 - }
139 -
140 - r, err := fsrepo.Open(repoPath)
141 - if err != nil { // repo is owned by the node
142 - return nil, err
143 - }
144 -
145 - // ok everything is good. set it on the invocation (for ownership)
146 - // and return it.
147 - n, err = core.NewNode(ctx, &core.BuildCfg{
148 - Repo: r,
149 - })
150 - if err != nil {
151 - return nil, err
152 - }
153 -
154 - return n, nil
155 - },
156 - }, nil
157 -}
158 -
159 -func Start(buildEnv func(ctx context.Context, req *cmds.Request) (cmds.Environment, error)) (exitCode int) {
160 - ctx := logging.ContextWithLoggable(context.Background(), newUUID("session"))
161 -
162 - tp, err := tracing.NewTracerProvider(ctx)
163 - if err != nil {
164 - return printErr(err)
165 - }
166 - defer func() {
167 - if err := tp.Shutdown(ctx); err != nil {
168 - exitCode = printErr(err)
169 - }
170 - }()
171 - otel.SetTracerProvider(tp)
172 - otel.SetTextMapPropagator(autoprop.NewTextMapPropagator())
173 - tracer = tp.Tracer("Kubo-cli")
174 -
175 - stopFunc, err := profileIfEnabled()
176 - if err != nil {
177 - return printErr(err)
178 - }
179 - defer stopFunc() // to be executed as late as possible
180 -
181 - intrh, ctx := util.SetupInterruptHandler(ctx)
182 - defer intrh.Close()
183 -
184 - // Handle `ipfs version` or `ipfs help`
185 - if len(os.Args) > 1 {
186 - // Handle `ipfs --version'
187 - if os.Args[1] == "--version" {
188 - os.Args[1] = "version"
189 - }
190 -
191 - // Handle `ipfs help` and `ipfs help <sub-command>`
192 - if os.Args[1] == "help" {
193 - if len(os.Args) > 2 {
194 - os.Args = append(os.Args[:1], os.Args[2:]...)
195 - // Handle `ipfs help --help`
196 - // append `--help`,when the command is not `ipfs help --help`
197 - if os.Args[1] != "--help" {
198 - os.Args = append(os.Args, "--help")
199 - }
200 - } else {
201 - os.Args[1] = "--help"
202 - }
203 - }
204 - } else if insideGUI() { // if no args were passed, and we're in a GUI environment
205 - // launch the daemon instead of launching a ghost window
206 - os.Args = append(os.Args, "daemon", "--init")
207 - }
208 -
209 - // output depends on executable name passed in os.Args
210 - // so we need to make sure it's stable
211 - os.Args[0] = "ipfs"
212 -
213 - err = cli.Run(ctx, Root, os.Args, os.Stdin, os.Stdout, os.Stderr, buildEnv, makeExecutor)
214 - if err != nil {
215 - return 1
216 - }
217 -
218 - // everything went better than expected :)
219 - return 0
220 -}
221 -
222 -func insideGUI() bool {
223 - return util.InsideGUI()
224 -}
225 -
226 -func checkDebug(req *cmds.Request) {
227 - // check if user wants to debug. option OR env var.
228 - debug, _ := req.Options["debug"].(bool)
229 - if debug || os.Getenv("IPFS_LOGGING") == "debug" {
230 - u.Debug = true
231 - logging.SetDebugLogging()
232 - }
233 - if u.GetenvBool("DEBUG") {
234 - u.Debug = true
235 - }
236 -}
237 -
238 -func apiAddrOption(req *cmds.Request) (ma.Multiaddr, error) {
239 - apiAddrStr, apiSpecified := req.Options[corecmds.ApiOption].(string)
240 - if !apiSpecified {
241 - return nil, nil
242 - }
243 - return ma.NewMultiaddr(apiAddrStr)
244 -}
245 -
246 -// encodedAbsolutePathVersion is the version from which the absolute path header in
247 -// multipart requests is %-encoded. Before this version, its sent raw.
248 -var encodedAbsolutePathVersion = semver.MustParse("0.23.0-dev")
249 -
250 -func makeExecutor(req *cmds.Request, env interface{}) (cmds.Executor, error) {
251 - exe := tracingWrappedExecutor{cmds.NewExecutor(req.Root)}
252 - cctx := env.(*oldcmds.Context)
253 -
254 - // Check if the command is disabled.
255 - if req.Command.NoLocal && req.Command.NoRemote {
256 - return nil, fmt.Errorf("command disabled: %v", req.Path)
257 - }
258 -
259 - // Can we just run this locally?
260 - if !req.Command.NoLocal {
261 - if doesNotUseRepo, ok := corecmds.GetDoesNotUseRepo(req.Command.Extra); doesNotUseRepo && ok {
262 - return exe, nil
263 - }
264 - }
265 -
266 - // Get the API option from the commandline.
267 - apiAddr, err := apiAddrOption(req)
268 - if err != nil {
269 - return nil, err
270 - }
271 -
272 - // Require that the command be run on the daemon when the API flag is
273 - // passed (unless we're trying to _run_ the daemon).
274 - daemonRequested := apiAddr != nil && req.Command != daemonCmd
275 -
276 - // Run this on the client if required.
277 - if req.Command.NoRemote {
278 - if daemonRequested {
279 - // User requested that the command be run on the daemon but we can't.
280 - // NOTE: We drop this check for the `ipfs daemon` command.
281 - return nil, errors.New("api flag specified but command cannot be run on the daemon")
282 - }
283 - return exe, nil
284 - }
285 -
286 - // Finally, look in the repo for an API file.
287 - if apiAddr == nil {
288 - var err error
289 - apiAddr, err = fsrepo.APIAddr(cctx.ConfigRoot)
290 - switch err {
291 - case nil, repo.ErrApiNotRunning:
292 - default:
293 - return nil, err
294 - }
295 - }
296 -
297 - // Still no api specified? Run it on the client or fail.
298 - if apiAddr == nil {
299 - if req.Command.NoLocal {
300 - return nil, fmt.Errorf("command must be run on the daemon: %v", req.Path)
301 - }
302 - return exe, nil
303 - }
304 -
305 - // Resolve the API addr.
306 - apiAddr, err = resolveAddr(req.Context, apiAddr)
307 - if err != nil {
308 - return nil, err
309 - }
310 - network, host, err := manet.DialArgs(apiAddr)
311 - if err != nil {
312 - return nil, err
313 - }
314 -
315 - // Construct the executor.
316 - opts := []cmdhttp.ClientOpt{
317 - cmdhttp.ClientWithAPIPrefix(corehttp.APIPath),
318 - }
319 -
320 - // Fallback on a local executor if we (a) have a repo and (b) aren't
321 - // forcing a daemon.
322 - if !daemonRequested && fsrepo.IsInitialized(cctx.ConfigRoot) {
323 - opts = append(opts, cmdhttp.ClientWithFallback(exe))
324 - }
325 -
326 - var tpt http.RoundTripper
327 - switch network {
328 - case "tcp", "tcp4", "tcp6":
329 - tpt = http.DefaultTransport
330 - case "unix":
331 - path := host
332 - host = "unix"
333 - tpt = &http.Transport{
334 - DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
335 - return net.Dial("unix", path)
336 - },
337 - }
338 - default:
339 - return nil, fmt.Errorf("unsupported API address: %s", apiAddr)
340 - }
341 -
342 - apiAuth, specified := req.Options[corecmds.ApiAuthOption].(string)
343 - if specified {
344 - authorization := config.ConvertAuthSecret(apiAuth)
345 - tpt = auth.NewAuthorizedRoundTripper(authorization, tpt)
346 - }
347 -
348 - httpClient := &http.Client{
349 - Transport: otelhttp.NewTransport(tpt),
350 - }
351 - opts = append(opts, cmdhttp.ClientWithHTTPClient(httpClient))
352 -
353 - // Fetch remove version, as some feature compatibility might change depending on it.
354 - remoteVersion, err := getRemoteVersion(tracingWrappedExecutor{cmdhttp.NewClient(host, opts...)})
355 - if err != nil {
356 - return nil, err
357 - }
358 - opts = append(opts, cmdhttp.ClientWithRawAbsPath(remoteVersion.LT(encodedAbsolutePathVersion)))
359 -
360 - return tracingWrappedExecutor{cmdhttp.NewClient(host, opts...)}, nil
361 -}
362 -
363 -type tracingWrappedExecutor struct {
364 - exec cmds.Executor
365 -}
366 -
367 -func (twe tracingWrappedExecutor) Execute(req *cmds.Request, re cmds.ResponseEmitter, env cmds.Environment) error {
368 - ctx, span := tracer.Start(req.Context, "cmds."+strings.Join(req.Path, "."), trace.WithAttributes(attribute.StringSlice("Arguments", req.Arguments)))
369 - defer span.End()
370 - req.Context = ctx
371 -
372 - err := twe.exec.Execute(req, re, env)
373 - if err != nil {
374 - span.SetStatus(codes.Error, err.Error())
375 - }
376 - return err
377 -}
378 -
379 -func GetRepoPath(req *cmds.Request) (string, error) {
380 - repoOpt, found := req.Options[corecmds.RepoDirOption].(string)
381 - if found && repoOpt != "" {
382 - return repoOpt, nil
383 - }
384 -
385 - repoPath, err := fsrepo.BestKnownPath()
386 - if err != nil {
387 - return "", err
388 - }
389 - return repoPath, nil
390 -}
391 -
392 -// startProfiling begins CPU profiling and returns a `stop` function to be
393 -// executed as late as possible. The stop function captures the memprofile.
394 -func startProfiling() (func(), error) {
395 - // start CPU profiling as early as possible
396 - ofi, err := os.Create(cpuProfile)
397 - if err != nil {
398 - return nil, err
399 - }
400 - err = pprof.StartCPUProfile(ofi)
401 - if err != nil {
402 - ofi.Close()
403 - return nil, err
404 - }
405 - go func() {
406 - for range time.NewTicker(time.Second * 30).C {
407 - err := writeHeapProfileToFile()
408 - if err != nil {
409 - log.Error(err)
410 - }
411 - }
412 - }()
413 -
414 - stopProfiling := func() {
415 - pprof.StopCPUProfile()
416 - ofi.Close() // captured by the closure
417 - }
418 - return stopProfiling, nil
419 -}
420 -
421 -func writeHeapProfileToFile() error {
422 - mprof, err := os.Create(heapProfile)
423 - if err != nil {
424 - return err
425 - }
426 - defer mprof.Close() // _after_ writing the heap profile
427 - return pprof.WriteHeapProfile(mprof)
428 -}
429 -
430 -func profileIfEnabled() (func(), error) {
431 - // FIXME this is a temporary hack so profiling of asynchronous operations
432 - // works as intended.
433 - if os.Getenv(EnvEnableProfiling) != "" {
434 - stopProfilingFunc, err := startProfiling() // TODO maybe change this to its own option... profiling makes it slower.
435 - if err != nil {
436 - return nil, err
437 - }
438 - return stopProfilingFunc, nil
439 - }
440 - return func() {}, nil
441 -}
442 -
443 -func resolveAddr(ctx context.Context, addr ma.Multiaddr) (ma.Multiaddr, error) {
444 - ctx, cancelFunc := context.WithTimeout(ctx, 10*time.Second)
445 - defer cancelFunc()
446 -
447 - addrs, err := dnsResolver.Resolve(ctx, addr)
448 - if err != nil {
449 - return nil, err
450 - }
451 -
452 - if len(addrs) == 0 {
453 - return nil, errors.New("non-resolvable API endpoint")
454 - }
455 -
456 - return addrs[0], nil
457 -}
458 -
459 -type nopWriter struct {
460 - io.Writer
461 -}
462 -
463 -func (nw nopWriter) Close() error {
464 - return nil
465 -}
466 -
467 -func getRemoteVersion(exe cmds.Executor) (*semver.Version, error) {
468 - ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*30))
469 - defer cancel()
470 -
471 - req, err := cmds.NewRequest(ctx, []string{"version"}, nil, nil, nil, Root)
472 - if err != nil {
473 - return nil, err
474 - }
475 -
476 - var buf bytes.Buffer
477 - re, err := cmds.NewWriterResponseEmitter(nopWriter{&buf}, req)
478 - if err != nil {
479 - return nil, err
480 - }
481 -
482 - err = exe.Execute(req, re, nil)
483 - if err != nil {
484 - return nil, err
485 - }
486 -
487 - var out ipfs.VersionInfo
488 - dec := json.NewDecoder(&buf)
489 - if err := dec.Decode(&out); err != nil {
490 - return nil, err
491 - }
492 -
493 - return semver.New(out.Version)
10 + os.Exit(kubo.Start(kubo.BuildDefaultEnv))
11 }
cmd/ipfs/runmain_test.go
+5 -2
@@ -1,13 +1,15 @@
1 //go:build testrunmain
2 // +build testrunmain
3
4 -package main
4 +package main_test
5
6 import (
7 "flag"
8 "fmt"
9 "os"
10 "testing"
11 +
12 + "github.com/ipfs/kubo/cmd/ipfs/kubo"
13 )
14
15 // this abuses go so much that I felt dirty writing this code
@@ -16,7 +18,8 @@ import (
18 func TestRunMain(t *testing.T) {
19 args := flag.Args()
20 os.Args = append([]string{os.Args[0]}, args...)
19 - ret := Start()
21 +
22 + ret := kubo.Start(kubo.BuildDefaultEnv)
23
24 p := os.Getenv("IPFS_COVER_RET_FILE")
25 if len(p) != 0 {