cli: Prevent overuse of console-only type function
The console-only `type` function allows interrogation of any value's type. An implementation quirk is that we use a cty.Mark to allow the console to display this type information without the usual HCL quoting. For example: > type("boop") string instead of: > type("boop") "string" Because these marks can propagate when used in complex expressions, using the type function as part of a complex expression could result in this "print as raw" mark being attached to a collection. When this happened, it would result in a crash when we tried to iterate over a marked value. The `type` function was never intended to be used in this way, which is why its use is limited to the console command. Its purpose was as a pseudo-builtin, used only at the top level to display the type of a given value. This commit goes some way to preventing the use of the `type` function in complex expressions, by refusing to display any non-string value which was marked by `type`, or contains a sub-value which was so marked.
This commit is contained in:
parent
c1dc94a3d2
commit
691b98b612
|
@ -17,10 +17,6 @@ func FormatValue(v cty.Value, indent int) string {
|
||||||
if !v.IsKnown() {
|
if !v.IsKnown() {
|
||||||
return "(known after apply)"
|
return "(known after apply)"
|
||||||
}
|
}
|
||||||
if v.Type().Equals(cty.String) && v.HasMark(marks.Raw) {
|
|
||||||
raw, _ := v.Unmark()
|
|
||||||
return raw.AsString()
|
|
||||||
}
|
|
||||||
if v.HasMark(marks.Sensitive) {
|
if v.HasMark(marks.Sensitive) {
|
||||||
return "(sensitive)"
|
return "(sensitive)"
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
"github.com/hashicorp/terraform/internal/lang"
|
"github.com/hashicorp/terraform/internal/lang"
|
||||||
|
"github.com/hashicorp/terraform/internal/lang/marks"
|
||||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,6 +55,29 @@ func (s *Session) handleEval(line string) (string, tfdiags.Diagnostics) {
|
||||||
return "", diags
|
return "", diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The raw mark is used only by the console-only `type` function, in order
|
||||||
|
// to allow display of a string value representation of the type without the
|
||||||
|
// usual HCL formatting. If we receive a string value with this mark, we do
|
||||||
|
// not want to format it any further.
|
||||||
|
//
|
||||||
|
// Due to mark propagation in cty, calling `type` as part of a larger
|
||||||
|
// expression can lead to other values being marked, which can in turn lead
|
||||||
|
// to unpredictable results. If any non-string value has the raw mark, we
|
||||||
|
// return a diagnostic explaining that this use of `type` is not permitted.
|
||||||
|
if marks.Contains(val, marks.Raw) {
|
||||||
|
if val.Type().Equals(cty.String) {
|
||||||
|
raw, _ := val.Unmark()
|
||||||
|
return raw.AsString(), diags
|
||||||
|
} else {
|
||||||
|
diags = diags.Append(tfdiags.Sourceless(
|
||||||
|
tfdiags.Error,
|
||||||
|
"Invalid use of type function",
|
||||||
|
"The console-only \"type\" function cannot be used as part of an expression.",
|
||||||
|
))
|
||||||
|
return "", diags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return FormatValue(val, 0), diags
|
return FormatValue(val, 0), diags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,20 @@ func TestSession_basicState(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("type function", func(t *testing.T) {
|
||||||
|
testSession(t, testSessionTest{
|
||||||
|
State: state,
|
||||||
|
Inputs: []testSessionInput{
|
||||||
|
{
|
||||||
|
Input: "type(test_instance.foo)",
|
||||||
|
Output: `object({
|
||||||
|
id: string,
|
||||||
|
})`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSession_stateless(t *testing.T) {
|
func TestSession_stateless(t *testing.T) {
|
||||||
|
@ -178,6 +192,18 @@ func TestSession_stateless(t *testing.T) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("type function cannot be used in expressions", func(t *testing.T) {
|
||||||
|
testSession(t, testSessionTest{
|
||||||
|
Inputs: []testSessionInput{
|
||||||
|
{
|
||||||
|
Input: `[for i in [1, "two", true]: type(i)]`,
|
||||||
|
Error: true,
|
||||||
|
ErrorContains: "Invalid use of type function",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSession(t *testing.T, test testSessionTest) {
|
func testSession(t *testing.T, test testSessionTest) {
|
||||||
|
@ -221,6 +247,9 @@ func testSession(t *testing.T, test testSessionTest) {
|
||||||
t.Fatalf("failed to create scope: %s", diags.Err())
|
t.Fatalf("failed to create scope: %s", diags.Err())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure that any console-only functions are available
|
||||||
|
scope.ConsoleMode = true
|
||||||
|
|
||||||
// Build the session
|
// Build the session
|
||||||
s := &Session{
|
s := &Session{
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
|
|
Loading…
Reference in New Issue