commands

package
v0.0.0-...-15711b3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 10, 2019 License: MIT, MIT Imports: 16 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var AttrCmd = cli.Command{
	Category: "ATTRIBUTES",
	Name:     "attr",
	Usage:    "get, set, list attributes",
	Subcommands: []cli.Command{
		AttrSetCmd,
		AttrGetCmd,
		AttrListCmd,
	},
}
View Source
var AttrGetCmd = cli.Command{
	Name:      "get",
	Usage:     "get an attribute for a node",
	ArgsUsage: "<node> <attr>",
	Action: func(c *cli.Context) error {
		flagRoot := c.GlobalString("IPTB_ROOT")
		flagTestbed := c.GlobalString("testbed")

		if c.NArg() != 2 {
			return NewUsageError("get takes exactly 2 argument")
		}

		argNode := c.Args()[0]
		argAttr := c.Args()[1]

		i, err := strconv.Atoi(argNode)
		if err != nil {
			return fmt.Errorf("parse err: %s", err)
		}

		tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed))

		node, err := tb.Node(i)
		if err != nil {
			return err
		}

		attrNode, ok := node.(testbedi.Attribute)
		if !ok {
			return fmt.Errorf("node does not implement attributes")
		}

		value, err := attrNode.Attr(argAttr)
		if err != nil {
			return err
		}

		_, err = fmt.Fprintf(c.App.Writer, "%s\n", value)

		return err
	},
}
View Source
var AttrListCmd = cli.Command{
	Name:      "list",
	Usage:     "list attributes available for a node",
	ArgsUsage: "<node>",
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "type",
			Usage: "look up attributes for node type",
		},
	},
	Action: func(c *cli.Context) error {
		flagRoot := c.GlobalString("IPTB_ROOT")
		flagTestbed := c.GlobalString("testbed")
		flagType := c.String("type")

		if !c.Args().Present() && len(flagType) == 0 {
			return NewUsageError("specify a node, or a type")
		}

		if c.Args().Present() {
			i, err := strconv.Atoi(c.Args().First())
			if err != nil {
				return fmt.Errorf("parse err: %s", err)
			}

			tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed))

			spec, err := tb.Spec(i)
			if err != nil {
				return err
			}

			flagType = spec.Type
		}

		plg, ok := testbed.GetPlugin(flagType)
		if !ok {
			return fmt.Errorf("Unknown plugin %s", flagType)
		}

		attrList := plg.GetAttrList()
		for _, a := range attrList {
			desc, err := plg.GetAttrDesc(a)
			if err != nil {
				return fmt.Errorf("error getting attribute description: %s", err)
			}

			fmt.Fprintf(c.App.Writer, "\t%s: %s\n", a, desc)
		}

		return nil
	},
}
View Source
var AttrSetCmd = cli.Command{
	Name:      "set",
	Usage:     "set an attribute for a node",
	ArgsUsage: "<node> <attr> <value>",
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:  "save",
			Usage: "saves attribute value to nodespec",
		},
	},
	Action: func(c *cli.Context) error {
		flagRoot := c.GlobalString("IPTB_ROOT")
		flagTestbed := c.GlobalString("testbed")
		flagSave := c.Bool("save")

		if c.NArg() != 3 {
			return NewUsageError("set takes exactly 3 argument")
		}

		argNode := c.Args()[0]
		argAttr := c.Args()[1]
		argValue := c.Args()[2]

		i, err := strconv.Atoi(argNode)
		if err != nil {
			return fmt.Errorf("parse err: %s", err)
		}

		tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed))

		node, err := tb.Node(i)
		if err != nil {
			return err
		}

		attrNode, ok := node.(testbedi.Attribute)
		if !ok {
			return fmt.Errorf("node does not implement attributes")
		}

		if err := attrNode.SetAttr(argAttr, argValue); err != nil {
			return err
		}

		if flagSave {
			specs, err := tb.Specs()
			if err != nil {
				return err
			}

			specs[i].SetAttr(argAttr, argValue)

			if err := testbed.WriteNodeSpecs(tb.Dir(), specs); err != nil {
				return err
			}
		}

		return nil
	},
}
View Source
var AutoCmd = cli.Command{
	Name:  "auto",
	Usage: "create default testbed and initialize",
	Description: `
The auto command is a quick way to use iptb for simple configurations.

The auto command is similar to 'testbed create' except in a few ways

 - No attr options can be passed in
 - All nodes are initialize by default and ready to be started
 - An optional --start flag can be passed to start all nodes

The following two examples are equivalent

$ iptb testbed create -count 5 -type <type> -init
$ iptb auto           -count 5 -type <type>
`,
	ArgsUsage: "--type <type>",
	Flags: []cli.Flag{
		cli.IntFlag{
			Name:  "count",
			Usage: "number of nodes to initialize",
			Value: 1,
		},
		cli.BoolFlag{
			Name:  "force",
			Usage: "force overwrite of existing nodespecs",
		},
		cli.StringFlag{
			Name:  "type",
			Usage: "kind of nodes to initialize",
		},
		cli.BoolFlag{
			Name:  "start",
			Usage: "starts nodes immediately",
		},
	},
	Action: func(c *cli.Context) error {
		flagRoot := c.GlobalString("IPTB_ROOT")
		flagTestbed := c.GlobalString("testbed")
		flagQuiet := c.GlobalBool("quiet")
		flagType := c.String("type")
		flagStart := c.Bool("start")
		flagCount := c.Int("count")
		flagForce := c.Bool("force")

		tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed))

		if err := testbed.AlreadyInitCheck(tb.Dir(), flagForce); err != nil {
			return err
		}

		specs, err := testbed.BuildSpecs(tb.Dir(), flagCount, flagType, nil)
		if err != nil {
			return err
		}

		if err := testbed.WriteNodeSpecs(tb.Dir(), specs); err != nil {
			return err
		}

		nodes, err := tb.Nodes()
		if err != nil {
			return err
		}

		var list []int
		for i, _ := range nodes {
			list = append(list, i)
		}

		runCmd := func(node testbedi.Core) (testbedi.Output, error) {
			return node.Init(context.Background())
		}

		results, err := mapWithOutput(list, nodes, runCmd)
		if err != nil {
			return err
		}

		if err := buildReport(results, flagQuiet); err != nil {
			return err
		}

		if flagStart {
			runCmd := func(node testbedi.Core) (testbedi.Output, error) {
				return node.Start(context.Background(), true)
			}

			results, err := mapWithOutput(list, nodes, runCmd)
			if err != nil {
				return err
			}

			if err := buildReport(results, flagQuiet); err != nil {
				return err
			}
		}

		return nil
	},
}
View Source
var ConnectCmd = cli.Command{
	Category:  "CORE",
	Name:      "connect",
	Usage:     "connect sets of nodes together (or all)",
	ArgsUsage: "[nodes] [nodes]",
	Description: `
The connect command allows for connecting sets of nodes together.

Every node listed in the first set, will try to connect to every node
listed in the second set.

There are three variants of the command. It can accept no arugments,
a single argument, or two arguments. The no argument and single argument
expands out to the two argument usage.

$ iptb connect             => iptb connect [0-C] [0-C]
$ iptb connect [n-m]       => iptb connect [n-m] [n-m]
$ iptb connect [n-m] [i-k]

Sets of nodes can be expressed in the following ways

INPUT         EXPANDED
0             0
[0]           0
[0-4]         0,1,2,3,4
[0,2-4]       0,2,3,4
[2-4,0]       2,3,4,0
[0,2,4]       0,2,4
`,
	Flags: []cli.Flag{
		cli.StringFlag{
			Name:  "timeout",
			Usage: "timeout on the command",
			Value: "30s",
		},
	},
	Action: func(c *cli.Context) error {
		flagRoot := c.GlobalString("IPTB_ROOT")
		flagTestbed := c.GlobalString("testbed")
		flagQuiet := c.GlobalBool("quiet")
		flagTimeout := c.String("timeout")

		timeout, err := time.ParseDuration(flagTimeout)
		if err != nil {
			return err
		}

		tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed))
		args := c.Args()

		var results []Result
		switch c.NArg() {
		case 0:
			nodes, err := tb.Nodes()
			if err != nil {
				return err
			}

			fromto, err := parseRange(fmt.Sprintf("[0-%d]", len(nodes)-1))
			if err != nil {
				return err
			}

			results, err = connectNodes(tb, fromto, fromto, timeout)
			if err != nil {
				return err
			}
		case 1:
			fromto, err := parseRange(args[0])
			if err != nil {
				return err
			}

			results, err = connectNodes(tb, fromto, fromto, timeout)
			if err != nil {
				return err
			}
		case 2:
			from, err := parseRange(args[0])
			if err != nil {
				return err
			}

			to, err := parseRange(args[1])
			if err != nil {
				return err
			}

			results, err = connectNodes(tb, from, to, timeout)
			if err != nil {
				return err
			}
		default:
			return NewUsageError("connet accepts between 0 and 2 arguments")
		}

		return buildReport(results, flagQuiet)
	},
}
View Source
var EventsCmd = cli.Command{
	Category:  "METRICS",
	Name:      "events",
	Usage:     "stream events from specified nodes (or all)",
	ArgsUsage: "[node]",
	Action: func(c *cli.Context) error {
		flagRoot := c.GlobalString("IPTB_ROOT")
		flagTestbed := c.GlobalString("testbed")

		if !c.Args().Present() {
			return NewUsageError("events takes exactly 1 argument")
		}

		i, err := strconv.Atoi(c.Args().First())
		if err != nil {
			return fmt.Errorf("parse err: %s", err)
		}

		tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed))

		node, err := tb.Node(i)
		if err != nil {
			return err
		}

		mn, ok := node.(testbedi.Metric)
		if !ok {
			return fmt.Errorf("node does not implement metrics")
		}

		el, err := mn.Events()
		if err != nil {
			return err
		}

		_, err = io.Copy(c.App.Writer, el)
		return err
	},
}
View Source
var InitCmd = cli.Command{
	Category:  "CORE",
	Name:      "init",
	Usage:     "initialize specified nodes (or all)",
	ArgsUsage: "[nodes] -- [arguments...]",
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:   "terminator",
			Hidden: true,
		},
	},
	Before: func(c *cli.Context) error {
		if present := isTerminatorPresent(c); present {
			return c.Set("terminator", "true")
		}

		return nil
	},
	Action: func(c *cli.Context) error {
		flagRoot := c.GlobalString("IPTB_ROOT")
		flagTestbed := c.GlobalString("testbed")
		flagQuiet := c.GlobalBool("quiet")

		tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed))
		nodes, err := tb.Nodes()
		if err != nil {
			return err
		}

		nodeRange, args := parseCommand(c.Args(), c.IsSet("terminator"))

		if nodeRange == "" {
			nodeRange = fmt.Sprintf("[0-%d]", len(nodes)-1)
		}

		list, err := parseRange(nodeRange)
		if err != nil {
			return fmt.Errorf("could not parse node range %s", nodeRange)
		}

		runCmd := func(node testbedi.Core) (testbedi.Output, error) {
			return node.Init(context.Background(), args...)
		}

		results, err := mapWithOutput(list, nodes, runCmd)
		if err != nil {
			return err
		}

		return buildReport(results, flagQuiet)
	},
}
View Source
var LogsCmd = cli.Command{
	Category:  "METRICS",
	Name:      "logs",
	Usage:     "show logs from specified nodes (or all)",
	ArgsUsage: "[nodes]",
	Flags: []cli.Flag{
		cli.BoolTFlag{
			Name:  "err, e",
			Usage: "show stderr stream",
		},
		cli.BoolTFlag{
			Name:  "out, o",
			Usage: "show stdout stream",
		},
	},
	Action: func(c *cli.Context) error {
		flagRoot := c.GlobalString("IPTB_ROOT")
		flagTestbed := c.GlobalString("testbed")
		flagQuiet := c.GlobalBool("quiet")
		flagErr := c.BoolT("err")
		flagOut := c.BoolT("out")

		tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed))
		nodes, err := tb.Nodes()
		if err != nil {
			return err
		}

		nodeRange := c.Args().First()

		if nodeRange == "" {
			nodeRange = fmt.Sprintf("[0-%d]", len(nodes)-1)
		}

		list, err := parseRange(nodeRange)
		if err != nil {
			return err
		}

		runCmd := func(node testbedi.Core) (testbedi.Output, error) {
			metricNode, ok := node.(testbedi.Metric)
			if !ok {
				return nil, fmt.Errorf("node does not implement metrics")
			}

			stdout := ioutil.NopCloser(strings.NewReader(""))
			stderr := ioutil.NopCloser(strings.NewReader(""))

			if flagOut {
				var err error
				stdout, err = metricNode.StdoutReader()
				if err != nil {
					return nil, err
				}
			}

			if flagErr {
				var err error
				stderr, err = metricNode.StderrReader()
				if err != nil {
					return nil, err
				}
			}

			return NewOutput(stdout, stderr), nil
		}

		results, err := mapWithOutput(list, nodes, runCmd)
		if err != nil {
			return err
		}

		return buildReport(results, flagQuiet)
	},
}
View Source
var MetricCmd = cli.Command{
	Category:  "METRICS",
	Name:      "metric",
	Usage:     "get metric from node",
	ArgsUsage: "<node> [metric]",
	Action: func(c *cli.Context) error {
		if c.NArg() == 1 {
			return metricList(c)
		}

		if c.NArg() == 2 {
			return metricGet(c)
		}

		return NewUsageError("metric takes 1 or 2 arguments only")
	},
}
View Source
var RestartCmd = cli.Command{
	Category:  "CORE",
	Name:      "restart",
	Usage:     "restart specified nodes (or all)",
	ArgsUsage: "[nodes] -- [arguments...]",
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:  "wait",
			Usage: "wait for nodes to start before returning",
		},
		cli.BoolFlag{
			Name:   "terminator",
			Hidden: true,
		},
	},
	Before: func(c *cli.Context) error {
		if present := isTerminatorPresent(c); present {
			return c.Set("terminator", "true")
		}

		return nil
	},
	Action: func(c *cli.Context) error {
		flagRoot := c.GlobalString("IPTB_ROOT")
		flagTestbed := c.GlobalString("testbed")
		flagQuiet := c.GlobalBool("quiet")
		flagWait := c.Bool("wait")

		tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed))
		nodes, err := tb.Nodes()
		if err != nil {
			return err
		}

		nodeRange, args := parseCommand(c.Args(), c.IsSet("terminator"))

		if nodeRange == "" {
			nodeRange = fmt.Sprintf("[0-%d]", len(nodes)-1)
		}

		list, err := parseRange(nodeRange)
		if err != nil {
			return fmt.Errorf("could not parse node range %s", nodeRange)
		}

		runCmd := func(node testbedi.Core) (testbedi.Output, error) {
			if err := node.Stop(context.Background()); err != nil {
				return nil, err
			}

			return node.Start(context.Background(), flagWait, args...)
		}

		results, err := mapWithOutput(list, nodes, runCmd)
		if err != nil {
			return err
		}

		return buildReport(results, flagQuiet)
	},
}
View Source
var RunCmd = cli.Command{
	Category:  "CORE",
	Name:      "run",
	Usage:     "concurrently run command(s) on specified nodes (or all)",
	ArgsUsage: "[nodes] -- <command...>",
	Description: `
Commands may also be passed in via stdin or a pipe, e.g. the command

$ iptb run 0 -- echo "Running on node 0"

can be equivalently written as

$ iptb run <<CMD
    0 -- echo "Running on node 0"
  CMD

or

$ echo '0 -- echo "Running on node 0"' | iptb run

All lines starting with '#' will be ignored, which allows for comments:

$ iptb run <<CMD
    # print ipfs peers
    0 -- ipfs swarm peers
  CMD

Multiple commands may also be passed via stdin/pipe:

$ iptb run <<CMDS
    0     -- echo "Running on node 0"
    [0,1] -- echo "Running on nodes 0 and 1"
          -- echo "Running on all nodes"
  CMDS

Note that any single call to ` + "`iptb run`" + ` runs *all* commands concurrently. So,
in the above example, there is no guarantee as to the order in which the lines
are printed.
`,
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:   "terminator",
			Hidden: true,
		},
		cli.BoolFlag{
			Name:   "stdin",
			Hidden: true,
		},
	},
	Before: func(c *cli.Context) error {
		if c.NArg() == 0 {
			finfo, err := os.Stdin.Stat()
			if err != nil {
				return err
			}
			if finfo.Size() == 0 && finfo.Mode()&os.ModeNamedPipe == 0 {
				return fmt.Errorf("error: no command input and stdin is empty")
			}
			return c.Set("stdin", "true")
		}
		if present := isTerminatorPresent(c); present {
			return c.Set("terminator", "true")
		}
		return nil
	},
	Action: func(c *cli.Context) error {
		flagRoot := c.GlobalString("IPTB_ROOT")
		flagTestbed := c.GlobalString("testbed")
		flagQuiet := c.GlobalBool("quiet")

		tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed))
		nodes, err := tb.Nodes()
		if err != nil {
			return err
		}

		var reader io.Reader
		if c.IsSet("stdin") {
			reader = bufio.NewReader(os.Stdin)
		} else {
			var builder strings.Builder
			if c.IsSet("terminator") {
				builder.WriteString("-- ")
			}
			for i, arg := range c.Args() {
				builder.WriteString(strconv.Quote(arg))
				if i != c.NArg()-1 {
					builder.WriteString(" ")
				}
			}
			reader = strings.NewReader(builder.String())
		}

		var args [][]string
		scanner := bufio.NewScanner(reader)
		line := 1
		for scanner.Scan() {
			tokens, err := shellwords.Parse(scanner.Text())
			if err != nil {
				return fmt.Errorf("parse error on line %d: %s", line, err)
			}
			if strings.HasPrefix(tokens[0], "#") {
				continue
			}
			args = append(args, tokens)
			line++
		}

		ranges := make([][]int, len(args))
		runCmds := make([]outputFunc, len(args))
		for i, cmd := range args {
			nodeRange, tokens := parseCommand(cmd, false)
			if nodeRange == "" {
				nodeRange = fmt.Sprintf("[0-%d]", len(nodes)-1)
			}
			list, err := parseRange(nodeRange)
			if err != nil {
				return fmt.Errorf("could not parse node range %s", nodeRange)
			}
			ranges[i] = list

			runCmd := func(node testbedi.Core) (testbedi.Output, error) {
				return node.RunCmd(context.Background(), nil, tokens...)
			}
			runCmds[i] = runCmd
		}

		results, err := mapListWithOutput(ranges, nodes, runCmds)
		return buildReport(results, flagQuiet)
	},
}
View Source
var ShellCmd = cli.Command{
	Category:  "CORE",
	Name:      "shell",
	Usage:     "starts a shell within the context of node",
	ArgsUsage: "<node>",
	Action: func(c *cli.Context) error {
		flagRoot := c.GlobalString("IPTB_ROOT")
		flagTestbed := c.GlobalString("testbed")

		if !c.Args().Present() {
			return NewUsageError("shell takes exactly 1 argument")
		}

		i, err := strconv.Atoi(c.Args().First())
		if err != nil {
			return fmt.Errorf("parse err: %s", err)
		}

		tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed))

		nodes, err := tb.Nodes()
		if err != nil {
			return err
		}

		return nodes[i].Shell(context.Background(), nodes)
	},
}
View Source
var StartCmd = cli.Command{
	Category:  "CORE",
	Name:      "start",
	Usage:     "start specified nodes (or all)",
	ArgsUsage: "[nodes] -- [arguments...]",
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:  "wait",
			Usage: "wait for nodes to start before returning",
		},
		cli.BoolFlag{
			Name:   "terminator",
			Hidden: true,
		},
	},
	Before: func(c *cli.Context) error {
		if present := isTerminatorPresent(c); present {
			return c.Set("terminator", "true")
		}

		return nil
	},
	Action: func(c *cli.Context) error {
		flagRoot := c.GlobalString("IPTB_ROOT")
		flagTestbed := c.GlobalString("testbed")
		flagQuiet := c.GlobalBool("quiet")
		flagWait := c.Bool("wait")

		tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed))
		nodes, err := tb.Nodes()
		if err != nil {
			return err
		}

		nodeRange, args := parseCommand(c.Args(), c.IsSet("terminator"))

		if nodeRange == "" {
			nodeRange = fmt.Sprintf("[0-%d]", len(nodes)-1)
		}

		list, err := parseRange(nodeRange)
		if err != nil {
			return fmt.Errorf("could not parse node range %s", nodeRange)
		}

		runCmd := func(node testbedi.Core) (testbedi.Output, error) {
			return node.Start(context.Background(), flagWait, args...)
		}

		results, err := mapWithOutput(list, nodes, runCmd)
		if err != nil {
			return err
		}

		return buildReport(results, flagQuiet)
	},
}
View Source
var StopCmd = cli.Command{
	Category:  "CORE",
	Name:      "stop",
	Usage:     "stop specified nodes (or all)",
	ArgsUsage: "[nodes]",
	Action: func(c *cli.Context) error {
		flagRoot := c.GlobalString("IPTB_ROOT")
		flagTestbed := c.GlobalString("testbed")
		flagQuiet := c.GlobalBool("quiet")

		tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed))
		nodes, err := tb.Nodes()
		if err != nil {
			return err
		}

		nodeRange := c.Args().First()

		if nodeRange == "" {
			nodeRange = fmt.Sprintf("[0-%d]", len(nodes)-1)
		}

		list, err := parseRange(nodeRange)
		if err != nil {
			return fmt.Errorf("could not parse node range %s", nodeRange)
		}

		runCmd := func(node testbedi.Core) (testbedi.Output, error) {
			return nil, node.Stop(context.Background())
		}

		results, err := mapWithOutput(list, nodes, runCmd)
		if err != nil {
			return err
		}

		return buildReport(results, flagQuiet)
	},
}
View Source
var TestbedCmd = cli.Command{
	Name:  "testbed",
	Usage: "manage testbeds",
	Subcommands: []cli.Command{
		TestbedCreateCmd,
	},
}
View Source
var TestbedCreateCmd = cli.Command{
	Name:      "create",
	Usage:     "create testbed",
	ArgsUsage: "--type <type>",
	Flags: []cli.Flag{
		cli.IntFlag{
			Name:  "count",
			Usage: "number of nodes to initialize",
			Value: 1,
		},
		cli.BoolFlag{
			Name:  "force",
			Usage: "force overwrite of existing testbed",
		},
		cli.StringFlag{
			Name:  "type",
			Usage: "kind of nodes to initialize",
		},
		cli.StringSliceFlag{
			Name:  "attr",
			Usage: "specify addition attributes for nodes",
		},
		cli.BoolFlag{
			Name:  "init",
			Usage: "initialize after creation (like calling `init` after create)",
		},
	},
	Action: func(c *cli.Context) error {
		flagRoot := c.GlobalString("IPTB_ROOT")
		flagTestbed := c.GlobalString("testbed")
		flagType := c.String("type")
		flagInit := c.Bool("init")
		flagCount := c.Int("count")
		flagForce := c.Bool("force")
		flagAttrs := c.StringSlice("attr")

		attrs := parseAttrSlice(flagAttrs)
		tb := testbed.NewTestbed(path.Join(flagRoot, "testbeds", flagTestbed))

		if err := testbed.AlreadyInitCheck(tb.Dir(), flagForce); err != nil {
			return err
		}

		specs, err := testbed.BuildSpecs(tb.Dir(), flagCount, flagType, attrs)
		if err != nil {
			return err
		}

		if err := testbed.WriteNodeSpecs(tb.Dir(), specs); err != nil {
			return err
		}

		if flagInit {
			nodes, err := tb.Nodes()
			if err != nil {
				return err
			}

			for _, n := range nodes {
				if _, err := n.Init(context.Background()); err != nil {
					return err
				}
			}
		}

		return nil
	},
}

Functions

func NewOutput

func NewOutput(stdout, stderr io.ReadCloser) testbedi.Output

func NewUsageError

func NewUsageError(s string) error

Types

type Output

type Output struct {
	// contains filtered or unexported fields
}

func (*Output) Args

func (o *Output) Args() []string

func (*Output) Error

func (o *Output) Error() error

func (*Output) ExitCode

func (o *Output) ExitCode() int

func (*Output) Stderr

func (o *Output) Stderr() io.ReadCloser

func (*Output) Stdout

func (o *Output) Stdout() io.ReadCloser

type Result

type Result struct {
	Node   int
	Output testbedi.Output
	Error  error
}

type UsageError

type UsageError struct {
	// contains filtered or unexported fields
}

func (*UsageError) Error

func (e *UsageError) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL