@@ -80,10 +80,12 @@ func New(workdir string, opts ...Option) (*Parser, tfconfig.Diagnostics) {
80
80
}
81
81
82
82
// WorkspaceTags looks for all coder_workspace_tags datasource in the module
83
- // and returns the raw values for the tags.
84
- func (p * Parser ) WorkspaceTags (ctx context.Context ) (map [string ]string , error ) {
83
+ // and returns the raw values for the tags. It also returns the set of
84
+ // variables referenced by any expressions in the raw values of tags.
85
+ func (p * Parser ) WorkspaceTags (ctx context.Context ) (map [string ]string , map [string ]struct {}, error ) {
85
86
tags := map [string ]string {}
86
- var skipped []string
87
+ skipped := []string {}
88
+ requiredVars := map [string ]struct {}{}
87
89
for _ , dataResource := range p .module .DataResources {
88
90
if dataResource .Type != "coder_workspace_tags" {
89
91
skipped = append (skipped , strings .Join ([]string {"data" , dataResource .Type , dataResource .Name }, "." ))
@@ -99,13 +101,13 @@ func (p *Parser) WorkspaceTags(ctx context.Context) (map[string]string, error) {
99
101
// We know in which HCL file is the data resource defined.
100
102
file , diags = p .underlying .ParseHCLFile (dataResource .Pos .Filename )
101
103
if diags .HasErrors () {
102
- return nil , xerrors .Errorf ("can't parse the resource file: %s" , diags .Error ())
104
+ return nil , nil , xerrors .Errorf ("can't parse the resource file: %s" , diags .Error ())
103
105
}
104
106
105
107
// Parse root to find "coder_workspace_tags".
106
108
content , _ , diags := file .Body .PartialContent (rootTemplateSchema )
107
109
if diags .HasErrors () {
108
- return nil , xerrors .Errorf ("can't parse the resource file: %s" , diags .Error ())
110
+ return nil , nil , xerrors .Errorf ("can't parse the resource file: %s" , diags .Error ())
109
111
}
110
112
111
113
// Iterate over blocks to locate the exact "coder_workspace_tags" data resource.
@@ -117,51 +119,99 @@ func (p *Parser) WorkspaceTags(ctx context.Context) (map[string]string, error) {
117
119
// Parse "coder_workspace_tags" to find all key-value tags.
118
120
resContent , _ , diags := block .Body .PartialContent (coderWorkspaceTagsSchema )
119
121
if diags .HasErrors () {
120
- return nil , xerrors .Errorf (`can't parse the resource coder_workspace_tags: %s` , diags .Error ())
122
+ return nil , nil , xerrors .Errorf (`can't parse the resource coder_workspace_tags: %s` , diags .Error ())
121
123
}
122
124
123
125
if resContent == nil {
124
126
continue // workspace tags are not present
125
127
}
126
128
127
129
if _ , ok := resContent .Attributes ["tags" ]; ! ok {
128
- return nil , xerrors .Errorf (`"tags" attribute is required by coder_workspace_tags` )
130
+ return nil , nil , xerrors .Errorf (`"tags" attribute is required by coder_workspace_tags` )
129
131
}
130
132
131
133
expr := resContent .Attributes ["tags" ].Expr
132
134
tagsExpr , ok := expr .(* hclsyntax.ObjectConsExpr )
133
135
if ! ok {
134
- return nil , xerrors .Errorf (`"tags" attribute is expected to be a key-value map` )
136
+ return nil , nil , xerrors .Errorf (`"tags" attribute is expected to be a key-value map` )
135
137
}
136
138
137
139
// Parse key-value entries in "coder_workspace_tags"
138
140
for _ , tagItem := range tagsExpr .Items {
139
141
key , err := previewFileContent (tagItem .KeyExpr .Range ())
140
142
if err != nil {
141
- return nil , xerrors .Errorf ("can't preview the resource file: %v" , err )
143
+ return nil , nil , xerrors .Errorf ("can't preview the resource file: %v" , err )
142
144
}
143
145
key = strings .Trim (key , `"` )
144
146
145
147
value , err := previewFileContent (tagItem .ValueExpr .Range ())
146
148
if err != nil {
147
- return nil , xerrors .Errorf ("can't preview the resource file: %v" , err )
149
+ return nil , nil , xerrors .Errorf ("can't preview the resource file: %v" , err )
148
150
}
149
151
150
152
if _ , ok := tags [key ]; ok {
151
- return nil , xerrors .Errorf (`workspace tag %q is defined multiple times` , key )
153
+ return nil , nil , xerrors .Errorf (`workspace tag %q is defined multiple times` , key )
152
154
}
153
155
tags [key ] = value
156
+
157
+ // Find values referenced by the expression.
158
+ refVars := referencedVariablesExpr (tagItem .ValueExpr )
159
+ for _ , refVar := range refVars {
160
+ requiredVars [refVar ] = struct {}{}
161
+ }
154
162
}
155
163
}
156
164
}
157
- p .logger .Debug (ctx , "found workspace tags" , slog .F ("tags" , maps .Keys (tags )), slog .F ("skipped" , skipped ))
158
- return tags , nil
165
+
166
+ requiredVarNames := maps .Keys (requiredVars )
167
+ slices .Sort (requiredVarNames )
168
+ p .logger .Debug (ctx , "found workspace tags" , slog .F ("tags" , maps .Keys (tags )), slog .F ("skipped" , skipped ), slog .F ("required_vars" , requiredVarNames ))
169
+ return tags , requiredVars , nil
170
+ }
171
+
172
+ // referencedVariablesExpr determines the variables referenced in expr
173
+ // and returns the names of those variables.
174
+ func referencedVariablesExpr (expr hclsyntax.Expression ) (names []string ) {
175
+ var parts []string
176
+ for _ , expVar := range expr .Variables () {
177
+ for _ , tr := range expVar {
178
+ switch v := tr .(type ) {
179
+ case hcl.TraverseRoot :
180
+ parts = append (parts , v .Name )
181
+ case hcl.TraverseAttr :
182
+ parts = append (parts , v .Name )
183
+ default : // skip
184
+ }
185
+ }
186
+
187
+ cleaned := cleanupTraversalName (parts )
188
+ names = append (names , strings .Join (cleaned , "." ))
189
+ }
190
+ return names
191
+ }
192
+
193
+ // cleanupTraversalName chops off extraneous pieces of the traversal.
194
+ // for example:
195
+ // - var.foo -> unchanged
196
+ // - data.coder_parameter.bar.value -> data.coder_parameter.bar
197
+ // - null_resource.baz.zap -> null_resource.baz
198
+ func cleanupTraversalName (parts []string ) []string {
199
+ if len (parts ) == 0 {
200
+ return parts
201
+ }
202
+ if len (parts ) > 3 && parts [0 ] == "data" {
203
+ return parts [:3 ]
204
+ }
205
+ if len (parts ) > 2 {
206
+ return parts [:2 ]
207
+ }
208
+ return parts
159
209
}
160
210
161
211
func (p * Parser ) WorkspaceTagDefaults (ctx context.Context ) (map [string ]string , error ) {
162
212
// This only gets us the expressions. We need to evaluate them.
163
213
// Example: var.region -> "us"
164
- tags , err := p .WorkspaceTags (ctx )
214
+ tags , requiredVars , err := p .WorkspaceTags (ctx )
165
215
if err != nil {
166
216
return nil , xerrors .Errorf ("extract workspace tags: %w" , err )
167
217
}
@@ -172,11 +222,11 @@ func (p *Parser) WorkspaceTagDefaults(ctx context.Context) (map[string]string, e
172
222
173
223
// To evaluate the expressions, we need to load the default values for
174
224
// variables and parameters.
175
- varsDefaults , err := p .VariableDefaults (ctx , tags )
225
+ varsDefaults , err := p .VariableDefaults (ctx )
176
226
if err != nil {
177
227
return nil , xerrors .Errorf ("load variable defaults: %w" , err )
178
228
}
179
- paramsDefaults , err := p .CoderParameterDefaults (ctx , varsDefaults , tags )
229
+ paramsDefaults , err := p .CoderParameterDefaults (ctx , varsDefaults , requiredVars )
180
230
if err != nil {
181
231
return nil , xerrors .Errorf ("load parameter defaults: %w" , err )
182
232
}
@@ -251,39 +301,28 @@ func WriteArchive(bs []byte, mimetype string, path string) error {
251
301
return nil
252
302
}
253
303
254
- // VariableDefaults returns the default values for all variables referenced in the values of tags.
255
- func (p * Parser ) VariableDefaults (ctx context.Context , tags map [string ]string ) (map [string ]string , error ) {
256
- var skipped []string
304
+ // VariableDefaults returns the default values for all variables in the module.
305
+ func (p * Parser ) VariableDefaults (ctx context.Context ) (map [string ]string , error ) {
257
306
// iterate through vars to get the default values for all
258
307
// required variables.
259
308
m := make (map [string ]string )
260
309
for _ , v := range p .module .Variables {
261
310
if v == nil {
262
311
continue
263
312
}
264
- var found bool
265
- for _ , tv := range tags {
266
- if strings .Contains (tv , v .Name ) {
267
- found = true
268
- }
269
- }
270
- if ! found {
271
- skipped = append (skipped , "var." + v .Name )
272
- continue
273
- }
274
313
sv , err := interfaceToString (v .Default )
275
314
if err != nil {
276
315
return nil , xerrors .Errorf ("can't convert variable default value to string: %v" , err )
277
316
}
278
317
m [v .Name ] = strings .Trim (sv , `"` )
279
318
}
280
- p .logger .Debug (ctx , "found default values for variables" , slog .F ("defaults" , m ), slog . F ( "skipped" , skipped ) )
319
+ p .logger .Debug (ctx , "found default values for variables" , slog .F ("defaults" , m ))
281
320
return m , nil
282
321
}
283
322
284
323
// CoderParameterDefaults returns the default values of all coder_parameter data sources
285
324
// in the parsed module.
286
- func (p * Parser ) CoderParameterDefaults (ctx context.Context , varsDefaults map [string ]string , tags map [string ]string ) (map [string ]string , error ) {
325
+ func (p * Parser ) CoderParameterDefaults (ctx context.Context , varsDefaults map [string ]string , names map [string ]struct {} ) (map [string ]string , error ) {
287
326
defaultsM := make (map [string ]string )
288
327
var (
289
328
skipped []string
@@ -296,23 +335,17 @@ func (p *Parser) CoderParameterDefaults(ctx context.Context, varsDefaults map[st
296
335
continue
297
336
}
298
337
299
- if dataResource .Type != "coder_parameter" {
300
- skipped = append (skipped , strings .Join ([]string {"data" , dataResource .Type , dataResource .Name }, "." ))
301
- continue
302
- }
303
-
304
338
if ! strings .HasSuffix (dataResource .Pos .Filename , ".tf" ) {
305
339
continue
306
340
}
307
341
308
- var found bool
309
342
needle := strings .Join ([]string {"data" , dataResource .Type , dataResource .Name }, "." )
310
- for _ , tv := range tags {
311
- if strings .Contains (tv , needle ) {
312
- found = true
313
- }
343
+ if dataResource .Type != "coder_parameter" {
344
+ skipped = append (skipped , needle )
345
+ continue
314
346
}
315
- if ! found {
347
+
348
+ if _ , found := names [needle ]; ! found {
316
349
skipped = append (skipped , needle )
317
350
continue
318
351
}
0 commit comments