Merge pull request #7385 from hashicorp/windows_shell_allow_exit_code

allowed_exit_codes for windows-shell and shell provisioners
This commit is contained in:
Megan Marsh 2019-03-15 16:13:45 -07:00 committed by GitHub
commit 8853fc1946
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 159 additions and 203 deletions

39
common/shell/exit_code.go Normal file
View File

@ -0,0 +1,39 @@
package shell
import "fmt"
func (p *Provisioner) ValidExitCode(code int) error {
// Check exit code against allowed codes (likely just 0)
validCodes := p.ValidExitCodes
if len(validCodes) == 0 {
validCodes = []int{0}
}
validExitCode := false
for _, v := range validCodes {
if code == v {
validExitCode = true
break
}
}
if !validExitCode {
return &ErrorInvalidExitCode{
Code: code,
Allowed: validCodes,
}
}
return nil
}
type ErrorInvalidExitCode struct {
Code int
Allowed []int
}
func (e *ErrorInvalidExitCode) Error() string {
if e == nil {
return "<nil>"
}
return fmt.Sprintf("Script exited with non-zero exit status: %d."+
"Allowed exit codes are: %v",
e.Code, e.Allowed)
}

View File

@ -0,0 +1,31 @@
package shell
import (
"fmt"
"testing"
)
func TestProvisioner_ValidExitCode(t *testing.T) {
tests := []struct {
exitCodes []int
code int
wantErr bool
}{
{nil, 0, false},
{nil, 1, true},
{[]int{2}, 2, false},
{[]int{2}, 3, true},
}
for n := range tests {
tt := tests[n]
t.Run(fmt.Sprintf("%v - %v - %v", tt.exitCodes, tt.code, tt.wantErr), func(t *testing.T) {
p := Provisioner{
ValidExitCodes: tt.exitCodes,
}
if err := p.ValidExitCode(tt.code); (err != nil) != tt.wantErr {
t.Errorf("Provisioner.ValidExitCode() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

42
common/shell/shell.go Normal file
View File

@ -0,0 +1,42 @@
// Package shell defines code that is common in shells
package shell
import "github.com/hashicorp/packer/common"
// Provisioner contains common fields to all shell provisioners
type Provisioner struct {
common.PackerConfig `mapstructure:",squash"`
// If true, the script contains binary and line endings will not be
// converted from Windows to Unix-style.
Binary bool
// The command used to execute the script. The '{{ .Path }}' variable
// should be used to specify where the script goes, {{ .Vars }}
// can be used to inject the environment_vars into the environment.
ExecuteCommand string `mapstructure:"execute_command"`
// An inline script to execute. Multiple strings are all executed
// in the context of a single shell.
Inline []string
// The remote path where the local shell script will be uploaded to.
// This should be set to a writable file that is in a pre-existing directory.
// This defaults to remote_folder/remote_file
RemotePath string `mapstructure:"remote_path"`
// The local path of the shell script to upload and execute.
Script string
// An array of multiple scripts to run.
Scripts []string
// Valid Exit Codes - 0 is not always the only valid error code! See
// http://www.symantec.com/connect/articles/windows-system-error-codes-exit-codes-description
// for examples such as 3010 - "The requested operation is successful.
ValidExitCodes []int `mapstructure:"valid_exit_codes"`
// An array of environment variables that will be injected before
// your command(s) are executed.
Vars []string `mapstructure:"environment_vars"`
}

View File

@ -13,6 +13,7 @@ import (
"time"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/shell"
"github.com/hashicorp/packer/common/uuid"
commonhelper "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/helper/config"
@ -32,41 +33,13 @@ var psEscape = strings.NewReplacer(
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
// If true, the script contains binary and line endings will not be
// converted from Windows to Unix-style.
Binary bool
// An inline script to execute. Multiple strings are all executed in the
// context of a single shell.
Inline []string
// The local path of the powershell script to upload and execute.
Script string
// An array of multiple scripts to run.
Scripts []string
// An array of environment variables that will be injected before your
// command(s) are executed.
Vars []string `mapstructure:"environment_vars"`
// The remote path where the local powershell script will be uploaded to.
// This should be set to a writable file that is in a pre-existing
// directory.
RemotePath string `mapstructure:"remote_path"`
shell.Provisioner `mapstructure:",squash"`
// The remote path where the file containing the environment variables
// will be uploaded to. This should be set to a writable file that is in a
// pre-existing directory.
RemoteEnvVarPath string `mapstructure:"remote_env_var_path"`
// The command used to execute the script. The '{{ .Path }}' variable
// should be used to specify where the script goes, {{ .Vars }} can be
// used to inject the environment_vars into the environment.
ExecuteCommand string `mapstructure:"execute_command"`
// The command used to execute the elevated script. The '{{ .Path }}'
// variable should be used to specify where the script goes, {{ .Vars }}
// can be used to inject the environment_vars into the environment.
@ -91,12 +64,6 @@ type Config struct {
ElevatedUser string `mapstructure:"elevated_user"`
ElevatedPassword string `mapstructure:"elevated_password"`
// Valid Exit Codes - 0 is not always the only valid error code! See
// http://www.symantec.com/connect/articles/windows-system-error-codes-exit-codes-description
// for examples such as 3010 - "The requested operation is successful.
// Changes will not be effective until the system is rebooted."
ValidExitCodes []int `mapstructure:"valid_exit_codes"`
ctx interpolate.Context
}
@ -179,10 +146,6 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
p.config.Vars = make([]string, 0)
}
if p.config.ValidExitCodes == nil {
p.config.ValidExitCodes = []int{0}
}
var errs error
if p.config.Script != "" && len(p.config.Scripts) > 0 {
errs = packer.MultiErrorAppend(errs,
@ -307,17 +270,8 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
// Close the original file since we copied it
f.Close()
// Check exit code against allowed codes (likely just 0)
validExitCode := false
for _, v := range p.config.ValidExitCodes {
if cmd.ExitStatus == v {
validExitCode = true
}
}
if !validExitCode {
return fmt.Errorf(
"Script exited with non-zero exit status: %d. Allowed exit codes are: %v",
cmd.ExitStatus, p.config.ValidExitCodes)
if err := p.config.ValidExitCode(cmd.ExitStatus); err != nil {
return err
}
}

View File

@ -87,18 +87,6 @@ func TestProvisionerPrepare_Defaults(t *testing.T) {
t.Fatalf(`Default command should be 'powershell -executionpolicy bypass "& { if (Test-Path variable:global:ProgressPreference){set-variable -name variable:global:ProgressPreference -value 'SilentlyContinue'};. {{.Vars}}; &'{{.Path}}'; exit $LastExitCode }"', but got '%s'`, p.config.ElevatedExecuteCommand)
}
if p.config.ValidExitCodes == nil {
t.Fatalf("ValidExitCodes should not be nil")
}
if p.config.ValidExitCodes != nil {
expCodes := []int{0}
for i, v := range p.config.ValidExitCodes {
if v != expCodes[i] {
t.Fatalf("Expected ValidExitCodes don't match actual")
}
}
}
if p.config.ElevatedEnvVarFormat != `$env:%s="%s"; ` {
t.Fatalf(`Default command should be powershell '$env:%%s="%%s"; ', but got %s`, p.config.ElevatedEnvVarFormat)
}

View File

@ -15,6 +15,7 @@ import (
"time"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/shell"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/packer/tmp"
@ -22,29 +23,11 @@ import (
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
// If true, the script contains binary and line endings will not be
// converted from Windows to Unix-style.
Binary bool
// An inline script to execute. Multiple strings are all executed
// in the context of a single shell.
Inline []string
shell.Provisioner `mapstructure:",squash"`
// The shebang value used when running inline scripts.
InlineShebang string `mapstructure:"inline_shebang"`
// The local path of the shell script to upload and execute.
Script string
// An array of multiple scripts to run.
Scripts []string
// An array of environment variables that will be injected before
// your command(s) are executed.
Vars []string `mapstructure:"environment_vars"`
// A duration of how long to pause after the provisioner
RawPauseAfter string `mapstructure:"pause_after"`
@ -62,16 +45,6 @@ type Config struct {
// This defaults to script_nnn.sh
RemoteFile string `mapstructure:"remote_file"`
// The remote path where the local shell script will be uploaded to.
// This should be set to a writable file that is in a pre-existing directory.
// This defaults to remote_folder/remote_file
RemotePath string `mapstructure:"remote_path"`
// The command used to execute the script. The '{{ .Path }}' variable
// should be used to specify where the script goes, {{ .Vars }}
// can be used to inject the environment_vars into the environment.
ExecuteCommand string `mapstructure:"execute_command"`
// The timeout for retrying to start the process. Until this timeout
// is reached, if the provisioner can't start a process, it retries.
// This can be set high to allow for reboots.
@ -362,10 +335,11 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
return fmt.Errorf("Script disconnected unexpectedly. " +
"If you expected your script to disconnect, i.e. from a " +
"restart, you can try adding `\"expect_disconnect\": true` " +
"to the shell provisioner parameters.")
"or `\"valid_exit_codes\": [0, 2300218]` to the shell " +
"provisioner parameters.")
}
} else if cmd.ExitStatus != 0 {
return fmt.Errorf("Script exited with non-zero exit status: %d", cmd.ExitStatus)
} else if err := p.config.ValidExitCode(cmd.ExitStatus); err != nil {
return err
}
if !p.config.SkipClean {

View File

@ -13,6 +13,7 @@ import (
"time"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/common/shell"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/packer/tmp"
@ -25,29 +26,7 @@ const DefaultRemotePath = "c:/Windows/Temp/script.bat"
var retryableSleep = 2 * time.Second
type Config struct {
common.PackerConfig `mapstructure:",squash"`
// If true, the script contains binary and line endings will not be
// converted from Windows to Unix-style.
Binary bool
// An inline script to execute. Multiple strings are all executed
// in the context of a single shell.
Inline []string
// The local path of the shell script to upload and execute.
Script string
// An array of multiple scripts to run.
Scripts []string
// An array of environment variables that will be injected before
// your command(s) are executed.
Vars []string `mapstructure:"environment_vars"`
// The remote path where the local shell script will be uploaded to.
// This should be set to a writable file that is in a pre-existing directory.
RemotePath string `mapstructure:"remote_path"`
shell.Provisioner `mapstructure:",squash"`
// The command used to execute the script. The '{{ .Path }}' variable
// should be used to specify where the script goes, {{ .Vars }}
@ -243,8 +222,8 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
// Close the original file since we copied it
f.Close()
if cmd.ExitStatus != 0 {
return fmt.Errorf("Script exited with non-zero exit status: %d", cmd.ExitStatus)
if err := p.config.ValidExitCode(cmd.ExitStatus); err != nil {
return err
}
}

View File

@ -31,33 +31,7 @@ The example below is fully functional.
## Configuration Reference
The reference of available configuration options is listed below. The only
required element is either "inline" or "script". Every other option is
optional.
Exactly *one* of the following is required:
- `inline` (array of strings) - This is an array of commands to execute. The
commands are concatenated by newlines and turned into a single file, so
they are all executed within the same context. This allows you to change
directories in one command and use something in the directory in the next
and so on. Inline scripts are the easiest way to pull off simple tasks
within the machine.
- `script` (string) - The path to a script to upload and execute in the
machine. This path can be absolute or relative. If it is relative, it is
relative to the working directory when Packer is executed.
- `scripts` (array of strings) - An array of scripts to execute. The scripts
will be uploaded and executed in the order specified. Each script is
executed in isolation, so state such as variables from one script won't
carry on to the next.
Optional parameters:
- `binary` (boolean) - If true, specifies that the script(s) are binary
files, and Packer should therefore not convert Windows line endings to Unix
line endings (if there are any). By default this is false.
<%= partial "partials/provisioners/shell-config" %>
- `elevated_execute_command` (string) - The command to use to execute the
elevated script. By default this is as follows:
@ -155,9 +129,6 @@ Optional parameters:
exists in order to deal with times when SSH may restart, such as a system
reboot. Set this to a higher value if reboots take a longer amount of time.
- `valid_exit_codes` (list of ints) - Valid exit codes for the script. By
default this is just 0.
## Default Environmental Variables
In addition to being able to specify custom environmental variables using the

View File

@ -33,33 +33,7 @@ The example below is fully functional.
## Configuration Reference
The reference of available configuration options is listed below. The only
required element is either "inline" or "script". Every other option is
optional.
Exactly *one* of the following is required:
- `inline` (array of strings) - This is an array of commands to execute. The
commands are concatenated by newlines and turned into a single file, so
they are all executed within the same context. This allows you to change
directories in one command and use something in the directory in the next
and so on. Inline scripts are the easiest way to pull off simple tasks
within the machine.
- `script` (string) - The path to a script to upload and execute in the
machine. This path can be absolute or relative. If it is relative, it is
relative to the working directory when Packer is executed.
- `scripts` (array of strings) - An array of scripts to execute. The scripts
will be uploaded and executed in the order specified. Each script is
executed in isolation, so state such as variables from one script won't
carry on to the next.
Optional parameters:
- `binary` (boolean) - If true, specifies that the script(s) are binary
files, and Packer should therefore not convert Windows line endings to Unix
line endings (if there are any). By default this is false.
<%= partial "partials/provisioners/shell-config" %>
- `environment_vars` (array of strings) - An array of key/value pairs to
inject prior to the execute\_command. The format should be `key=value`.

View File

@ -27,33 +27,7 @@ The example below is fully functional.
## Configuration Reference
The reference of available configuration options is listed below. The only
required element is either "inline" or "script". Every other option is
optional.
Exactly *one* of the following is required:
- `inline` (array of strings) - This is an array of commands to execute. The
commands are concatenated by newlines and turned into a single file, so
they are all executed within the same context. This allows you to change
directories in one command and use something in the directory in the next
and so on. Inline scripts are the easiest way to pull off simple tasks
within the machine.
- `script` (string) - The path to a script to upload and execute in the
machine. This path can be absolute or relative. If it is relative, it is
relative to the working directory when Packer is executed.
- `scripts` (array of strings) - An array of scripts to execute. The scripts
will be uploaded and executed in the order specified. Each script is
executed in isolation, so state such as variables from one script won't
carry on to the next.
Optional parameters:
- `binary` (boolean) - If true, specifies that the script(s) are binary
files, and Packer should therefore not convert Windows line endings to Unix
line endings (if there are any). By default this is false.
<%= partial "partials/provisioners/shell-config" %>
- `environment_vars` (array of strings) - An array of key/value pairs to
inject prior to the execute\_command. The format should be `key=value`.

View File

@ -0,0 +1,30 @@
The reference of available configuration options is listed below. The only
required element is either "inline" or "script". Every other option is
optional.
Exactly *one* of the following is required:
- `inline` (array of strings) - This is an array of commands to execute. The
commands are concatenated by newlines and turned into a single file, so
they are all executed within the same context. This allows you to change
directories in one command and use something in the directory in the next
and so on. Inline scripts are the easiest way to pull off simple tasks
within the machine.
- `script` (string) - The path to a script to upload and execute in the
machine. This path can be absolute or relative. If it is relative, it is
relative to the working directory when Packer is executed.
- `scripts` (array of strings) - An array of scripts to execute. The scripts
will be uploaded and executed in the order specified. Each script is
executed in isolation, so state such as variables from one script won't
carry on to the next.
Optional parameters:
- `binary` (boolean) - If true, specifies that the script(s) are binary
files, and Packer should therefore not convert Windows line endings to Unix
line endings (if there are any). By default this is false.
- `valid_exit_codes` (list of ints) - Valid exit codes for the script. By
default this is just 0.