clean up remote.Cmd api
Combine the ExitStatus and Err values from remote.Cmd into an error returned by Wait, better matching the behavior of the os/exec package. Non-zero exit codes are returned from Wait as a remote.ExitError. Communicator related errors are returned directly. Clean up all the error handling in the provisioners using a communicator. Also remove the extra copyOutput synchronization that was copied from package to package.
This commit is contained in:
parent
ecec59f0f0
commit
3fbdee0777
|
@ -680,17 +680,10 @@ func (p *provisioner) runCommand(o terraform.UIOutput, comm communicator.Communi
|
||||||
|
|
||||||
outR, outW := io.Pipe()
|
outR, outW := io.Pipe()
|
||||||
errR, errW := io.Pipe()
|
errR, errW := io.Pipe()
|
||||||
outDoneCh := make(chan struct{})
|
go p.copyOutput(o, outR)
|
||||||
errDoneCh := make(chan struct{})
|
go p.copyOutput(o, errR)
|
||||||
go p.copyOutput(o, outR, outDoneCh)
|
defer outW.Close()
|
||||||
go p.copyOutput(o, errR, errDoneCh)
|
defer errW.Close()
|
||||||
go func() {
|
|
||||||
// Wait for output to clean up
|
|
||||||
outW.Close()
|
|
||||||
errW.Close()
|
|
||||||
<-outDoneCh
|
|
||||||
<-errDoneCh
|
|
||||||
}()
|
|
||||||
|
|
||||||
cmd := &remote.Cmd{
|
cmd := &remote.Cmd{
|
||||||
Command: command,
|
Command: command,
|
||||||
|
@ -703,20 +696,17 @@ func (p *provisioner) runCommand(o terraform.UIOutput, comm communicator.Communi
|
||||||
return fmt.Errorf("Error executing command %q: %v", cmd.Command, err)
|
return fmt.Errorf("Error executing command %q: %v", cmd.Command, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Wait()
|
if err := cmd.Wait(); err != nil {
|
||||||
if cmd.Err() != nil {
|
if rc, ok := err.(remote.ExitError); ok {
|
||||||
return cmd.Err()
|
return fmt.Errorf("Command %q exited with non-zero exit status: %d", cmd.Command, rc)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
if cmd.ExitStatus() != 0 {
|
|
||||||
return fmt.Errorf("Command %q exited with non-zero exit status: %d", cmd.Command, cmd.ExitStatus())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *provisioner) copyOutput(o terraform.UIOutput, r io.Reader, doneCh chan<- struct{}) {
|
func (p *provisioner) copyOutput(o terraform.UIOutput, r io.Reader) {
|
||||||
defer close(doneCh)
|
|
||||||
lr := linereader.New(r)
|
lr := linereader.New(r)
|
||||||
for line := range lr.Ch {
|
for line := range lr.Ch {
|
||||||
o.Output(line)
|
o.Output(line)
|
||||||
|
|
|
@ -729,8 +729,7 @@ func (p *provisioner) uploadUserTOML(o terraform.UIOutput, comm communicator.Com
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *provisioner) copyOutput(o terraform.UIOutput, r io.Reader, doneCh chan<- struct{}) {
|
func (p *provisioner) copyOutput(o terraform.UIOutput, r io.Reader) {
|
||||||
defer close(doneCh)
|
|
||||||
lr := linereader.New(r)
|
lr := linereader.New(r)
|
||||||
for line := range lr.Ch {
|
for line := range lr.Ch {
|
||||||
o.Output(line)
|
o.Output(line)
|
||||||
|
@ -741,16 +740,10 @@ func (p *provisioner) runCommand(o terraform.UIOutput, comm communicator.Communi
|
||||||
outR, outW := io.Pipe()
|
outR, outW := io.Pipe()
|
||||||
errR, errW := io.Pipe()
|
errR, errW := io.Pipe()
|
||||||
|
|
||||||
outDoneCh := make(chan struct{})
|
go p.copyOutput(o, outR)
|
||||||
errDoneCh := make(chan struct{})
|
go p.copyOutput(o, errR)
|
||||||
go p.copyOutput(o, outR, outDoneCh)
|
defer outW.Close()
|
||||||
go p.copyOutput(o, errR, errDoneCh)
|
defer errW.Close()
|
||||||
defer func() {
|
|
||||||
outW.Close()
|
|
||||||
errW.Close()
|
|
||||||
<-outDoneCh
|
|
||||||
<-errDoneCh
|
|
||||||
}()
|
|
||||||
|
|
||||||
cmd := &remote.Cmd{
|
cmd := &remote.Cmd{
|
||||||
Command: command,
|
Command: command,
|
||||||
|
@ -762,13 +755,11 @@ func (p *provisioner) runCommand(o terraform.UIOutput, comm communicator.Communi
|
||||||
return fmt.Errorf("Error executing command %q: %v", cmd.Command, err)
|
return fmt.Errorf("Error executing command %q: %v", cmd.Command, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Wait()
|
if err := cmd.Wait(); err != nil {
|
||||||
if cmd.Err() != nil {
|
if rc, ok := err.(remote.ExitError); ok {
|
||||||
return cmd.Err()
|
return fmt.Errorf("Command %q exited with non-zero exit status: %d", cmd.Command, rc)
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
if cmd.ExitStatus() != 0 {
|
|
||||||
return fmt.Errorf("Command %q exited with non-zero exit status: %d", cmd.Command, cmd.ExitStatus())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -196,16 +196,14 @@ func runScripts(
|
||||||
if err := comm.Start(cmd); err != nil {
|
if err := comm.Start(cmd); err != nil {
|
||||||
return fmt.Errorf("Error starting script: %v", err)
|
return fmt.Errorf("Error starting script: %v", err)
|
||||||
}
|
}
|
||||||
cmd.Wait()
|
|
||||||
|
|
||||||
if err := cmd.Err(); err != nil {
|
if err := cmd.Wait(); err != nil {
|
||||||
|
if rc, ok := err.(remote.ExitError); ok {
|
||||||
|
return fmt.Errorf("Script exited with non-zero exit status: %d", rc)
|
||||||
|
}
|
||||||
return fmt.Errorf("Remote command exited with error: %s", err)
|
return fmt.Errorf("Remote command exited with error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.ExitStatus() != 0 {
|
|
||||||
err = fmt.Errorf("Script exited with non-zero exit status: %d", cmd.ExitStatus())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload a blank follow up file in the same path to prevent residual
|
// Upload a blank follow up file in the same path to prevent residual
|
||||||
// script contents from remaining on remote machine
|
// script contents from remaining on remote machine
|
||||||
empty := bytes.NewReader([]byte(""))
|
empty := bytes.NewReader([]byte(""))
|
||||||
|
|
|
@ -114,8 +114,6 @@ func Provisioner() terraform.ResourceProvisioner {
|
||||||
// Apply executes the file provisioner
|
// Apply executes the file provisioner
|
||||||
func applyFn(ctx context.Context) error {
|
func applyFn(ctx context.Context) error {
|
||||||
// Decode the raw config for this provisioner
|
// Decode the raw config for this provisioner
|
||||||
var err error
|
|
||||||
|
|
||||||
o := ctx.Value(schema.ProvOutputKey).(terraform.UIOutput)
|
o := ctx.Value(schema.ProvOutputKey).(terraform.UIOutput)
|
||||||
d := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData)
|
d := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData)
|
||||||
connState := ctx.Value(schema.ProvRawStateKey).(*terraform.InstanceState)
|
connState := ctx.Value(schema.ProvRawStateKey).(*terraform.InstanceState)
|
||||||
|
@ -162,21 +160,20 @@ func applyFn(ctx context.Context) error {
|
||||||
err = fmt.Errorf("Unable to download Salt: %s", err)
|
err = fmt.Errorf("Unable to download Salt: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err := cmd.Wait(); err != nil {
|
||||||
cmd.Wait()
|
if rc, ok := err.(remote.ExitError); ok {
|
||||||
if cmd.Err() != nil {
|
return fmt.Errorf("Curl exited with non-zero exit status: %d", rc)
|
||||||
err = cmd.Err()
|
|
||||||
} else if cmd.ExitStatus() != 0 {
|
|
||||||
err = fmt.Errorf("Curl exited with non-zero exit status: %d", cmd.ExitStatus())
|
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
outR, outW := io.Pipe()
|
outR, outW := io.Pipe()
|
||||||
errR, errW := io.Pipe()
|
errR, errW := io.Pipe()
|
||||||
outDoneCh := make(chan struct{})
|
go copyOutput(o, outR)
|
||||||
errDoneCh := make(chan struct{})
|
go copyOutput(o, errR)
|
||||||
go copyOutput(o, outR, outDoneCh)
|
defer outW.Close()
|
||||||
go copyOutput(o, errR, errDoneCh)
|
defer errW.Close()
|
||||||
|
|
||||||
cmd = &remote.Cmd{
|
cmd = &remote.Cmd{
|
||||||
Command: fmt.Sprintf("%s /tmp/install_salt.sh %s", p.sudo("sh"), p.BootstrapArgs),
|
Command: fmt.Sprintf("%s /tmp/install_salt.sh %s", p.sudo("sh"), p.BootstrapArgs),
|
||||||
Stdout: outW,
|
Stdout: outW,
|
||||||
|
@ -184,24 +181,14 @@ func applyFn(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Output(fmt.Sprintf("Installing Salt with command %s", cmd.Command))
|
o.Output(fmt.Sprintf("Installing Salt with command %s", cmd.Command))
|
||||||
if err = comm.Start(cmd); err != nil {
|
if err := comm.Start(cmd); err != nil {
|
||||||
err = fmt.Errorf("Unable to install Salt: %s", err)
|
return fmt.Errorf("Unable to install Salt: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err := cmd.Wait(); err != nil {
|
||||||
cmd.Wait()
|
if rc, ok := err.(remote.ExitError); ok {
|
||||||
if cmd.Err() != nil {
|
return fmt.Errorf("install_salt.sh exited with non-zero exit status: %d", rc)
|
||||||
err = cmd.Err()
|
|
||||||
} else if cmd.ExitStatus() != 0 {
|
|
||||||
err = fmt.Errorf("install_salt.sh exited with non-zero exit status: %d", cmd.ExitStatus())
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// Wait for output to clean up
|
|
||||||
outW.Close()
|
|
||||||
errW.Close()
|
|
||||||
<-outDoneCh
|
|
||||||
<-errDoneCh
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -270,11 +257,11 @@ func applyFn(ctx context.Context) error {
|
||||||
|
|
||||||
outR, outW := io.Pipe()
|
outR, outW := io.Pipe()
|
||||||
errR, errW := io.Pipe()
|
errR, errW := io.Pipe()
|
||||||
outDoneCh := make(chan struct{})
|
go copyOutput(o, outR)
|
||||||
errDoneCh := make(chan struct{})
|
go copyOutput(o, errR)
|
||||||
|
defer outW.Close()
|
||||||
|
defer errW.Close()
|
||||||
|
|
||||||
go copyOutput(o, outR, outDoneCh)
|
|
||||||
go copyOutput(o, errR, errDoneCh)
|
|
||||||
o.Output(fmt.Sprintf("Running: salt-call --local %s", p.CmdArgs))
|
o.Output(fmt.Sprintf("Running: salt-call --local %s", p.CmdArgs))
|
||||||
cmd := &remote.Cmd{
|
cmd := &remote.Cmd{
|
||||||
Command: p.sudo(fmt.Sprintf("salt-call --local %s", p.CmdArgs)),
|
Command: p.sudo(fmt.Sprintf("salt-call --local %s", p.CmdArgs)),
|
||||||
|
@ -285,21 +272,13 @@ func applyFn(ctx context.Context) error {
|
||||||
err = fmt.Errorf("Error executing salt-call: %s", err)
|
err = fmt.Errorf("Error executing salt-call: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err := cmd.Wait(); err != nil {
|
||||||
cmd.Wait()
|
if rc, ok := err.(remote.ExitError); ok {
|
||||||
if cmd.Err() != nil {
|
return fmt.Errorf("Script exited with non-zero exit status: %d", rc)
|
||||||
err = cmd.Err()
|
|
||||||
} else if cmd.ExitStatus() != 0 {
|
|
||||||
err = fmt.Errorf("Script exited with non-zero exit status: %d", cmd.ExitStatus())
|
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
// Wait for output to clean up
|
return nil
|
||||||
outW.Close()
|
|
||||||
errW.Close()
|
|
||||||
<-outDoneCh
|
|
||||||
<-errDoneCh
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepends sudo to supplied command if config says to
|
// Prepends sudo to supplied command if config says to
|
||||||
|
@ -360,12 +339,13 @@ func (p *provisioner) moveFile(o terraform.UIOutput, comm communicator.Communica
|
||||||
if err := comm.Start(cmd); err != nil {
|
if err := comm.Start(cmd); err != nil {
|
||||||
return fmt.Errorf("Unable to move %s to %s: %s", src, dst, err)
|
return fmt.Errorf("Unable to move %s to %s: %s", src, dst, err)
|
||||||
}
|
}
|
||||||
cmd.Wait()
|
if err := cmd.Wait(); err != nil {
|
||||||
if cmd.ExitStatus() != 0 {
|
if rc, ok := err.(remote.ExitError); ok {
|
||||||
return fmt.Errorf("Unable to move %s to %s: exit status: %d", src, dst, cmd.ExitStatus())
|
return fmt.Errorf("Unable to move %s to %s: exit status: %d", src, dst, rc)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
return cmd.Err()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *provisioner) createDir(o terraform.UIOutput, comm communicator.Communicator, dir string) error {
|
func (p *provisioner) createDir(o terraform.UIOutput, comm communicator.Communicator, dir string) error {
|
||||||
|
@ -377,11 +357,13 @@ func (p *provisioner) createDir(o terraform.UIOutput, comm communicator.Communic
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Wait()
|
if err := cmd.Wait(); err != nil {
|
||||||
if cmd.ExitStatus() != 0 {
|
if _, ok := err.(remote.ExitError); ok {
|
||||||
return fmt.Errorf("Non-zero exit status.")
|
return fmt.Errorf("Non-zero exit status.")
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return cmd.Err()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *provisioner) removeDir(o terraform.UIOutput, comm communicator.Communicator, dir string) error {
|
func (p *provisioner) removeDir(o terraform.UIOutput, comm communicator.Communicator, dir string) error {
|
||||||
|
@ -392,11 +374,13 @@ func (p *provisioner) removeDir(o terraform.UIOutput, comm communicator.Communic
|
||||||
if err := comm.Start(cmd); err != nil {
|
if err := comm.Start(cmd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cmd.Wait()
|
if err := cmd.Wait(); err != nil {
|
||||||
if cmd.ExitStatus() != 0 {
|
if _, ok := err.(remote.ExitError); ok {
|
||||||
return fmt.Errorf("Non-zero exit status.")
|
return fmt.Errorf("Non-zero exit status.")
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return cmd.Err()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *provisioner) uploadDir(o terraform.UIOutput, comm communicator.Communicator, dst, src string, ignore []string) error {
|
func (p *provisioner) uploadDir(o terraform.UIOutput, comm communicator.Communicator, dst, src string, ignore []string) error {
|
||||||
|
@ -549,8 +533,7 @@ func decodeConfig(d *schema.ResourceData) (*provisioner, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func copyOutput(
|
func copyOutput(
|
||||||
o terraform.UIOutput, r io.Reader, doneCh chan<- struct{}) {
|
o terraform.UIOutput, r io.Reader) {
|
||||||
defer close(doneCh)
|
|
||||||
lr := linereader.New(r)
|
lr := linereader.New(r)
|
||||||
for line := range lr.Ch {
|
for line := range lr.Ch {
|
||||||
o.Output(line)
|
o.Output(line)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package remote
|
package remote
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
@ -59,23 +60,28 @@ func (c *Cmd) SetExitStatus(status int, err error) {
|
||||||
close(c.exitCh)
|
close(c.exitCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Err returns any communicator related error.
|
|
||||||
func (c *Cmd) Err() error {
|
|
||||||
c.Lock()
|
|
||||||
defer c.Unlock()
|
|
||||||
|
|
||||||
return c.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExitStatus returns the exit status of the remote command
|
|
||||||
func (c *Cmd) ExitStatus() int {
|
|
||||||
c.Lock()
|
|
||||||
defer c.Unlock()
|
|
||||||
|
|
||||||
return c.exitStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait waits for the remote command to complete.
|
// Wait waits for the remote command to complete.
|
||||||
func (c *Cmd) Wait() {
|
// Wait may return an error from the communicator, or an ExitError if the
|
||||||
|
// process exits with a non-zero exit status.
|
||||||
|
func (c *Cmd) Wait() error {
|
||||||
<-c.exitCh
|
<-c.exitCh
|
||||||
|
|
||||||
|
c.Lock()
|
||||||
|
defer c.Unlock()
|
||||||
|
|
||||||
|
if c.err != nil {
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.exitStatus != 0 {
|
||||||
|
return ExitError(c.exitStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExitError int
|
||||||
|
|
||||||
|
func (e ExitError) Error() string {
|
||||||
|
return fmt.Sprintf("exit status: %d", e)
|
||||||
}
|
}
|
||||||
|
|
|
@ -359,11 +359,11 @@ func (c *Communicator) UploadScript(path string, input io.Reader) error {
|
||||||
"Error chmodding script file to 0777 in remote "+
|
"Error chmodding script file to 0777 in remote "+
|
||||||
"machine: %s", err)
|
"machine: %s", err)
|
||||||
}
|
}
|
||||||
cmd.Wait()
|
|
||||||
if cmd.ExitStatus() != 0 {
|
if err := cmd.Wait(); err != nil {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"Error chmodding script file to 0777 in remote "+
|
"Error chmodding script file to 0777 in remote "+
|
||||||
"machine %d: %s %s", cmd.ExitStatus(), stdout.String(), stderr.String())
|
"machine %v: %s %s", err, stdout.String(), stderr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -219,13 +219,10 @@ func TestLostConnection(t *testing.T) {
|
||||||
c.Disconnect()
|
c.Disconnect()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
cmd.Wait()
|
err = cmd.Wait()
|
||||||
if cmd.Err() == nil {
|
if err == nil {
|
||||||
t.Fatal("expected communicator error")
|
t.Fatal("expected communicator error")
|
||||||
}
|
}
|
||||||
if cmd.ExitStatus() != 0 {
|
|
||||||
t.Fatal("command should not have returned an exit status")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHostKey(t *testing.T) {
|
func TestHostKey(t *testing.T) {
|
||||||
|
|
Loading…
Reference in New Issue