diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/configs/configschema/validate_traversal.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/configs/configschema/validate_traversal.go | 173 |
1 files changed, 173 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/configs/configschema/validate_traversal.go b/vendor/github.com/hashicorp/terraform/configs/configschema/validate_traversal.go new file mode 100644 index 0000000..a41e930 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/configs/configschema/validate_traversal.go | |||
@@ -0,0 +1,173 @@ | |||
1 | package configschema | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "sort" | ||
6 | |||
7 | "github.com/hashicorp/hcl2/hcl" | ||
8 | "github.com/hashicorp/hcl2/hcl/hclsyntax" | ||
9 | "github.com/zclconf/go-cty/cty" | ||
10 | |||
11 | "github.com/hashicorp/terraform/helper/didyoumean" | ||
12 | "github.com/hashicorp/terraform/tfdiags" | ||
13 | ) | ||
14 | |||
15 | // StaticValidateTraversal checks whether the given traversal (which must be | ||
16 | // relative) refers to a construct in the receiving schema, returning error | ||
17 | // diagnostics if any problems are found. | ||
18 | // | ||
19 | // This method is "optimistic" in that it will not return errors for possible | ||
20 | // problems that cannot be detected statically. It is possible that an | ||
21 | // traversal which passed static validation will still fail when evaluated. | ||
22 | func (b *Block) StaticValidateTraversal(traversal hcl.Traversal) tfdiags.Diagnostics { | ||
23 | if !traversal.IsRelative() { | ||
24 | panic("StaticValidateTraversal on absolute traversal") | ||
25 | } | ||
26 | if len(traversal) == 0 { | ||
27 | return nil | ||
28 | } | ||
29 | |||
30 | var diags tfdiags.Diagnostics | ||
31 | |||
32 | next := traversal[0] | ||
33 | after := traversal[1:] | ||
34 | |||
35 | var name string | ||
36 | switch step := next.(type) { | ||
37 | case hcl.TraverseAttr: | ||
38 | name = step.Name | ||
39 | case hcl.TraverseIndex: | ||
40 | // No other traversal step types are allowed directly at a block. | ||
41 | // If it looks like the user was trying to use index syntax to | ||
42 | // access an attribute then we'll produce a specialized message. | ||
43 | key := step.Key | ||
44 | if key.Type() == cty.String && key.IsKnown() && !key.IsNull() { | ||
45 | maybeName := key.AsString() | ||
46 | if hclsyntax.ValidIdentifier(maybeName) { | ||
47 | diags = diags.Append(&hcl.Diagnostic{ | ||
48 | Severity: hcl.DiagError, | ||
49 | Summary: `Invalid index operation`, | ||
50 | Detail: fmt.Sprintf(`Only attribute access is allowed here. Did you mean to access attribute %q using the dot operator?`, maybeName), | ||
51 | Subject: &step.SrcRange, | ||
52 | }) | ||
53 | return diags | ||
54 | } | ||
55 | } | ||
56 | // If it looks like some other kind of index then we'll use a generic error. | ||
57 | diags = diags.Append(&hcl.Diagnostic{ | ||
58 | Severity: hcl.DiagError, | ||
59 | Summary: `Invalid index operation`, | ||
60 | Detail: `Only attribute access is allowed here, using the dot operator.`, | ||
61 | Subject: &step.SrcRange, | ||
62 | }) | ||
63 | return diags | ||
64 | default: | ||
65 | // No other traversal types should appear in a normal valid traversal, | ||
66 | // but we'll handle this with a generic error anyway to be robust. | ||
67 | diags = diags.Append(&hcl.Diagnostic{ | ||
68 | Severity: hcl.DiagError, | ||
69 | Summary: `Invalid operation`, | ||
70 | Detail: `Only attribute access is allowed here, using the dot operator.`, | ||
71 | Subject: next.SourceRange().Ptr(), | ||
72 | }) | ||
73 | return diags | ||
74 | } | ||
75 | |||
76 | if attrS, exists := b.Attributes[name]; exists { | ||
77 | // For attribute validation we will just apply the rest of the | ||
78 | // traversal to an unknown value of the attribute type and pass | ||
79 | // through HCL's own errors, since we don't want to replicate all of | ||
80 | // HCL's type checking rules here. | ||
81 | val := cty.UnknownVal(attrS.Type) | ||
82 | _, hclDiags := after.TraverseRel(val) | ||
83 | diags = diags.Append(hclDiags) | ||
84 | return diags | ||
85 | } | ||
86 | |||
87 | if blockS, exists := b.BlockTypes[name]; exists { | ||
88 | moreDiags := blockS.staticValidateTraversal(name, after) | ||
89 | diags = diags.Append(moreDiags) | ||
90 | return diags | ||
91 | } | ||
92 | |||
93 | // If we get here then the name isn't valid at all. We'll collect up | ||
94 | // all of the names that _are_ valid to use as suggestions. | ||
95 | var suggestions []string | ||
96 | for name := range b.Attributes { | ||
97 | suggestions = append(suggestions, name) | ||
98 | } | ||
99 | for name := range b.BlockTypes { | ||
100 | suggestions = append(suggestions, name) | ||
101 | } | ||
102 | sort.Strings(suggestions) | ||
103 | suggestion := didyoumean.NameSuggestion(name, suggestions) | ||
104 | if suggestion != "" { | ||
105 | suggestion = fmt.Sprintf(" Did you mean %q?", suggestion) | ||
106 | } | ||
107 | diags = diags.Append(&hcl.Diagnostic{ | ||
108 | Severity: hcl.DiagError, | ||
109 | Summary: `Unsupported attribute`, | ||
110 | Detail: fmt.Sprintf(`This object has no argument, nested block, or exported attribute named %q.%s`, name, suggestion), | ||
111 | Subject: next.SourceRange().Ptr(), | ||
112 | }) | ||
113 | |||
114 | return diags | ||
115 | } | ||
116 | |||
117 | func (b *NestedBlock) staticValidateTraversal(typeName string, traversal hcl.Traversal) tfdiags.Diagnostics { | ||
118 | if b.Nesting == NestingSingle || b.Nesting == NestingGroup { | ||
119 | // Single blocks are easy: just pass right through. | ||
120 | return b.Block.StaticValidateTraversal(traversal) | ||
121 | } | ||
122 | |||
123 | if len(traversal) == 0 { | ||
124 | // It's always valid to access a nested block's attribute directly. | ||
125 | return nil | ||
126 | } | ||
127 | |||
128 | var diags tfdiags.Diagnostics | ||
129 | next := traversal[0] | ||
130 | after := traversal[1:] | ||
131 | |||
132 | switch b.Nesting { | ||
133 | |||
134 | case NestingSet: | ||
135 | // Can't traverse into a set at all, since it does not have any keys | ||
136 | // to index with. | ||
137 | diags = diags.Append(&hcl.Diagnostic{ | ||
138 | Severity: hcl.DiagError, | ||
139 | Summary: `Cannot index a set value`, | ||
140 | Detail: fmt.Sprintf(`Block type %q is represented by a set of objects, and set elements do not have addressable keys. To find elements matching specific criteria, use a "for" expression with an "if" clause.`, typeName), | ||
141 | Subject: next.SourceRange().Ptr(), | ||
142 | }) | ||
143 | return diags | ||
144 | |||
145 | case NestingList: | ||
146 | if _, ok := next.(hcl.TraverseIndex); ok { | ||
147 | moreDiags := b.Block.StaticValidateTraversal(after) | ||
148 | diags = diags.Append(moreDiags) | ||
149 | } else { | ||
150 | diags = diags.Append(&hcl.Diagnostic{ | ||
151 | Severity: hcl.DiagError, | ||
152 | Summary: `Invalid operation`, | ||
153 | Detail: fmt.Sprintf(`Block type %q is represented by a list of objects, so it must be indexed using a numeric key, like .%s[0].`, typeName, typeName), | ||
154 | Subject: next.SourceRange().Ptr(), | ||
155 | }) | ||
156 | } | ||
157 | return diags | ||
158 | |||
159 | case NestingMap: | ||
160 | // Both attribute and index steps are valid for maps, so we'll just | ||
161 | // pass through here and let normal evaluation catch an | ||
162 | // incorrectly-typed index key later, if present. | ||
163 | moreDiags := b.Block.StaticValidateTraversal(after) | ||
164 | diags = diags.Append(moreDiags) | ||
165 | return diags | ||
166 | |||
167 | default: | ||
168 | // Invalid nesting type is just ignored. It's checked by | ||
169 | // InternalValidate. (Note that we handled NestingSingle separately | ||
170 | // back at the start of this function.) | ||
171 | return nil | ||
172 | } | ||
173 | } | ||