Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitde83723

Browse files
authored
feat: show Terraform error details (#6643)
1 parenta4d86e9 commitde83723

File tree

3 files changed

+137
-10
lines changed

3 files changed

+137
-10
lines changed

‎provisioner/terraform/diagnostic.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package terraform
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"sort"
7+
"strings"
8+
9+
tfjson"github.com/hashicorp/terraform-json"
10+
)
11+
12+
// This implementation bases on the original Terraform formatter, which unfortunately is internal:
13+
// https://github.com/hashicorp/terraform/blob/6b35927cf0988262739a5f0acea4790ae58a16d3/internal/command/format/diagnostic.go#L125
14+
15+
funcFormatDiagnostic(diag*tfjson.Diagnostic)string {
16+
varbuf bytes.Buffer
17+
appendSourceSnippets(&buf,diag)
18+
_,_=buf.WriteString(diag.Detail)
19+
returnbuf.String()
20+
}
21+
22+
funcappendSourceSnippets(buf*bytes.Buffer,diag*tfjson.Diagnostic) {
23+
ifdiag.Range==nil {
24+
return
25+
}
26+
27+
ifdiag.Snippet==nil {
28+
// This should generally not happen, as long as sources are always
29+
// loaded through the main loader. We may load things in other
30+
// ways in weird cases, so we'll tolerate it at the expense of
31+
// a not-so-helpful error message.
32+
_,_=fmt.Fprintf(buf,"on %s line %d:\n (source code not available)\n",diag.Range.Filename,diag.Range.Start.Line)
33+
}else {
34+
snippet:=diag.Snippet
35+
code:=snippet.Code
36+
37+
varcontextStrstring
38+
ifsnippet.Context!=nil {
39+
contextStr=fmt.Sprintf(", in %s",*snippet.Context)
40+
}
41+
_,_=fmt.Fprintf(buf,"on %s line %d%s:\n",diag.Range.Filename,diag.Range.Start.Line,contextStr)
42+
43+
// Split the snippet into lines and render one at a time
44+
lines:=strings.Split(code,"\n")
45+
fori,line:=rangelines {
46+
_,_=fmt.Fprintf(buf," %d: %s\n",snippet.StartLine+i,line)
47+
}
48+
49+
iflen(snippet.Values)>0 {
50+
// The diagnostic may also have information about the dynamic
51+
// values of relevant variables at the point of evaluation.
52+
// This is particularly useful for expressions that get evaluated
53+
// multiple times with different values, such as blocks using
54+
// "count" and "for_each", or within "for" expressions.
55+
values:=make([]tfjson.DiagnosticExpressionValue,len(snippet.Values))
56+
copy(values,snippet.Values)
57+
sort.Slice(values,func(i,jint)bool {
58+
returnvalues[i].Traversal<values[j].Traversal
59+
})
60+
61+
_,_=buf.WriteString(" ├────────────────\n")
62+
for_,value:=rangevalues {
63+
_,_=fmt.Fprintf(buf," │ %s %s\n",value.Traversal,value.Statement)
64+
}
65+
}
66+
}
67+
_=buf.WriteByte('\n')
68+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package terraform_test
2+
3+
import (
4+
"encoding/json"
5+
"strings"
6+
"testing"
7+
8+
tfjson"github.com/hashicorp/terraform-json"
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/coder/coder/provisioner/terraform"
12+
)
13+
14+
typehasDiagnosticstruct {
15+
Diagnostic*tfjson.Diagnostic`json:"diagnostic"`
16+
}
17+
18+
funcTestFormatDiagnostic(t*testing.T) {
19+
t.Parallel()
20+
21+
tests:=map[string]struct {
22+
inputstring
23+
expected []string
24+
}{
25+
"Expression": {
26+
input:`{"@level":"error","@message":"Error: Unsupported attribute","@module":"terraform.ui","@timestamp":"2023-03-17T10:33:38.761493+01:00","diagnostic":{"severity":"error","summary":"Unsupported attribute","detail":"This object has no argument, nested block, or exported attribute named \"foobar\".","range":{"filename":"main.tf","start":{"line":230,"column":81,"byte":5648},"end":{"line":230,"column":88,"byte":5655}},"snippet":{"context":"resource \"docker_container\" \"workspace\"","code":" name = \"coder-${data.coder_workspace.me.owner}-${lower(data.coder_workspace.me.foobar)}\"","start_line":230,"highlight_start_offset":80,"highlight_end_offset":87,"values":[]}},"type":"diagnostic"}`,
27+
expected: []string{
28+
"on main.tf line 230, in resource\"docker_container\"\"workspace\":",
29+
" 230: name =\"coder-${data.coder_workspace.me.owner}-${lower(data.coder_workspace.me.foobar)}\"",
30+
"",
31+
"This object has no argument, nested block, or exported attribute named\"foobar\".",
32+
},
33+
},
34+
"DynamicValues": {
35+
input:`{"@level":"error","@message":"Error: Invalid value for variable","@module":"terraform.ui","@timestamp":"2023-03-17T12:25:37.864793+01:00","diagnostic":{"severity":"error","summary":"Invalid value for variable","detail":"Invalid Digital Ocean Project ID.\n\nThis was checked by the validation rule at main.tf:27,3-13.","range":{"filename":"main.tf","start":{"line":18,"column":1,"byte":277},"end":{"line":18,"column":31,"byte":307}},"snippet":{"context":null,"code":"variable \"step1_do_project_id\" {","start_line":18,"highlight_start_offset":0,"highlight_end_offset":30,"values":[{"traversal":"var.step1_do_project_id","statement":"is \"magic-project-id\""}]}},"type":"diagnostic"}`,
36+
expected: []string{
37+
"on main.tf line 18:",
38+
" 18: variable\"step1_do_project_id\" {",
39+
" ├────────────────",
40+
" │ var.step1_do_project_id is\"magic-project-id\"",
41+
"",
42+
"Invalid Digital Ocean Project ID.",
43+
"",
44+
"This was checked by the validation rule at main.tf:27,3-13.",
45+
},
46+
},
47+
}
48+
49+
forname,tc:=rangetests {
50+
tc:=tc
51+
52+
t.Run(name,func(t*testing.T) {
53+
t.Parallel()
54+
55+
vardhasDiagnostic
56+
err:=json.Unmarshal([]byte(tc.input),&d)
57+
require.NoError(t,err)
58+
59+
output:=terraform.FormatDiagnostic(d.Diagnostic)
60+
require.Equal(t,tc.expected,strings.Split(output,"\n"))
61+
})
62+
}
63+
}

‎provisioner/terraform/executor.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -496,8 +496,10 @@ func provisionReadAndLog(sink logSink, r io.Reader, done chan<- any) {
496496
iflog.Diagnostic==nil {
497497
continue
498498
}
499-
logLevel=convertTerraformLogLevel(log.Diagnostic.Severity,sink)
500-
sink.Log(&proto.Log{Level:logLevel,Output:log.Diagnostic.Detail})
499+
logLevel=convertTerraformLogLevel(string(log.Diagnostic.Severity),sink)
500+
for_,diagLine:=rangestrings.Split(FormatDiagnostic(log.Diagnostic),"\n") {
501+
sink.Log(&proto.Log{Level:logLevel,Output:diagLine})
502+
}
501503
}
502504
}
503505

@@ -509,7 +511,7 @@ func convertTerraformLogLevel(logLevel string, sink logSink) proto.LogLevel {
509511
returnproto.LogLevel_DEBUG
510512
case"info":
511513
returnproto.LogLevel_INFO
512-
case"warn":
514+
case"warn","warning":
513515
returnproto.LogLevel_WARN
514516
case"error":
515517
returnproto.LogLevel_ERROR
@@ -526,13 +528,7 @@ type terraformProvisionLog struct {
526528
Levelstring`json:"@level"`
527529
Messagestring`json:"@message"`
528530

529-
Diagnostic*terraformProvisionLogDiagnostic`json:"diagnostic"`
530-
}
531-
532-
typeterraformProvisionLogDiagnosticstruct {
533-
Severitystring`json:"severity"`
534-
Summarystring`json:"summary"`
535-
Detailstring`json:"detail"`
531+
Diagnostic*tfjson.Diagnostic`json:"diagnostic,omitempty"`
536532
}
537533

538534
// syncWriter wraps an io.Writer in a sync.Mutex.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp