Skip to content

Commit a5d6da1

Browse files
Merge branch 'main' of github.com:bschaatsbergen/terraform
2 parents 9c5fd55 + 671ea4e commit a5d6da1

File tree

27 files changed

+1481
-57
lines changed

27 files changed

+1481
-57
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: BUG FIXES
2+
body: 'lang/funcs/transpose: Avoid crash due to map with null values'
3+
time: 2025-03-03T12:57:22.400359Z
4+
custom:
5+
Issue: "36611"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: ENHANCEMENTS
2+
body: Produce detailed diagnostic objects when test run assertions fail
3+
time: 2025-02-20T12:04:38.005393+01:00
4+
custom:
5+
Issue: "34428"

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ require (
2121
github.com/go-test/deep v1.0.3
2222
github.com/google/go-cmp v0.6.0
2323
github.com/google/uuid v1.6.0
24-
github.com/hashicorp/cli v1.1.6
24+
github.com/hashicorp/cli v1.1.7
2525
github.com/hashicorp/go-checkpoint v0.5.0
2626
github.com/hashicorp/go-cleanhttp v0.5.2
2727
github.com/hashicorp/go-getter v1.7.8

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,8 +1059,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rH
10591059
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
10601060
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.58 h1:lf6PxLIHge0UL5LJgt/Szs0K3PYS27yqDEkaOa0P+ZU=
10611061
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.58/go.mod h1:9DB57cKw/ZNu1UQJX1YNmaJ7A2/+xCpCUUwbGZy4Qx0=
1062-
github.com/hashicorp/cli v1.1.6 h1:CMOV+/LJfL1tXCOKrgAX0uRKnzjj/mpmqNXloRSy2K8=
1063-
github.com/hashicorp/cli v1.1.6/go.mod h1:MPon5QYlgjjo0BSoAiN0ESeT5fRzDjVRp+uioJ0piz4=
1062+
github.com/hashicorp/cli v1.1.7 h1:/fZJ+hNdwfTSfsxMBa9WWMlfjUZbX8/LnUxgAd7lCVU=
1063+
github.com/hashicorp/cli v1.1.7/go.mod h1:e6Mfpga9OCT1vqzFuoGZiiF/KaG9CbUfO5s3ghU3YgU=
10641064
github.com/hashicorp/consul/api v1.13.0 h1:2hnLQ0GjQvw7f3O61jMO8gbasZviZTrt9R8WzgiirHc=
10651065
github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
10661066
github.com/hashicorp/consul/sdk v0.8.0 h1:OJtKBtEjboEZvG6AOUdh4Z1Zbyu0WcxQ0qatRrZHTVU=

internal/command/arguments/apply.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func ParseApply(args []string) (*Apply, tfdiags.Diagnostics) {
6161
diags = diags.Append(tfdiags.Sourceless(
6262
tfdiags.Warning,
6363
"Deprecated flag: -state",
64-
"Use `path` attribute within the `local` backend instead: https://developer.hashicorp.com/terraform/language/v1.10.x/settings/backends/local#path",
64+
`Use the "path" attribute within the "local" backend to specify a file for state storage`,
6565
))
6666
}
6767

internal/command/arguments/plan.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func ParsePlan(args []string) (*Plan, tfdiags.Diagnostics) {
6666
diags = diags.Append(tfdiags.Sourceless(
6767
tfdiags.Warning,
6868
"Deprecated flag: -state",
69-
"Use `path` attribute within the `local` backend instead: https://developer.hashicorp.com/terraform/language/v1.10.x/settings/backends/local#path",
69+
`Use the "path" attribute within the "local" backend to specify a file for state storage`,
7070
))
7171
}
7272

internal/command/arguments/refresh.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func ParseRefresh(args []string) (*Refresh, tfdiags.Diagnostics) {
5151
diags = diags.Append(tfdiags.Sourceless(
5252
tfdiags.Warning,
5353
"Deprecated flag: -state",
54-
"Use `path` attribute within the `local` backend instead: https://developer.hashicorp.com/terraform/language/v1.10.x/settings/backends/local#path",
54+
`Use the "path" attribute within the "local" backend to specify a file for state storage`,
5555
))
5656
}
5757

internal/command/format/diagnostic.go

Lines changed: 147 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"bufio"
88
"bytes"
99
"fmt"
10+
"iter"
1011
"sort"
1112
"strings"
1213

@@ -77,7 +78,8 @@ func DiagnosticFromJSON(diag *viewsjson.Diagnostic, color *colorstring.Colorize,
7778
// be pure text that lends itself well to word-wrapping.
7879
fmt.Fprintf(&buf, color.Color("[bold]%s[reset]\n\n"), diag.Summary)
7980

80-
appendSourceSnippets(&buf, diag, color)
81+
f := &snippetFormatter{&buf, diag, color}
82+
f.write()
8183

8284
if diag.Detail != "" {
8385
paraWidth := width - leftRuleWidth - 1 // leave room for the left rule
@@ -151,7 +153,8 @@ func DiagnosticPlainFromJSON(diag *viewsjson.Diagnostic, width int) string {
151153
// be pure text that lends itself well to word-wrapping.
152154
fmt.Fprintf(&buf, "%s\n\n", diag.Summary)
153155

154-
appendSourceSnippets(&buf, diag, disabledColorize)
156+
f := &snippetFormatter{&buf, diag, disabledColorize}
157+
f.write()
155158

156159
if diag.Detail != "" {
157160
if width > 1 {
@@ -215,7 +218,17 @@ func DiagnosticWarningsCompact(diags tfdiags.Diagnostics, color *colorstring.Col
215218
return b.String()
216219
}
217220

218-
func appendSourceSnippets(buf *bytes.Buffer, diag *viewsjson.Diagnostic, color *colorstring.Colorize) {
221+
// snippetFormatter handles formatting diagnostic information with source snippets
222+
type snippetFormatter struct {
223+
buf *bytes.Buffer
224+
diag *viewsjson.Diagnostic
225+
color *colorstring.Colorize
226+
}
227+
228+
func (f *snippetFormatter) write() {
229+
diag := f.diag
230+
buf := f.buf
231+
color := f.color
219232
if diag.Address != "" {
220233
fmt.Fprintf(buf, " with %s,\n", diag.Address)
221234
}
@@ -281,7 +294,7 @@ func appendSourceSnippets(buf *bytes.Buffer, diag *viewsjson.Diagnostic, color *
281294
)
282295
}
283296

284-
if len(snippet.Values) > 0 || (snippet.FunctionCall != nil && snippet.FunctionCall.Signature != nil) {
297+
if len(snippet.Values) > 0 || (snippet.FunctionCall != nil && snippet.FunctionCall.Signature != nil) || snippet.TestAssertionExpr != nil {
285298
// The diagnostic may also have information about the dynamic
286299
// values of relevant variables at the point of evaluation.
287300
// This is particularly useful for expressions that get evaluated
@@ -312,11 +325,139 @@ func appendSourceSnippets(buf *bytes.Buffer, diag *viewsjson.Diagnostic, color *
312325
}
313326
buf.WriteString(")\n")
314327
}
315-
for _, value := range values {
316-
fmt.Fprintf(buf, color.Color(" [dark_gray]│[reset] [bold]%s[reset] %s\n"), value.Traversal, value.Statement)
328+
329+
// always print the values unless in the case of a test assertion, where we only print them if the user has requested verbose output
330+
printValues := snippet.TestAssertionExpr == nil || snippet.TestAssertionExpr.ShowVerbose
331+
332+
// The diagnostic may also have information about failures from test assertions
333+
// in a `terraform test` run. This is useful for understanding the values that
334+
// were being compared when the assertion failed.
335+
// Also, we'll print a JSON diff of the two values to make it easier to see the
336+
// differences.
337+
if snippet.TestAssertionExpr != nil {
338+
f.printTestDiagOutput(snippet.TestAssertionExpr)
339+
}
340+
341+
if printValues {
342+
for _, value := range values {
343+
// if the statement is one line, we'll just print it as is
344+
// otherwise, we have to ensure that each line is indented correctly
345+
// and that the first line has the traversal information
346+
valSlice := strings.Split(value.Statement, "\n")
347+
fmt.Fprintf(buf, color.Color(" [dark_gray]│[reset] [bold]%s[reset] %s\n"),
348+
value.Traversal, valSlice[0])
349+
350+
for _, line := range valSlice[1:] {
351+
fmt.Fprintf(buf, color.Color(" [dark_gray]│[reset] %s\n"), line)
352+
}
353+
}
317354
}
318355
}
319356
}
320357

321358
buf.WriteByte('\n')
322359
}
360+
361+
func (f *snippetFormatter) printTestDiagOutput(diag *viewsjson.DiagnosticTestBinaryExpr) {
362+
buf := f.buf
363+
color := f.color
364+
// We only print the LHS and RHS if the user has requested verbose output
365+
// for the test assertion.
366+
if diag.ShowVerbose {
367+
fmt.Fprint(buf, color.Color(" [dark_gray]│[reset] [bold]LHS[reset]:\n"))
368+
for line := range strings.SplitSeq(diag.LHS, "\n") {
369+
fmt.Fprintf(buf, color.Color(" [dark_gray]│[reset] %s\n"), line)
370+
}
371+
fmt.Fprint(buf, color.Color(" [dark_gray]│[reset] [bold]RHS[reset]:\n"))
372+
for line := range strings.SplitSeq(diag.RHS, "\n") {
373+
fmt.Fprintf(buf, color.Color(" [dark_gray]│[reset] %s\n"), line)
374+
}
375+
}
376+
if diag.Warning != "" {
377+
fmt.Fprintf(buf, color.Color(" [dark_gray]│[reset] [bold]Warning[reset]: %s\n"), diag.Warning)
378+
}
379+
f.printJSONDiff(diag.LHS, diag.RHS)
380+
buf.WriteByte('\n')
381+
}
382+
383+
// printJSONDiff prints a colorized line-by-line diff of the JSON values of the LHS and RHS expressions
384+
// in a test assertion.
385+
// It visually distinguishes removed and added lines, helping users identify
386+
// discrepancies between an "actual" (lhsStr) and an "expected" (rhsStr) JSON output.
387+
func (f *snippetFormatter) printJSONDiff(lhsStr, rhsStr string) {
388+
389+
buf := f.buf
390+
color := f.color
391+
// No visible difference in the JSON, so we'll just return
392+
if lhsStr == rhsStr {
393+
return
394+
}
395+
fmt.Fprint(buf, color.Color(" [dark_gray]│[reset] [bold]Diff[reset]:\n"))
396+
fmt.Fprint(buf, color.Color(" [dark_gray]│[reset] [red][bold]--- actual[reset]\n"))
397+
fmt.Fprint(buf, color.Color(" [dark_gray]│[reset] [green][bold]+++ expected[reset]\n"))
398+
nextLhs, stopLhs := iter.Pull(strings.SplitSeq(lhsStr, "\n"))
399+
nextRhs, stopRhs := iter.Pull(strings.SplitSeq(rhsStr, "\n"))
400+
401+
printLine := func(prefix, line string) {
402+
var colour string
403+
switch prefix {
404+
case "-":
405+
colour = "[red]"
406+
case "+":
407+
colour = "[green]"
408+
default:
409+
}
410+
msg := fmt.Sprintf(" [dark_gray]│[reset] %s%s[reset] %s\n", colour, prefix, line)
411+
fmt.Fprint(buf, color.Color(msg))
412+
}
413+
414+
// Collect differing lines separately for each side
415+
removedLines := []string{}
416+
addedLines := []string{}
417+
418+
// Function to print collected diffs and reset buffers
419+
printDiffs := func() {
420+
for _, line := range removedLines {
421+
printLine("-", line)
422+
}
423+
for _, line := range addedLines {
424+
printLine("+", line)
425+
}
426+
removedLines = []string{}
427+
addedLines = []string{}
428+
}
429+
430+
// We'll iterate over both sides of the expression and collect the differences
431+
// along the way. When a match is found, we'll then print all the collected diffs
432+
// and the matching line, and then reset the buffers.
433+
for {
434+
lhsLine, lhsOk := nextLhs()
435+
rhsLine, rhsOk := nextRhs()
436+
437+
if !lhsOk && !rhsOk { // Both sides are done, so we'll print the diffs and break
438+
printDiffs()
439+
break
440+
}
441+
442+
// If one side is done, we'll just print the remaining lines from the other side
443+
if !lhsOk {
444+
addedLines = append(addedLines, rhsLine)
445+
continue
446+
}
447+
if !rhsOk {
448+
removedLines = append(removedLines, lhsLine)
449+
continue
450+
}
451+
452+
if lhsLine == rhsLine {
453+
printDiffs()
454+
printLine(" ", lhsLine)
455+
} else {
456+
removedLines = append(removedLines, lhsLine)
457+
addedLines = append(addedLines, rhsLine)
458+
}
459+
}
460+
461+
stopLhs()
462+
stopRhs()
463+
}

0 commit comments

Comments
 (0)