func Run(node Node, command string, w io.Writer) (err error) {
	var session *ssh.Session
	err = wait.Retry(context.TODO(), func() error {
		session, err = node.Connect()
		if err != nil {
			log.Debug(trace.DebugReport(err))
		}
		return trace.Wrap(err)
	})
	if err != nil {
		return trace.Wrap(err)
	}
	return sshutils.RunCommandWithOutput(session, defaultLogger, command, w)
}
Run executes the specified command on node and streams
session's Stdout/Stderr to the specified w
package infra
import (
	"context"
	"fmt"
	"io"
	"os"
	"sync"
	"golang.org/x/crypto/ssh"
	"github.com/gravitational/robotest/lib/loc"
	sshutils "github.com/gravitational/robotest/lib/ssh"
	"github.com/gravitational/robotest/lib/wait"
	"github.com/gravitational/trace"
	log "github.com/sirupsen/logrus"
)
func New(config Config, opsCenterURL string, provisioner Provisioner) (Infra, error) {
	return &autoCluster{
		opsCenterURL: opsCenterURL,
		provisioner:  provisioner,
	}, nil
}
func NewWizard(config Config, provisioner Provisioner, installer Node) (Infra, *loc.Locator, error) {
	cluster, err := startWizard(provisioner, installer)
	if err != nil {
		return nil, nil, trace.Wrap(err)
	}
	cluster.config = config
	return cluster, &cluster.application, nil
}
type Infra interface {
	OpsCenterURL() string
	Close() error
	Destroy() error
	Provisioner() Provisioner
	Config() Config
}
type Provisioner interface {
	Create(ctx context.Context, withInstaller bool) (installer Node, err error)
	Destroy(ctx context.Context) error
	Connect(addr string) (*ssh.Session, error)
	Client(addr string) (*ssh.Client, error)
	SelectInterface(installer Node, addrs []string) (int, error)
	StartInstall(session *ssh.Session) error
	UploadUpdate(session *ssh.Session) error
	NodePool() NodePool
	InstallerLogPath() string
	State() ProvisionerState
}
type NodePool interface {
	Nodes() []Node
	AllocatedNodes() []Node
	Node(addr string) (Node, error)
	Size() int
	SizeAllocated() int
	Allocate(amount int) ([]Node, error)
	Free([]Node) error
}
type Node interface {
	fmt.Stringer
	Addr() string
	PrivateAddr() string
	Connect() (*ssh.Session, error)
	Client() (*ssh.Client, error)
}
type ExternalStateLoader interface {
	LoadFromExternalState(r io.Reader, withInstaller bool) (installer Node, err error)
}
var defaultLogger = log.New()
func Distribute(command string, nodes ...Node) error {
	log.Infof("running %q on %v", command, nodes)
	errCh := make(chan error, len(nodes))
	wg := sync.WaitGroup{}
	wg.Add(len(nodes))
	for _, node := range nodes {
		go func(node Node) {
			log.Infof("running on %v", node)
			errCh <- Run(node, command, os.Stderr)
			wg.Done()
		}(node)
	}
	wg.Wait()
	close(errCh)
	var errors []error
	for err := range errCh {
		if err != nil {
			errors = append(errors, err)
		}
	}
	return trace.NewAggregate(errors...)
}