Merge pull request #12316 from rifelpet/managed-files

Allow arbitrary length terraform literals
This commit is contained in:
Kubernetes Prow Robot 2021-09-13 06:58:07 -07:00 committed by GitHub
commit b4ba59993e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 273 additions and 95 deletions

2
go.mod
View File

@ -59,7 +59,7 @@ require (
github.com/google/uuid v1.2.0
github.com/gophercloud/gophercloud v0.18.0
github.com/gorilla/mux v1.8.0 // indirect
github.com/hashicorp/hcl/v2 v2.10.0
github.com/hashicorp/hcl/v2 v2.10.1
github.com/hashicorp/vault/api v1.1.0
github.com/jacksontj/memberlistmesh v0.0.0-20190905163944-93462b9d2bb7
github.com/jetstack/cert-manager v1.3.1

4
go.sum
View File

@ -584,8 +584,8 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.10.0 h1:1S1UnuhDGlv3gRFV4+0EdwB+znNP5HmcGbIqwnSCByg=
github.com/hashicorp/hcl/v2 v2.10.0/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
github.com/hashicorp/hcl/v2 v2.10.1 h1:h4Xx4fsrRE26ohAk/1iGF/JBqRQbyUqu5Lvj60U54ys=
github.com/hashicorp/hcl/v2 v2.10.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=

View File

@ -789,7 +789,7 @@ github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.10.0/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
github.com/hashicorp/hcl/v2 v2.10.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=

View File

@ -112,13 +112,15 @@ func writeLiteral(body *hclwrite.Body, key string, literal *terraformWriter.Lite
},
}
body.SetAttributeRaw(key, tokens)
} else if literal.ResourceType == "" || literal.ResourceName == "" || literal.ResourceProp == "" {
} else if len(literal.Tokens) == 0 {
body.SetAttributeValue(key, cty.StringVal(literal.Value))
} else {
traversal := hcl.Traversal{
hcl.TraverseRoot{Name: literal.ResourceType},
hcl.TraverseAttr{Name: literal.ResourceName},
hcl.TraverseAttr{Name: literal.ResourceProp},
hcl.TraverseRoot{Name: literal.Tokens[0]},
}
for i := 1; i < len(literal.Tokens); i++ {
token := literal.Tokens[i]
traversal = append(traversal, hcl.TraverseAttr{Name: token})
}
body.SetAttributeTraversal(key, traversal)
}
@ -134,20 +136,21 @@ func writeLiteralList(body *hclwrite.Body, key string, literals []*terraformWrit
{Type: hclsyntax.TokenOBrack, Bytes: []byte("["), SpacesBefore: 1},
}
for i, literal := range literals {
if literal.ResourceType == "" || literal.ResourceName == "" || literal.ResourceProp == "" {
if len(literal.Tokens) == 0 {
tokens = append(tokens, []*hclwrite.Token{
{Type: hclsyntax.TokenOQuote, Bytes: []byte{'"'}, SpacesBefore: 1},
{Type: hclsyntax.TokenQuotedLit, Bytes: []byte(literal.Value)},
{Type: hclsyntax.TokenCQuote, Bytes: []byte{'"'}, SpacesBefore: 1},
}...)
} else {
tokens = append(tokens, []*hclwrite.Token{
{Type: hclsyntax.TokenStringLit, Bytes: []byte(literal.ResourceType), SpacesBefore: 1},
{Type: hclsyntax.TokenDot, Bytes: []byte(".")},
{Type: hclsyntax.TokenStringLit, Bytes: []byte(literal.ResourceName)},
{Type: hclsyntax.TokenDot, Bytes: []byte(".")},
{Type: hclsyntax.TokenStringLit, Bytes: []byte(literal.ResourceProp)},
}...)
tokens = append(tokens, &hclwrite.Token{Type: hclsyntax.TokenStringLit, Bytes: []byte(literal.Tokens[0]), SpacesBefore: 1})
for i := 1; i < len(literal.Tokens); i++ {
token := literal.Tokens[i]
tokens = append(tokens, []*hclwrite.Token{
{Type: hclsyntax.TokenDot, Bytes: []byte(".")},
{Type: hclsyntax.TokenStringLit, Bytes: []byte(token)},
}...)
}
}
if i < len(literals)-1 {
tokens = append(tokens, &hclwrite.Token{Type: hclsyntax.TokenComma, Bytes: []byte(",")})

View File

@ -119,18 +119,20 @@ func TestWriteLiteral(t *testing.T) {
}{
{
name: "string",
literal: &terraformWriter.Literal{Value: "value"},
literal: terraformWriter.LiteralFromStringValue("value"),
expected: `foo = "value"`,
},
{
name: "traversal",
literal: &terraformWriter.Literal{
ResourceType: "type",
ResourceName: "name",
ResourceProp: "prop",
},
name: "traversal",
literal: terraformWriter.LiteralProperty("type", "name", "prop"),
expected: "foo = type.name.prop",
},
{
name: "provider alias",
literal: terraformWriter.LiteralTokens("aws", "files"),
expected: "foo = aws.files",
},
{
name: "file",
literal: terraformWriter.LiteralFunctionExpression("file", []string{"\"${path.module}/foo\""}),
@ -164,9 +166,7 @@ func TestWriteLiteralList(t *testing.T) {
name: "one literal",
literals: []*terraformWriter.Literal{
{
ResourceType: "type",
ResourceName: "name",
ResourceProp: "prop",
Tokens: []string{"type", "name", "prop"},
},
},
expected: "foo = [type.name.prop]",
@ -175,14 +175,10 @@ func TestWriteLiteralList(t *testing.T) {
name: "two literals",
literals: []*terraformWriter.Literal{
{
ResourceType: "type1",
ResourceName: "name1",
ResourceProp: "prop1",
Tokens: []string{"type1", "name1", "prop1"},
},
{
ResourceType: "type2",
ResourceName: "name2",
ResourceProp: "prop2",
Tokens: []string{"type2", "name2", "prop2"},
},
},
expected: "foo = [type1.name1.prop1, type2.name2.prop2]",
@ -191,9 +187,7 @@ func TestWriteLiteralList(t *testing.T) {
name: "one traversal literal, one string literal",
literals: []*terraformWriter.Literal{
{
ResourceType: "type",
ResourceName: "name",
ResourceProp: "prop",
Tokens: []string{"type", "name", "prop"},
},
{
Value: "foobar",

View File

@ -31,12 +31,9 @@ type Literal struct {
// "${}" interpolation is supported.
Value string `cty:"value"`
// ResourceType represents the type of a resource in a literal reference
ResourceType string `cty:"resource_type"`
// ResourceName represents the name of a resource in a literal reference
ResourceName string `cty:"resource_name"`
// ResourceProp represents the property of a resource in a literal reference
ResourceProp string `cty:"resource_prop"`
// Tokens are portions of a literal reference joined by periods.
// example: {"aws_vpc", "foo", "id"}
Tokens []string `cty:"tokens"`
// FnName represents the name of a terraform function.
FnName string `cty:"fn_name"`
@ -67,10 +64,16 @@ func LiteralProperty(resourceType, resourceName, prop string) *Literal {
tfName := sanitizeName(resourceName)
expr := "${" + resourceType + "." + tfName + "." + prop + "}"
return &Literal{
Value: expr,
ResourceType: resourceType,
ResourceName: tfName,
ResourceProp: prop,
Value: expr,
Tokens: []string{resourceType, tfName, prop},
}
}
func LiteralTokens(tokens ...string) *Literal {
expr := "${" + strings.Join(tokens, ".") + "}"
return &Literal{
Value: expr,
Tokens: tokens,
}
}

View File

@ -1,5 +1,12 @@
# HCL Changelog
## v2.10.1 (July 21, 2021)
* dynblock: Decode unknown dynamic blocks in order to obtain any diagnostics even though the decoded value is not used ([#476](https://github.com/hashicorp/hcl/pull/476))
* hclsyntax: Calling functions is now more robust in the face of an incorrectly-implemented function which returns a `function.ArgError` whose argument index is out of range for the length of the arguments. Previously this would often lead to a panic, but now it'll return a less-precice error message instead. Functions that return out-of-bounds argument indices still ought to be fixed so that the resulting error diagnostics can be as precise as possible. ([#472](https://github.com/hashicorp/hcl/pull/472))
* hclsyntax: Ensure marks on unknown values are maintained when processing string templates. ([#478](https://github.com/hashicorp/hcl/pull/478))
* hcl: Improved error messages for various common error situtions in `hcl.Index` and `hcl.GetAttr`. These are part of the implementation of indexing and attribute lookup in the native syntax expression language too, so the new error messages will apply to problems using those operators. ([#474](https://github.com/hashicorp/hcl/pull/474))
## v2.10.0 (April 20, 2021)
### Enhancements

View File

@ -451,22 +451,55 @@ func (e *FunctionCallExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti
param = varParam
}
// this can happen if an argument is (incorrectly) null.
if i > len(e.Args)-1 {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid function argument",
Detail: fmt.Sprintf(
"Invalid value for %q parameter: %s.",
param.Name, err,
),
Subject: args[len(params)].StartRange().Ptr(),
Context: e.Range().Ptr(),
Expression: e,
EvalContext: ctx,
})
if param == nil || i > len(args)-1 {
// Getting here means that the function we called has a bug:
// it returned an arg error that refers to an argument index
// that wasn't present in the call. For that situation
// we'll degrade to a less specific error just to give
// some sort of answer, but best to still fix the buggy
// function so that it only returns argument indices that
// are in range.
switch {
case param != nil:
// In this case we'll assume that the function was trying
// to talk about a final variadic parameter but the caller
// didn't actually provide any arguments for it. That means
// we can at least still name the parameter in the
// error message, but our source range will be the call
// as a whole because we don't have an argument expression
// to highlight specifically.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid function argument",
Detail: fmt.Sprintf(
"Invalid value for %q parameter: %s.",
param.Name, err,
),
Subject: e.Range().Ptr(),
Expression: e,
EvalContext: ctx,
})
default:
// This is the most degenerate case of all, where the
// index is out of range even for the declared parameters,
// and so we can't tell which parameter the function is
// trying to report an error for. Just a generic error
// report in that case.
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Error in function call",
Detail: fmt.Sprintf(
"Call to function %q failed: %s.",
e.Name, err,
),
Subject: e.StartRange().Ptr(),
Context: e.Range().Ptr(),
Expression: e,
EvalContext: ctx,
})
}
} else {
argExpr := e.Args[i]
argExpr := args[i]
// TODO: we should also unpick a PathError here and show the
// path to the deep value where the error was detected.

View File

@ -48,6 +48,12 @@ func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
continue
}
// Unmark the part and merge its marks into the set
unmarkedVal, partMarks := partVal.Unmark()
for k, v := range partMarks {
marks[k] = v
}
if !partVal.IsKnown() {
// If any part is unknown then the result as a whole must be
// unknown too. We'll keep on processing the rest of the parts
@ -57,7 +63,7 @@ func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
continue
}
strVal, err := convert.Convert(partVal, cty.String)
strVal, err := convert.Convert(unmarkedVal, cty.String)
if err != nil {
diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError,
@ -74,13 +80,7 @@ func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
continue
}
// Unmark the part and merge its marks into the set
unmarked, partMarks := strVal.Unmark()
for k, v := range partMarks {
marks[k] = v
}
buf.WriteString(unmarked.AsString())
buf.WriteString(strVal.AsString())
}
var ret cty.Value

View File

@ -1661,7 +1661,7 @@ func (p *parser) parseQuotedStringLiteral() (string, hcl.Range, hcl.Diagnostics)
var diags hcl.Diagnostics
ret := &bytes.Buffer{}
var cQuote Token
var endRange hcl.Range
Token:
for {
@ -1669,7 +1669,7 @@ Token:
switch tok.Type {
case TokenCQuote:
cQuote = tok
endRange = tok.Range
break Token
case TokenQuotedLit:
@ -1712,6 +1712,7 @@ Token:
Subject: &tok.Range,
Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(),
})
endRange = tok.Range
break Token
default:
@ -1724,13 +1725,14 @@ Token:
Context: hcl.RangeBetween(oQuote.Range, tok.Range).Ptr(),
})
p.recover(TokenCQuote)
endRange = tok.Range
break Token
}
}
return ret.String(), hcl.RangeBetween(oQuote.Range, cQuote.Range), diags
return ret.String(), hcl.RangeBetween(oQuote.Range, endRange), diags
}
// ParseStringLiteralToken processes the given token, which must be either a

View File

@ -293,18 +293,20 @@ Between the open and closing delimiters of these sequences, newline sequences
are ignored as whitespace.
There is a syntax ambiguity between _for expressions_ and collection values
whose first element is a reference to a variable named `for`. The
_for expression_ interpretation has priority, so to produce a tuple whose
first element is the value of a variable named `for`, or an object with a
key named `for`, use parentheses to disambiguate:
whose first element starts with an identifier named `for`. The _for expression_
interpretation has priority, so to write a key literally named `for`
or an expression derived from a variable named `for` you must use parentheses
or quotes to disambiguate:
- `[for, foo, baz]` is a syntax error.
- `[(for), foo, baz]` is a tuple whose first element is the value of variable
`for`.
- `{for: 1, baz: 2}` is a syntax error.
- `{(for): 1, baz: 2}` is an object with an attribute literally named `for`.
- `{baz: 2, for: 1}` is equivalent to the previous example, and resolves the
- `{for = 1, baz = 2}` is a syntax error.
- `{"for" = 1, baz = 2}` is an object with an attribute literally named `for`.
- `{baz = 2, for = 1}` is equivalent to the previous example, and resolves the
ambiguity by reordering.
- `{(for) = 1, baz = 2}` is an object with a key with the same value as the
variable `for`.
### Template Expressions

View File

@ -21,6 +21,8 @@ import (
// though nil can be provided if the calling application is going to
// ignore the subject of the returned diagnostics anyway.
func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics) {
const invalidIndex = "Invalid index"
if collection.IsNull() {
return cty.DynamicVal, Diagnostics{
{
@ -35,7 +37,7 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Summary: invalidIndex,
Detail: "Can't use a null value as an indexing key.",
Subject: srcRange,
},
@ -66,7 +68,7 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Summary: invalidIndex,
Detail: fmt.Sprintf(
"The given key does not identify an element in this collection value: %s.",
keyErr.Error(),
@ -88,32 +90,84 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
}
}
if has.False() {
// We have a more specialized error message for the situation of
// using a fractional number to index into a sequence, because
// that will tend to happen if the user is trying to use division
// to calculate an index and not realizing that HCL does float
// division rather than integer division.
if (ty.IsListType() || ty.IsTupleType()) && key.Type().Equals(cty.Number) {
if key.IsKnown() && !key.IsNull() {
// NOTE: we don't know what any marks might've represented
// up at the calling application layer, so we must avoid
// showing the literal number value in these error messages
// in case the mark represents something important, such as
// a value being "sensitive".
key, _ := key.Unmark()
bf := key.AsBigFloat()
if _, acc := bf.Int(nil); acc != big.Exact {
// We have a more specialized error message for the
// situation of using a fractional number to index into
// a sequence, because that will tend to happen if the
// user is trying to use division to calculate an index
// and not realizing that HCL does float division
// rather than integer division.
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Detail: fmt.Sprintf("The given key does not identify an element in this collection value: indexing a sequence requires a whole number, but the given index (%g) has a fractional part.", bf),
Summary: invalidIndex,
Detail: "The given key does not identify an element in this collection value: indexing a sequence requires a whole number, but the given index has a fractional part.",
Subject: srcRange,
},
}
}
if bf.Sign() < 0 {
// Some other languages allow negative indices to
// select "backwards" from the end of the sequence,
// but HCL doesn't do that in order to give better
// feedback if a dynamic index is calculated
// incorrectly.
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: invalidIndex,
Detail: "The given key does not identify an element in this collection value: a negative number is not a valid index for a sequence.",
Subject: srcRange,
},
}
}
if lenVal := collection.Length(); lenVal.IsKnown() && !lenVal.IsMarked() {
// Length always returns a number, and we already
// checked that it's a known number, so this is safe.
lenBF := lenVal.AsBigFloat()
var result big.Float
result.Sub(bf, lenBF)
if result.Sign() < 1 {
if lenBF.Sign() == 0 {
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: invalidIndex,
Detail: "The given key does not identify an element in this collection value: the collection has no elements.",
Subject: srcRange,
},
}
} else {
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: invalidIndex,
Detail: "The given key does not identify an element in this collection value: the given index is greater than or equal to the length of the collection.",
Subject: srcRange,
},
}
}
}
}
}
}
// If this is not one of the special situations we handled above
// then we'll fall back on a very generic message.
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Summary: invalidIndex,
Detail: "The given key does not identify an element in this collection value.",
Subject: srcRange,
},
@ -123,12 +177,13 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
return collection.Index(key), nil
case ty.IsObjectType():
wasNumber := key.Type() == cty.Number
key, keyErr := convert.Convert(key, cty.String)
if keyErr != nil {
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Summary: invalidIndex,
Detail: fmt.Sprintf(
"The given key does not identify an element in this collection value: %s.",
keyErr.Error(),
@ -148,11 +203,20 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
attrName := key.AsString()
if !ty.HasAttribute(attrName) {
var suggestion string
if wasNumber {
// We note this only as an addendum to an error we would've
// already returned anyway, because it is valid (albeit weird)
// to have an attribute whose name is just decimal digits
// and then access that attribute using a number whose
// decimal representation is the same digits.
suggestion = " An object only supports looking up attributes by name, not by numeric index."
}
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Detail: "The given key does not identify an element in this collection value.",
Summary: invalidIndex,
Detail: fmt.Sprintf("The given key does not identify an element in this collection value.%s", suggestion),
Subject: srcRange,
},
}
@ -160,11 +224,21 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics)
return collection.GetAttr(attrName), nil
case ty.IsSetType():
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: invalidIndex,
Detail: "Elements of a set are identified only by their value and don't have any separate index or key to select with, so it's only possible to perform operations across all elements of the set.",
Subject: srcRange,
},
}
default:
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Invalid index",
Summary: invalidIndex,
Detail: "This value does not have any indices.",
Subject: srcRange,
},
@ -197,6 +271,8 @@ func GetAttr(obj cty.Value, attrName string, srcRange *Range) (cty.Value, Diagno
}
}
const unsupportedAttr = "Unsupported attribute"
ty := obj.Type()
switch {
case ty.IsObjectType():
@ -204,7 +280,7 @@ func GetAttr(obj cty.Value, attrName string, srcRange *Range) (cty.Value, Diagno
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Unsupported attribute",
Summary: unsupportedAttr,
Detail: fmt.Sprintf("This object does not have an attribute named %q.", attrName),
Subject: srcRange,
},
@ -241,11 +317,69 @@ func GetAttr(obj cty.Value, attrName string, srcRange *Range) (cty.Value, Diagno
return obj.Index(idx), nil
case ty == cty.DynamicPseudoType:
return cty.DynamicVal, nil
case ty.IsListType() && ty.ElementType().IsObjectType():
// It seems a common mistake to try to access attributes on a whole
// list of objects rather than on a specific individual element, so
// we have some extra hints for that case.
switch {
case ty.ElementType().HasAttribute(attrName):
// This is a very strong indication that the user mistook the list
// of objects for a single object, so we can be a little more
// direct in our suggestion here.
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: unsupportedAttr,
Detail: fmt.Sprintf("Can't access attributes on a list of objects. Did you mean to access attribute %q for a specific element of the list, or across all elements of the list?", attrName),
Subject: srcRange,
},
}
default:
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: unsupportedAttr,
Detail: "Can't access attributes on a list of objects. Did you mean to access an attribute for a specific element of the list, or across all elements of the list?",
Subject: srcRange,
},
}
}
case ty.IsSetType() && ty.ElementType().IsObjectType():
// This is similar to the previous case, but we can't give such a
// direct suggestion because there is no mechanism to select a single
// item from a set.
// We could potentially suggest using a for expression or splat
// operator here, but we typically don't get into syntax specifics
// in hcl.GetAttr suggestions because it's a general function used in
// various other situations, such as in application-specific operations
// that might have a more constraint set of alternative approaches.
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: unsupportedAttr,
Detail: "Can't access attributes on a set of objects. Did you mean to access an attribute across all elements of the set?",
Subject: srcRange,
},
}
case ty.IsPrimitiveType():
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: unsupportedAttr,
Detail: fmt.Sprintf("Can't access attributes on a primitive-typed value (%s).", ty.FriendlyName()),
Subject: srcRange,
},
}
default:
return cty.DynamicVal, Diagnostics{
{
Severity: DiagError,
Summary: "Unsupported attribute",
Summary: unsupportedAttr,
Detail: "This value does not have any attributes.",
Subject: srcRange,
},

2
vendor/modules.txt generated vendored
View File

@ -463,7 +463,7 @@ github.com/hashicorp/hcl/hcl/token
github.com/hashicorp/hcl/json/parser
github.com/hashicorp/hcl/json/scanner
github.com/hashicorp/hcl/json/token
# github.com/hashicorp/hcl/v2 v2.10.0
# github.com/hashicorp/hcl/v2 v2.10.1
## explicit
github.com/hashicorp/hcl/v2
github.com/hashicorp/hcl/v2/ext/customdecode