-
Notifications
You must be signed in to change notification settings - Fork 112
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split errors and exit error wrappers to separate files
- Loading branch information
Showing
2 changed files
with
171 additions
and
164 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
package tfexec | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"os/exec" | ||
"regexp" | ||
"strings" | ||
) | ||
|
||
// this file contains errors parsed from stderr | ||
|
||
var ( | ||
// The "Required variable not set:" case is for 0.11 | ||
missingVarErrRegexp = regexp.MustCompile(`Error: No value for required variable|Error: Required variable not set:`) | ||
missingVarNameRegexp = regexp.MustCompile(`The root module input variable "(.+)" is not set, and has no default|Error: Required variable not set: (.+)`) | ||
|
||
usageRegexp = regexp.MustCompile(`Too many command line arguments|^Usage: .*Options:.*|Error: Invalid -\d+ option`) | ||
|
||
// "Could not load plugin" is present in 0.13 | ||
noInitErrRegexp = regexp.MustCompile(`Error: Could not satisfy plugin requirements|Error: Could not load plugin`) | ||
|
||
noConfigErrRegexp = regexp.MustCompile(`Error: No configuration files`) | ||
|
||
workspaceDoesNotExistRegexp = regexp.MustCompile(`Workspace "(.+)" doesn't exist.`) | ||
|
||
workspaceAlreadyExistsRegexp = regexp.MustCompile(`Workspace "(.+)" already exists`) | ||
|
||
tfVersionMismatchErrRegexp = regexp.MustCompile(`Error: The currently running version of Terraform doesn't meet the|Error: Unsupported Terraform Core version`) | ||
tfVersionMismatchConstraintRegexp = regexp.MustCompile(`required_version = "(.+)"|Required version: (.+)\b`) | ||
) | ||
|
||
func (tf *Terraform) parseError(err error, stderr string) error { | ||
ee, ok := err.(*exec.ExitError) | ||
if !ok { | ||
return err | ||
} | ||
|
||
switch { | ||
case tfVersionMismatchErrRegexp.MatchString(stderr): | ||
constraint := "" | ||
constraints := tfVersionMismatchConstraintRegexp.FindStringSubmatch(stderr) | ||
for i := 1; i < len(constraints); i++ { | ||
constraint = strings.TrimSpace(constraints[i]) | ||
if constraint != "" { | ||
break | ||
} | ||
} | ||
|
||
if constraint == "" { | ||
// hardcode a value here for weird cases (incl. 0.12) | ||
constraint = "unknown" | ||
} | ||
|
||
// only set this if it happened to be cached already | ||
ver := "" | ||
if tf != nil && tf.execVersion != nil { | ||
ver = tf.execVersion.String() | ||
} | ||
|
||
return &ErrTFVersionMismatch{ | ||
Constraint: constraint, | ||
TFVersion: ver, | ||
} | ||
case missingVarErrRegexp.MatchString(stderr): | ||
name := "" | ||
names := missingVarNameRegexp.FindStringSubmatch(stderr) | ||
for i := 1; i < len(names); i++ { | ||
name = strings.TrimSpace(names[i]) | ||
if name != "" { | ||
break | ||
} | ||
} | ||
|
||
return &ErrMissingVar{name} | ||
case usageRegexp.MatchString(stderr): | ||
return &ErrCLIUsage{stderr: stderr} | ||
case noInitErrRegexp.MatchString(stderr): | ||
return &ErrNoInit{stderr: stderr} | ||
case noConfigErrRegexp.MatchString(stderr): | ||
return &ErrNoConfig{stderr: stderr} | ||
case workspaceDoesNotExistRegexp.MatchString(stderr): | ||
submatches := workspaceDoesNotExistRegexp.FindStringSubmatch(stderr) | ||
if len(submatches) == 2 { | ||
return &ErrNoWorkspace{submatches[1]} | ||
} | ||
case workspaceAlreadyExistsRegexp.MatchString(stderr): | ||
submatches := workspaceAlreadyExistsRegexp.FindStringSubmatch(stderr) | ||
if len(submatches) == 2 { | ||
return &ErrWorkspaceExists{submatches[1]} | ||
} | ||
} | ||
errString := strings.TrimSpace(stderr) | ||
if errString == "" { | ||
// if stderr is empty, return the ExitError directly, as it will have a better message | ||
return ee | ||
} | ||
return errors.New(stderr) | ||
} | ||
|
||
type ErrMissingVar struct { | ||
VariableName string | ||
} | ||
|
||
func (err *ErrMissingVar) Error() string { | ||
return fmt.Sprintf("variable %q was required but not supplied", err.VariableName) | ||
} | ||
|
||
type ErrNoWorkspace struct { | ||
Name string | ||
} | ||
|
||
func (err *ErrNoWorkspace) Error() string { | ||
return fmt.Sprintf("workspace %q does not exist", err.Name) | ||
} | ||
|
||
// ErrWorkspaceExists is returned when creating a workspace that already exists | ||
type ErrWorkspaceExists struct { | ||
Name string | ||
} | ||
|
||
func (err *ErrWorkspaceExists) Error() string { | ||
return fmt.Sprintf("workspace %q already exists", err.Name) | ||
} | ||
|
||
type ErrNoInit struct { | ||
stderr string | ||
} | ||
|
||
func (e *ErrNoInit) Error() string { | ||
return e.stderr | ||
} | ||
|
||
type ErrNoConfig struct { | ||
stderr string | ||
} | ||
|
||
func (e *ErrNoConfig) Error() string { | ||
return e.stderr | ||
} | ||
|
||
// ErrCLIUsage is returned when the combination of flags or arguments is incorrect. | ||
// | ||
// CLI indicates usage errors in three different ways: either | ||
// 1. Exit 1, with a custom error message on stderr. | ||
// 2. Exit 1, with command usage logged to stderr. | ||
// 3. Exit 127, with command usage logged to stdout. | ||
// Currently cases 1 and 2 are handled. | ||
// TODO KEM: Handle exit 127 case. How does this work on non-Unix platforms? | ||
type ErrCLIUsage struct { | ||
stderr string | ||
} | ||
|
||
func (e *ErrCLIUsage) Error() string { | ||
return e.stderr | ||
} | ||
|
||
// ErrTFVersionMismatch is returned when the running Terraform version is not compatible with the | ||
// value specified for required_version in the terraform block. | ||
type ErrTFVersionMismatch struct { | ||
TFVersion string | ||
|
||
// Constraint is not returned in the error messaging on 0.12 | ||
Constraint string | ||
} | ||
|
||
func (e *ErrTFVersionMismatch) Error() string { | ||
return "terraform core version not supported by configuration" | ||
} |