-
-
Notifications
You must be signed in to change notification settings - Fork 995
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Improve *-all error message output #722
base: main
Are you sure you want to change the base?
Changes from all commits
4235479
b68b1ef
0053f8b
c6abfd9
1fa02b3
09a26c9
e0aafd6
b6d4aec
ad68cec
6068be2
c56c680
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
package configstack | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"strings" | ||
"sync" | ||
|
||
|
@@ -27,6 +29,9 @@ type runningModule struct { | |
Dependencies map[string]*runningModule | ||
NotifyWhenDone []*runningModule | ||
FlagExcluded bool | ||
OutStream bytes.Buffer | ||
ErrStream bytes.Buffer | ||
Writer io.Writer | ||
} | ||
|
||
// This controls in what order dependencies should be enforced between modules | ||
|
@@ -37,6 +42,13 @@ const ( | |
ReverseOrder | ||
) | ||
|
||
var ( | ||
// OutputMessageSeparator is the string used for separating the different module outputs | ||
OutputMessageSeparator = strings.Repeat("-", 132) | ||
// DetailedErrorMessageDivider is the string used for diving the detailed error output at the end of the execution from the rest of the output | ||
DetailedErrorMessageDivider = strings.Repeat("=", 132) | ||
) | ||
|
||
// Create a new RunningModule struct for the given module. This will initialize all fields to reasonable defaults, | ||
// except for the Dependencies and NotifyWhenDone, both of which will be empty. You should fill these using a | ||
// function such as crossLinkDependencies. | ||
|
@@ -48,6 +60,7 @@ func newRunningModule(module *TerraformModule) *runningModule { | |
Dependencies: map[string]*runningModule{}, | ||
NotifyWhenDone: []*runningModule{}, | ||
FlagExcluded: module.FlagExcluded, | ||
Writer: module.TerragruntOptions.Writer, | ||
} | ||
} | ||
|
||
|
@@ -130,6 +143,7 @@ func removeFlagExcluded(modules map[string]*runningModule) map[string]*runningMo | |
Err: module.Err, | ||
NotifyWhenDone: module.NotifyWhenDone, | ||
Status: module.Status, | ||
Writer: module.Module.TerragruntOptions.Writer, | ||
} | ||
|
||
// Only add dependencies that should not be excluded | ||
|
@@ -154,6 +168,8 @@ func runModules(modules map[string]*runningModule) error { | |
waitGroup.Add(1) | ||
go func(module *runningModule) { | ||
defer waitGroup.Done() | ||
module.Module.TerragruntOptions.ErrWriter = io.MultiWriter(&module.OutStream, &module.ErrStream) | ||
module.Module.TerragruntOptions.Writer = &module.OutStream | ||
module.runModuleWhenReady() | ||
}(module) | ||
} | ||
|
@@ -167,17 +183,37 @@ func runModules(modules map[string]*runningModule) error { | |
// occurred | ||
func collectErrors(modules map[string]*runningModule) error { | ||
errs := []error{} | ||
detailedErrs := []error{} | ||
|
||
for _, module := range modules { | ||
if module.Err != nil { | ||
errs = append(errs, module.Err) | ||
detailedErrs = append(detailedErrs, generateDetailedErrorMessage(module)) | ||
} | ||
} | ||
|
||
if len(errs) == 0 { | ||
return nil | ||
} | ||
|
||
return errors.WithStackTrace(MultiError{Errors: errs, DetailedErrors: detailedErrs}) | ||
} | ||
|
||
// generateDetailedErrorMessage extracts the clean stderr from a module and formats it for printing | ||
func generateDetailedErrorMessage(module *runningModule) error { | ||
// remove the auto-init pollution from the error stream | ||
cleanErrorOutput := strings.Replace(module.ErrStream.String(), module.Module.TerragruntOptions.InitStream.String(), "", -1) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, string replacement feels... A bit hacky. Is there any way to have the AutoInit write to |
||
|
||
// determine whether the error is a dependency error | ||
var errorType string | ||
|
||
if _, isDepErr := module.Err.(DependencyFinishedWithError); isDepErr { | ||
errorType = "(dependency error)" | ||
} else { | ||
return errors.WithStackTrace(MultiError{Errors: errs}) | ||
errorType = "(root error)" | ||
} | ||
|
||
return fmt.Errorf("%s %s: \n\n%s \n\n%s\n%s", module.Module.Path, errorType, module.Err, cleanErrorOutput, OutputMessageSeparator) | ||
} | ||
|
||
// Run a module once all of its dependencies have finished executing. | ||
|
@@ -223,6 +259,7 @@ func (module *runningModule) runNow() error { | |
module.Module.TerragruntOptions.Logger.Printf("Running module %s now", module.Module.Path) | ||
return module.Module.TerragruntOptions.RunTerragrunt(module.Module.TerragruntOptions) | ||
} | ||
|
||
} | ||
|
||
// Record that a module has finished executing and notify all of this module's dependencies | ||
|
@@ -233,6 +270,8 @@ func (module *runningModule) moduleFinished(moduleErr error) { | |
module.Module.TerragruntOptions.Logger.Printf("Module %s has finished with an error: %v", module.Module.Path, moduleErr) | ||
} | ||
|
||
fmt.Fprintf(module.Writer, "%s\n%v\n\n%v\n", OutputMessageSeparator, module.Module.Path, module.OutStream.String()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why |
||
|
||
module.Status = Finished | ||
module.Err = moduleErr | ||
|
||
|
@@ -261,15 +300,16 @@ func (this DependencyFinishedWithError) ExitStatus() (int, error) { | |
} | ||
|
||
type MultiError struct { | ||
Errors []error | ||
Errors []error | ||
DetailedErrors []error | ||
} | ||
|
||
func (err MultiError) Error() string { | ||
errorStrings := []string{} | ||
for _, err := range err.Errors { | ||
for _, err := range err.DetailedErrors { | ||
errorStrings = append(errorStrings, err.Error()) | ||
} | ||
return fmt.Sprintf("Encountered the following errors:\n%s", strings.Join(errorStrings, "\n")) | ||
return fmt.Sprintf("Encountered the following errors:\n%s\n%s", DetailedErrorMessageDivider, strings.Join(errorStrings, "\n")) | ||
} | ||
|
||
func (this MultiError) ExitStatus() (int, error) { | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,6 +1,7 @@ | ||||||
package options | ||||||
|
||||||
import ( | ||||||
"bytes" | ||||||
"fmt" | ||||||
"io" | ||||||
"log" | ||||||
|
@@ -80,6 +81,9 @@ type TerragruntOptions struct { | |||||
// If you want stderr to go somewhere other than os.stderr | ||||||
ErrWriter io.Writer | ||||||
|
||||||
// Stores output of auto-init so it can be removed later form other streams | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
InitStream bytes.Buffer | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps there should be an Also, should this value be in the |
||||||
|
||||||
// When searching the directory tree, this is the max folders to check before exiting with an error. This is | ||||||
// exposed here primarily so we can set it to a low value at test time. | ||||||
MaxFoldersToCheck int | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused by this... You're overwriting
TerragruntOptions.ErrWriter
to write to bothmodule.OutStream
andmodule.ErrStream
... But what was theTerragrunt.LOptions.ErrWriter
value set to before that? Aremodule.OutStream
andmodule.ErrStream
initialized to anything? Will this buffer those errors until the very end or stream tostdout
/stderr
?Same questions go for
TerragruntOptions.Writer
, with the additional one of what happens when you point a second item tomodule.OutStream
?