diff --git a/communicator/ssh/communicator.go b/communicator/ssh/communicator.go index 289543495..aadc69058 100644 --- a/communicator/ssh/communicator.go +++ b/communicator/ssh/communicator.go @@ -118,15 +118,13 @@ func (c *comm) Start(cmd *packer.RemoteCmd) (err error) { log.Printf("Remote command exited with '%d': %s", exitStatus, cmd.Command) case *ssh.ExitMissingError: log.Printf("Remote command exited without exit status or exit signal.") - exitStatus = -1 + exitStatus = packer.CmdDisconnect default: log.Printf("Error occurred waiting for ssh session: %s", err.Error()) - exitStatus = -1 } } cmd.SetExited(exitStatus) }() - return } diff --git a/packer/communicator.go b/packer/communicator.go index f989dbfc9..83a4a60de 100644 --- a/packer/communicator.go +++ b/packer/communicator.go @@ -9,6 +9,10 @@ import ( "github.com/mitchellh/iochan" ) +// CmdDisconnect is a sentry value to indicate a RemoteCmd +// exited because the remote side disconnected us. +const CmdDisconnect int = 2300218 + // RemoteCmd represents a remote command being prepared or run. type RemoteCmd struct { // Command is the command to run remotely. This is executed as if diff --git a/provisioner/shell/provisioner.go b/provisioner/shell/provisioner.go index 82224922d..3575c70cd 100644 --- a/provisioner/shell/provisioner.go +++ b/provisioner/shell/provisioner.go @@ -70,6 +70,8 @@ type Config struct { // Whether to clean scripts up SkipClean bool `mapstructure:"skip_clean"` + ExpectDisconnect *bool `mapstructure:"expect_disconnect"` + startRetryTimeout time.Duration ctx interpolate.Context } @@ -101,6 +103,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { p.config.ExecuteCommand = "chmod +x {{.Path}}; {{.Vars}} {{.Path}}" } + if p.config.ExpectDisconnect == nil { + t := true + p.config.ExpectDisconnect = &t + } + if p.config.Inline != nil && len(p.config.Inline) == 0 { p.config.Inline = nil } @@ -283,11 +290,18 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error { cmd = &packer.RemoteCmd{Command: command} return cmd.StartWithUi(comm, ui) }) + if err != nil { return err } - if cmd.ExitStatus != 0 { + // If the exit code indicates a remote disconnect, fail unless + // we were expecting it. + if cmd.ExitStatus == packer.CmdDisconnect { + if !*p.config.ExpectDisconnect { + return fmt.Errorf("Script disconnected unexpectedly.") + } + } else if cmd.ExitStatus != 0 { return fmt.Errorf("Script exited with non-zero exit status: %d", cmd.ExitStatus) } diff --git a/provisioner/shell/provisioner_test.go b/provisioner/shell/provisioner_test.go index 0b87da7bf..a007ef83e 100644 --- a/provisioner/shell/provisioner_test.go +++ b/provisioner/shell/provisioner_test.go @@ -32,11 +32,42 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { t.Fatalf("err: %s", err) } + if *p.config.ExpectDisconnect != true { + t.Errorf("expected ExpectDisconnect to be true") + } + if p.config.RemotePath == "" { t.Errorf("unexpected remote path: %s", p.config.RemotePath) } } +func TestProvisionerPrepare_ExpectDisconnect(t *testing.T) { + config := testConfig() + p := new(Provisioner) + config["expect_disconnect"] = false + + err := p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + if *p.config.ExpectDisconnect != false { + t.Errorf("expected ExpectDisconnect to be false") + } + + config["expect_disconnect"] = true + p = new(Provisioner) + err = p.Prepare(config) + if err != nil { + t.Fatalf("err: %s", err) + } + + if *p.config.ExpectDisconnect != true { + t.Errorf("expected ExpectDisconnect to be true") + } + +} + func TestProvisionerPrepare_InlineShebang(t *testing.T) { config := testConfig() diff --git a/website/source/docs/provisioners/shell.html.md b/website/source/docs/provisioners/shell.html.md index f693c6ddb..e0a193aff 100644 --- a/website/source/docs/provisioners/shell.html.md +++ b/website/source/docs/provisioners/shell.html.md @@ -71,6 +71,10 @@ Optional parameters: available variables: `Path`, which is the path to the script to run, and `Vars`, which is the list of `environment_vars`, if configured. +- `expect_disconnect` (bool) - Defaults to true. Whether to error if the + server disconnects us. A disconnect might happen if you restart the ssh + server or reboot the host. May default to false in the future. + - `inline_shebang` (string) - The [shebang](https://en.wikipedia.org/wiki/Shebang_%28Unix%29) value to use when running commands specified by `inline`. By default, this is `/bin/sh -e`. If