Exemple #1
0
// Run runs set of commands on multiple hosts defined by network sequentially.
// TODO: This megamoth method needs a big refactor and should be split
//       to multiple smaller methods.
func (sup *Stackup) Run(network *Network, commands ...*Command) error {
	if len(commands) == 0 {
		return errors.New("no commands to be run")
	}

	// Process all ENVs into a string of form
	// `export FOO="bar"; export BAR="baz";`.
	env := ``
	for _, v := range append(sup.conf.Env, network.Env...) {
		env += v.AsExport() + " "
	}

	// Create clients for every host (either SSH or Localhost).
	var (
		clients []Client
		bastion *SSHClient
	)

	if network.Bastion != "" {
		bastion = &SSHClient{}
		if err := bastion.Connect(network.Bastion); err != nil {
			return err
		}
	}

	for i, host := range network.Hosts {
		// Localhost client.
		if host == "localhost" {
			local := &LocalhostClient{
				env: env + `export SUP_HOST="` + host + `";`,
			}
			if err := local.Connect(host); err != nil {
				return err
			}
			clients = append(clients, local)
			continue
		}

		// SSH client.
		remote := &SSHClient{
			env:   env + `export SUP_HOST="` + host + `";`,
			color: Colors[i%len(Colors)],
		}

		if bastion != nil {
			if err := remote.ConnectWith(host, bastion.DialThrough); err != nil {
				return err
			}
		} else {
			if err := remote.Connect(host); err != nil {
				return err
			}
		}
		defer remote.Close()
		clients = append(clients, remote)
	}

	maxLen := 0
	for _, c := range clients {
		_, prefixLen := c.Prefix()
		if prefixLen > maxLen {
			maxLen = prefixLen
		}
	}

	// Run command or run multiple commands defined by target sequentially.
	for _, cmd := range commands {
		// Translate command into task(s).
		tasks, err := CreateTasks(cmd, clients, env)
		if err != nil {
			return fmt.Errorf("CreateTasks(): %s", err)
		}

		// Run tasks sequentially.
		for _, task := range tasks {
			var writers []io.Writer
			var wg sync.WaitGroup

			// Run tasks on the provided clients.
			for _, c := range task.Clients {
				prefix, prefixLen := c.Prefix()
				if len(prefix) < maxLen { // Left padding.
					prefix = strings.Repeat(" ", maxLen-prefixLen) + prefix
				}

				err := c.Run(task)
				if err != nil {
					return fmt.Errorf("%s%v", prefix, err)
				}

				// Copy over tasks's STDOUT.
				wg.Add(1)
				go func(c Client) {
					defer wg.Done()
					_, err := io.Copy(os.Stdout, prefixer.New(c.Stdout(), prefix))
					if err != nil && err != io.EOF {
						// TODO: io.Copy() should not return io.EOF at all.
						// Upstream bug? Or prefixer.WriteTo() bug?
						fmt.Fprintf(os.Stderr, "%sSTDOUT: %v", prefix, err)
					}
				}(c)

				// Copy over tasks's STDERR.
				wg.Add(1)
				go func(c Client) {
					defer wg.Done()
					_, err := io.Copy(os.Stderr, prefixer.New(c.Stderr(), prefix))
					if err != nil && err != io.EOF {
						fmt.Fprintf(os.Stderr, "%sSTDERR: %v", prefix, err)
					}
				}(c)

				writers = append(writers, c.Stdin())
			}

			// Copy over task's STDIN.
			if task.Input != nil {
				go func() {
					writer := io.MultiWriter(writers...)
					_, err := io.Copy(writer, task.Input)
					if err != nil && err != io.EOF {
						fmt.Fprintf(os.Stderr, "STDIN: %v", err)
					}
					// TODO: Use MultiWriteCloser (not in Stdlib), so we can writer.Close() instead?
					for _, c := range clients {
						c.WriteClose()
					}
				}()
			}

			// Wait for all I/O operations first.
			wg.Wait()

			// Make sure each client finishes the task, return on failure.
			for _, c := range task.Clients {
				wg.Add(1)
				go func(c Client) {
					defer wg.Done()
					if err := c.Wait(); err != nil {
						prefix, prefixLen := c.Prefix()
						if len(prefix) < maxLen { // Left padding.
							prefix = strings.Repeat(" ", maxLen-prefixLen) + prefix
						}
						if e, ok := err.(*ssh.ExitError); ok && e.ExitStatus() != 15 {
							// TODO: Store all the errors, and print them after Wait().
							fmt.Fprintf(os.Stderr, "%sexit %v\n", prefix, e.ExitStatus())
							os.Exit(e.ExitStatus())
						}
						fmt.Fprintf(os.Stderr, "%s%v\n", prefix, err)
						os.Exit(1)
					}
				}(c)
			}

			// Wait for all commands to finish.
			wg.Wait()
		}
	}

	return nil
}
Exemple #2
0
// Run runs set of commands on multiple hosts defined by network sequentially.
// TODO: This megamoth method needs a big refactor and should be split
//       to multiple smaller methods.
func (sup *Stackup) Run(network *Network, commands ...*Command) error {
	if len(commands) == 0 {
		return errors.New("no commands to be run")
	}

	// Process all ENVs into a string of form
	// `export FOO="bar"; export BAR="baz";`.
	env := ``
	for name, value := range sup.conf.Env {
		env += `export ` + name + `="` + value + `";`
	}
	for name, value := range network.Env {
		env += `export ` + name + `="` + value + `";`
	}

	var paddingLen int

	// Create clients for every host (either SSH or Localhost).
	var clients []Client
	for _, host := range network.Hosts {
		var c Client

		if host == "localhost" { // LocalhostClient

			local := &LocalhostClient{
				Env: env,
			}
			if err := local.Connect(host); err != nil {
				log.Fatal(err)
			}

			c = local

		} else { // SSHClient

			remote := &SSHClient{
				Env: env,
			}
			if err := remote.Connect(host); err != nil {
				log.Fatal(err)
			}
			defer remote.Close()

			c = remote
		}

		len := len(c.Prefix())
		if len > paddingLen {
			paddingLen = len
		}

		clients = append(clients, c)
	}

	// Run command or run multiple commands defined by target sequentially.
	for _, cmd := range commands {
		// Translate command into task(s).
		tasks, err := TasksFromConfigCommand(cmd, env)
		if err != nil {
			log.Fatalf("TasksFromConfigCommand(): ", err)
		}

		// Run tasks sequentally.
		for _, task := range tasks {

			var taskClients chan Client
			if task.RunOnce {
				taskClients = make(chan Client, 1)
				// Range over one client - range over map for randomness.
				for _, client := range clients {
					taskClients <- client
					break
				}
				close(taskClients)
			} else {
				// Range over all clients.
				taskClients = make(chan Client, len(clients))
				for _, client := range clients {
					taskClients <- client
				}
				close(taskClients)
			}

			var writers []io.Writer
			var wg sync.WaitGroup
			i := 0

			// Run tasks on the provided clients.
			for c := range taskClients {
				padding := strings.Repeat(" ", paddingLen-(len(c.Prefix())))
				color := Colors[i%len(Colors)]
				i++
				prefix := color + padding + c.Prefix() + " | "

				err := c.Run(task)
				if err != nil {
					log.Fatalf("%sexit %v", prefix, err)
				}

				// Wait for each client to finish the command.
				wg.Add(1)
				go func(c Client) {
					defer wg.Done()
					if err := c.Wait(); err != nil {
						//TODO: Handle the SSH ExitError in ssh pkg
						e, ok := err.(*ssh.ExitError)
						if ok && e.ExitStatus() != 15 {
							// TODO: Prefix should be with color.
							// TODO: Store all the errors, and print them after Wait().
							fmt.Fprintf(os.Stderr, "%s | exit %v\n", c.Prefix(), e.ExitStatus())
							os.Exit(e.ExitStatus())
						}
						// TODO: Prefix should be with color.
						fmt.Fprintf(os.Stderr, "%s | %v\n", c.Prefix(), err)
						os.Exit(1)
					}
				}(c)

				// Copy over tasks's STDOUT.
				wg.Add(1)
				go func(c Client) {
					defer wg.Done()
					switch t := c.(type) {
					case *SSHClient:
						_, err := io.Copy(os.Stdout, prefixer.New(t.RemoteStdout, prefix))
						if err != nil && err != io.EOF {
							// TODO: io.Copy() should not return io.EOF at all.
							// Upstream bug? Or prefixer.WriteTo() bug?
							log.Printf("%sSTDOUT: %v", t.Prefix(), err)
						}
					case *LocalhostClient:
						_, err := io.Copy(os.Stdout, prefixer.New(t.Stdout, prefix))
						if err != nil && err != io.EOF {
							log.Printf("%sSTDOUT: %v", t.Prefix(), err)
						}
					}
				}(c)

				// Copy over tasks's STDERR.
				wg.Add(1)
				go func(c Client) {
					defer wg.Done()
					switch t := c.(type) {
					case *SSHClient:
						_, err := io.Copy(os.Stderr, prefixer.New(t.RemoteStderr, prefix))
						if err != nil && err != io.EOF {
							log.Printf("%sSTDERR: %v", t.Prefix(), err)
						}
					case *LocalhostClient:
						_, err := io.Copy(os.Stderr, prefixer.New(t.Stderr, prefix))
						if err != nil && err != io.EOF {
							log.Printf("%sSTDERR: %v", t.Prefix(), err)
						}
					}
				}(c)

				switch t := c.(type) {
				case *SSHClient:
					writers = append(writers, t.RemoteStdin)
				case *LocalhostClient:
					writers = append(writers, t.Stdin)
				}
			}

			// Copy over task's STDIN.
			if task.Input != nil {
				writer := io.MultiWriter(writers...)
				_, err := io.Copy(writer, task.Input)
				if err != nil {
					log.Printf("STDIN: %v", err)
				}
				//TODO: Use MultiWriteCloser (not in Stdlib), so we can writer.Close()?
				// 	    Move this at least to some defer function instead.
				for _, c := range clients {
					c.WriteClose()
				}
			}

			wg.Wait()
		}
	}

	return nil
}