package harness import ( "encoding/json" "fmt" "io" "os" "reflect" "strings" . "github.com/ipfs/kubo/test/cli/testutils" ) func (n *Node) IPFSCommands() []string { res := n.IPFS("commands").Stdout.String() res = strings.TrimSpace(res) split := SplitLines(res) var cmds []string for _, line := range split { trimmed := strings.TrimSpace(line) if trimmed == "ipfs" { continue } cmds = append(cmds, trimmed) } return cmds } func (n *Node) SetIPFSConfig(key string, val any, flags ...string) { valBytes, err := json.Marshal(val) if err != nil { log.Panicf("marshling config for key '%s': %s", key, err) } valStr := string(valBytes) args := []string{"config", "--json"} args = append(args, flags...) args = append(args, key, valStr) n.IPFS(args...) // validate the config was set correctly // Create a new value which is a pointer to the same type as the source. var newVal any if val != nil { // If it is not nil grab the type with reflect. newVal = reflect.New(reflect.TypeOf(val)).Interface() } else { // else just set a pointer to an any. var anything any newVal = &anything } n.GetIPFSConfig(key, newVal) // dereference newVal using reflect to load the resulting value if !reflect.DeepEqual(val, reflect.ValueOf(newVal).Elem().Interface()) { log.Panicf("key '%s' did not retain value '%s' after it was set, got '%s'", key, val, newVal) } } func (n *Node) GetIPFSConfig(key string, val any) { res := n.IPFS("config", key) valStr := strings.TrimSpace(res.Stdout.String()) // only when the result is a string is the result not well-formed JSON, // so check the value type and add quotes if it's expected to be a string reflectVal := reflect.ValueOf(val) if reflectVal.Kind() == reflect.Pointer && reflectVal.Elem().Kind() == reflect.String { valStr = fmt.Sprintf(`"%s"`, valStr) } err := json.Unmarshal([]byte(valStr), val) if err != nil { log.Fatalf("unmarshaling config for key '%s', value '%s': %s", key, valStr, err) } } func (n *Node) IPFSAddStr(content string, args ...string) string { log.Debugf("node %d adding content '%s' with args: %v", n.ID, PreviewStr(content), args) return n.IPFSAdd(strings.NewReader(content), args...) } // IPFSAddDeterministic produces a CID of a file of a certain size, filled with deterministically generated bytes based on some seed. // Size is specified as a humanize string (e.g., "256KiB", "1MiB"). // This ensures deterministic CID on the other end, that can be used in tests. func (n *Node) IPFSAddDeterministic(size string, seed string, args ...string) string { log.Debugf("node %d adding %s of deterministic pseudo-random data with seed %q and args: %v", n.ID, size, seed, args) reader, err := DeterministicRandomReader(size, seed) if err != nil { panic(err) } return n.IPFSAdd(reader, args...) } // IPFSAddDeterministicBytes produces a CID of a file of exactly `size` bytes, filled with deterministically generated bytes based on some seed. // Use this when exact byte precision is needed (e.g., threshold tests at T and T+1 bytes). func (n *Node) IPFSAddDeterministicBytes(size int64, seed string, args ...string) string { log.Debugf("node %d adding %d bytes of deterministic pseudo-random data with seed %q and args: %v", n.ID, size, seed, args) reader, err := DeterministicRandomReaderBytes(size, seed) if err != nil { panic(err) } return n.IPFSAdd(reader, args...) } func (n *Node) IPFSAdd(content io.Reader, args ...string) string { log.Debugf("node %d adding with args: %v", n.ID, args) fullArgs := []string{"add", "-q"} fullArgs = append(fullArgs, args...) res := n.Runner.MustRun(RunRequest{ Path: n.IPFSBin, Args: fullArgs, CmdOpts: []CmdOpt{RunWithStdin(content)}, }) out := strings.TrimSpace(res.Stdout.String()) log.Debugf("add result: %q", out) return out } func (n *Node) IPFSBlockPut(content io.Reader, args ...string) string { log.Debugf("node %d block put with args: %v", n.ID, args) fullArgs := []string{"block", "put"} fullArgs = append(fullArgs, args...) res := n.Runner.MustRun(RunRequest{ Path: n.IPFSBin, Args: fullArgs, CmdOpts: []CmdOpt{RunWithStdin(content)}, }) out := strings.TrimSpace(res.Stdout.String()) log.Debugf("block put result: %q", out) return out } func (n *Node) IPFSDAGPut(content io.Reader, args ...string) string { log.Debugf("node %d dag put with args: %v", n.ID, args) fullArgs := []string{"dag", "put"} fullArgs = append(fullArgs, args...) res := n.Runner.MustRun(RunRequest{ Path: n.IPFSBin, Args: fullArgs, CmdOpts: []CmdOpt{RunWithStdin(content)}, }) out := strings.TrimSpace(res.Stdout.String()) log.Debugf("dag put result: %q", out) return out } func (n *Node) IPFSDagImport(content io.Reader, cid string, args ...string) error { log.Debugf("node %d dag import with args: %v", n.ID, args) fullArgs := []string{"dag", "import", "--pin-roots=false"} fullArgs = append(fullArgs, args...) res := n.Runner.MustRun(RunRequest{ Path: n.IPFSBin, Args: fullArgs, CmdOpts: []CmdOpt{RunWithStdin(content)}, }) if res.Err != nil { return res.Err } res = n.Runner.MustRun(RunRequest{ Path: n.IPFSBin, Args: []string{"block", "stat", "--offline", cid}, }) return res.Err } // IPFSDagExport exports a DAG rooted at cid to a CAR file at carPath. func (n *Node) IPFSDagExport(cid string, carPath string) error { log.Debugf("node %d dag export of %s to %q", n.ID, cid, carPath) car, err := os.Create(carPath) if err != nil { return err } defer car.Close() res := n.Runner.MustRun(RunRequest{ Path: n.IPFSBin, Args: []string{"dag", "export", cid}, CmdOpts: []CmdOpt{RunWithStdout(car)}, }) return res.Err }