diff --git a/internal/lagoonyml/lint.go b/internal/lagoonyml/lint.go index fde2604..a107e04 100644 --- a/internal/lagoonyml/lint.go +++ b/internal/lagoonyml/lint.go @@ -1,6 +1,8 @@ package lagoonyml import ( + "encoding/json" + "errors" "fmt" "os" @@ -20,9 +22,31 @@ func LintFile(path string, linters ...Linter) error { if err != nil { return fmt.Errorf("couldn't read %v: %v", path, err) } - err = yaml.Unmarshal(rawYAML, &l) - if err != nil { - return fmt.Errorf("couldn't unmarshal %v: %v", path, err) + yamlErr := yaml.Unmarshal(rawYAML, &l) + if yamlErr != nil { + // check to see if this is an indentation error + rawJSON, err := yaml.YAMLToJSON(rawYAML) + if err != nil { + // can't even convert this so return the original error + return fmt.Errorf("couldn't unmarshal %v: %v", path, yamlErr) + } + // json.Unmarshal returns richer errors than yaml.Unmarshal, which helps to + // diagnose exactly what went wrong + err = json.Unmarshal(rawJSON, &l) + var jTypeErr *json.UnmarshalTypeError + if errors.As(err, &jTypeErr) { + // this rawJSON slice will be partial JSON, but JSONToYAML ignores junk + // at the end of the snippet. + badYAML, err := yaml.JSONToYAML(rawJSON[jTypeErr.Offset:]) + if err != nil { + // can't convert this snippet, so return the original error + return fmt.Errorf("couldn't unmarshal %v: %v", path, yamlErr) + } + return fmt.Errorf("couldn't unmarshal %v: %v.\nThere appears to be invalid YAML in the `%s` field:\n\n%s", + path, yamlErr, jTypeErr.Field, badYAML) + } + // this isn't a json.UnmarshalTypeError, so just return the original error + return fmt.Errorf("couldn't unmarshal %v: %v", path, yamlErr) } for _, linter := range linters { if err := linter(&l); err != nil { diff --git a/internal/lagoonyml/lint_test.go b/internal/lagoonyml/lint_test.go index 2c79eb6..812d6c9 100644 --- a/internal/lagoonyml/lint_test.go +++ b/internal/lagoonyml/lint_test.go @@ -55,6 +55,10 @@ func TestLint(t *testing.T) { input: "testdata/invalid.3.lagoon.yml", valid: false, }, + "cronjob as environment": { + input: "testdata/invalid.4.lagoon.yml", + valid: false, + }, } for name, tc := range testCases { t.Run(name, func(tt *testing.T) { diff --git a/internal/lagoonyml/testdata/invalid.4.lagoon.yml b/internal/lagoonyml/testdata/invalid.4.lagoon.yml new file mode 100644 index 0000000..8a21007 --- /dev/null +++ b/internal/lagoonyml/testdata/invalid.4.lagoon.yml @@ -0,0 +1,23 @@ +environments: + cronjobs: + - name: a cronjob defined as environment + schedule: "* * * * *" + command: echo "broken definition" + service: cli + main: + routes: + - nginx: + - example.com + - "www.example.com": + tls-acme: 'true' + insecure: Redirect + hsts: max-age=31536000 + - "example.com": + annotations: + nginx.ingress.kubernetes.io/server-snippet: | + set_real_ip_from 1.2.3.4/32; + - "dev.example.com": + annotations: + nginx.ingress.kubernetes.io/server-snippet: | + set_real_ip_from 1.2.3.4/32; + add_header Content-type text/plain;