Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

add formatting for hcl parsing error messages #5972

Merged
merged 4 commits into from
Jul 19, 2019
Merged

Conversation

jazzyfresh
Copy link
Contributor

@jazzyfresh jazzyfresh commented Jul 17, 2019

Overview

Clean up error messages produced by hcl2 library

Behavior

Before (error messages had these weird numbers in them)

nomad agent -dev logs

Screenshot from 2019-07-17 10-40-24


nomad alloc status command

Screenshot from 2019-07-17 10-21-48


After (removed weird numbers and make message about invalid labels, not JSON)

nomad agent -dev logs

Screenshot from 2019-07-17 10-41-07


nomad alloc status command

Screenshot from 2019-07-19 10-02-54

Implementation

  • added private method to helper/pluginutils/hclutils/utils.go for hcl diagnostics error message formatting
    • it constructs an error from the diagnostic summary and detail
    • it ignores the diagnostic subject, which is just garbage "file line numbers" constructed from streamed bytes
    • if it finds a diagnostic that matches "Extraneous JSON label..." it creates an error with a nicer message
    • future work: submit pull request to hcl2 library with better error messages
  • also changed where the task event emission for an error happens
    • moved it inside runDriver (was formerly in MAIN: in run())
    • added task events for each error
    • config parsing errors emit TaskFailedValidation event
    • this appears to remove the "failed to parse config" logs that were added to nomad alloc status results when the event was TaskDriverFailure, so I might need to look into that more if we want to preserve that behavior

Copy link
Member

@nickethier nickethier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've never been so excited for error formatting! Looking good, just want to clear up my confusion on the comment about the err var.

if diag.HasErrors() {
return multierror.Append(errors.New("failed to parse config"), diag.Errs()...)
tr.EmitEvent(structs.NewTaskEvent(structs.TaskFailedValidation).SetDriverError(err))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't tell where this err comes from, is it the correct error to use here or should it be something from diagErrs?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you, good catch! I am supposed to pass in the multierror that gets created in the next line, but blindly copy-pastad and forgot to change it. This was part of an attempt to create more granular task events (eg. TaskFailedValidation for config parsing failures, and TaskDriverFailure for failures that were because of the driver), so I moved the event emission out of MAIN: and into runDriver(). I am not sure if that is too messy for a small change like this, so I can put the task events back the way they were...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fixed, but please give a double check

client/allocrunner/taskrunner/task_runner.go Show resolved Hide resolved
@@ -70,7 +70,7 @@ func (b *HCLParser) parse(t *testing.T, config, out interface{}) {
decSpec, diags := hclspecutils.Convert(b.spec)
require.Empty(t, diags)

ctyValue, diag := ParseHclInterface(config, decSpec, b.vars)
ctyValue, diag, _ := ParseHclInterface(config, decSpec, b.vars)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use err instead of _ here, and require.Nil on the error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor

@preetapan preetapan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had one test suggestion.

Also, could you update the PR description with a before and after? Was hard to tell what this change ends up doing to error messages.

@jazzyfresh
Copy link
Contributor Author

Thank you @preetapan, I also added a separate TestParseInvalid to test this behavior directly. Working on the description now...

@jazzyfresh
Copy link
Contributor Author

@preetapan @nickethier description is updated

@nickethier
Copy link
Member

@jazzyfresh Could you look into the test failures they look to be related to your changes

@jazzyfresh
Copy link
Contributor Author

jazzyfresh commented Jul 17, 2019

@nickethier yes.. I knew I would inevitably break something in GOTEST_PKGS_EXCLUDE="./api|./client|./drivers/docker|./drivers/exec|./nomad"

https://travis-ci.org/hashicorp/nomad/jobs/560098109#L793

taskrunner/task_runner.go:489: task_runner: running driver failed: task=web error="driver failure"

My guess is that changing the task emit events changed something... or it's something else, I'm still looking into it.

Copy link
Member

@nickethier nickethier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! LGTM once lint/tests are passing!

Copy link
Contributor

@notnoop notnoop left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great. This feels much better already; thanks! I noticed a couple of formatting issues, but good otherwise.

func formattedDiagnosticErrors(diag hcl.Diagnostics) []error {
var errs []error
for _, d := range diag {
if d.Summary == "Extraneous JSON object property" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add a comment pointing to where this text originates, so future us and notice when it changes or if this is no longer needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Added the comment before the method

if diag.HasErrors() {
return multierror.Append(errors.New("failed to parse config"), diag.Errs()...)
parseErr := multierror.Append(errors.New("failed to parse config"), diagErrs...)
tr.EmitEvent(structs.NewTaskEvent(structs.TaskFailedValidation).SetDriverError(parseErr))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that in the snippet that this returns a generic Validation of task failed as the message. It's because TaskFailedValidation expects ValidationError[1] to be set instead of DriverError.

Also, using the multierror.Append generates very awkward messages, like:

2019-07-18T10:22:30+07:00  Failed Validation  2 error(s) occurred:

* failed to parse config
* Invalid label: No argument or block type is named "bad_key".

It'd be nice to format the errors ourselves and present something like the following

2019-07-18T10:22:30+07:00  Failed Validation  failed to parse config; found the following:

* Invalid label: No argument or block type is named "bad_key".

[1]

case api.TaskFailedValidation:
if event.ValidationError != "" {
desc = event.ValidationError
} else {
desc = "Validation of task failed"
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going to address better multierror formatting in another PR

multierror.Append(&mErr, diag.Errs()...)
return multierror.Prefix(&mErr, "failed parsing config:")
multierror.Append(&mErr, diagErrs...)
return multierror.Prefix(&mErr, "failed to parse config")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd keep the trailing colon here, as Prefix() prefixes all elements rather than just once on top. I have a sample output in https://play.golang.org/p/sj0JcGRRVGH .

Though, I'd suggest avoiding multierror formatting. I find it awkward and we can reuse the formatter from other callers.

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/hashicorp/go-multierror"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line should appear in following grouping of imports, along with other non-stdlib imports. I'd suggest configuring your IDE to use goimports on save instead of plain gofmt.

errStr = fmt.Sprintf("%s\n* %s", errStr, err.Error())
}
return errors.New(errStr)
return multierror.Append(errors.New("failed to parse config"), diagErrs...)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of all of these, I like this previous formatting the most :P.

@jazzyfresh jazzyfresh merged commit e31db57 into master Jul 19, 2019
@github-actions
Copy link

github-actions bot commented Feb 6, 2023

I'm going to lock this pull request because it has been closed for 120 days ⏳. This helps our maintainers find and focus on the active contributions.
If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 6, 2023
# for free to subscribe to this conversation on GitHub. Already have an account? #.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants