@@ -21,6 +21,7 @@ import (
2121"github.com/hashicorp/hcl/v2/hclsyntax"
2222"github.com/hashicorp/terraform-config-inspect/tfconfig"
2323"github.com/zclconf/go-cty/cty"
24+ "golang.org/x/exp/maps"
2425"golang.org/x/xerrors"
2526
2627"cdr.dev/slog"
@@ -30,11 +31,25 @@ import (
3031// introducing a circular dependency
3132const maxFileSizeBytes = 10 * (10 << 20 )// 10 MB
3233
34+ // hclparse.Parser by default keeps track of all files it has ever parsed
35+ // by filename. By re-using the same parser instance we can avoid repeating
36+ // previous work.
37+ // NOTE: we can only do this if we _just_ use ParseHCLFile().
38+ // The ParseHCL() method allows specifying the filename directly, which allows
39+ // easily getting previously cached results. Whenever we parse HCL files from disk
40+ // we do so in a unique file path.
41+ // See: provisionersdk/session.go#L51
42+ var hclFileParser ParseHCLFiler = hclparse .NewParser ()
43+
44+ type ParseHCLFiler interface {
45+ ParseHCLFile (filename string ) (* hcl.File , hcl.Diagnostics )
46+ }
47+
3348// WorkspaceTags extracts tags from coder_workspace_tags data sources defined in module.
3449// Note that this only returns the lexical values of the data source, and does not
3550// evaluate variables and such. To do this, see evalProvisionerTags below.
36- func WorkspaceTags (ctx context. Context , module * tfconfig.Module ) (map [string ]string ,error ) {
37- workspaceTags := map [string ]string {}
51+ func WorkspaceTags (module * tfconfig.Module ) (map [string ]string ,error ) {
52+ tags := map [string ]string {}
3853
3954for _ ,dataResource := range module .DataResources {
4055if dataResource .Type != "coder_workspace_tags" {
@@ -43,13 +58,12 @@ func WorkspaceTags(ctx context.Context, module *tfconfig.Module) (map[string]str
4358
4459var file * hcl.File
4560var diags hcl.Diagnostics
46- parser := hclparse .NewParser ()
4761
4862if ! strings .HasSuffix (dataResource .Pos .Filename ,".tf" ) {
4963continue
5064}
5165// We know in which HCL file is the data resource defined.
52- file ,diags = parser .ParseHCLFile (dataResource .Pos .Filename )
66+ file ,diags = hclFileParser .ParseHCLFile (dataResource .Pos .Filename )
5367if diags .HasErrors () {
5468return nil ,xerrors .Errorf ("can't parse the resource file: %s" ,diags .Error ())
5569}
@@ -99,14 +113,14 @@ func WorkspaceTags(ctx context.Context, module *tfconfig.Module) (map[string]str
99113return nil ,xerrors .Errorf ("can't preview the resource file: %v" ,err )
100114}
101115
102- if _ ,ok := workspaceTags [key ];ok {
116+ if _ ,ok := tags [key ];ok {
103117return nil ,xerrors .Errorf (`workspace tag %q is defined multiple times` ,key )
104118}
105- workspaceTags [key ]= value
119+ tags [key ]= value
106120}
107121}
108122}
109- return workspaceTags ,nil
123+ return tags ,nil
110124}
111125
112126//WorkspaceTagDefaultsFromFile extracts the default values for a `coder_workspace_tags` resource from the given
@@ -131,16 +145,20 @@ func WorkspaceTagDefaultsFromFile(ctx context.Context, logger slog.Logger, file
131145
132146// This only gets us the expressions. We need to evaluate them.
133147// Example: var.region -> "us"
134- tags ,err = WorkspaceTags (ctx , module )
148+ tags ,err = WorkspaceTags (module )
135149if err != nil {
136150return nil ,xerrors .Errorf ("extract workspace tags: %w" ,err )
137151}
138152
139153// To evaluate the expressions, we need to load the default values for
140154// variables and parameters.
141- varsDefaults ,paramsDefaults ,err := loadDefaults (module )
155+ varsDefaults ,err := loadVarsDefaults (maps .Values (module .Variables ))
156+ if err != nil {
157+ return nil ,xerrors .Errorf ("load variable defaults: %w" ,err )
158+ }
159+ paramsDefaults ,err := loadParamsDefaults (maps .Values (module .DataResources ))
142160if err != nil {
143- return nil ,xerrors .Errorf ("load defaults: %w" ,err )
161+ return nil ,xerrors .Errorf ("loadparameter defaults: %w" ,err )
144162}
145163
146164// Evaluate the tags expressions given the inputs.
@@ -205,46 +223,53 @@ func loadModuleFromFile(file []byte, mimetype string) (module *tfconfig.Module,
205223return module ,cleanup ,nil
206224}
207225
208- // loadDefaults inspects the given module and returns the default values for
209- // all variables and coder_parameter data sources referenced there.
210- func loadDefaults (module * tfconfig.Module ) (varsDefaults map [string ]string ,paramsDefaults map [string ]string ,err error ) {
211- // iterate through module.Variables to get the default values for all
226+ // loadVarsDefaults returns the default values for all variables passed to it.
227+ func loadVarsDefaults (variables []* tfconfig.Variable ) (map [string ]string ,error ) {
228+ // iterate through vars to get the default values for all
212229// variables.
213- varsDefaults = make (map [string ]string )
214- for _ ,v := range module .Variables {
230+ m := make (map [string ]string )
231+ for _ ,v := range variables {
232+ if v == nil {
233+ continue
234+ }
215235sv ,err := interfaceToString (v .Default )
216236if err != nil {
217- return nil ,nil , xerrors .Errorf ("can't convert variable default value to string: %v" ,err )
237+ return nil ,xerrors .Errorf ("can't convert variable default value to string: %v" ,err )
218238}
219- varsDefaults [v .Name ]= strings .Trim (sv ,`"` )
239+ m [v .Name ]= strings .Trim (sv ,`"` )
220240}
241+ return m ,nil
242+ }
243+
244+ // loadParamsDefaults returns the default values of all coder_parameter data sources data sources provided.
245+ func loadParamsDefaults (dataSources []* tfconfig.Resource ) (map [string ]string ,error ) {
246+ defaultsM := make (map [string ]string )
247+ for _ ,dataResource := range dataSources {
248+ if dataResource == nil {
249+ continue
250+ }
221251
222- // iterate through module.DataResources to get the default values for all
223- // coder_parameter data resources.
224- paramsDefaults = make (map [string ]string )
225- for _ ,dataResource := range module .DataResources {
226252if dataResource .Type != "coder_parameter" {
227253continue
228254}
229255
230256var file * hcl.File
231257var diags hcl.Diagnostics
232- parser := hclparse .NewParser ()
233258
234259if ! strings .HasSuffix (dataResource .Pos .Filename ,".tf" ) {
235260continue
236261}
237262
238263// We know in which HCL file is the data resource defined.
239- file ,diags = parser .ParseHCLFile (dataResource .Pos .Filename )
264+ file ,diags = hclFileParser .ParseHCLFile (dataResource .Pos .Filename )
240265if diags .HasErrors () {
241- return nil ,nil , xerrors .Errorf ("can't parse the resource file %q: %s" ,dataResource .Pos .Filename ,diags .Error ())
266+ return nil ,xerrors .Errorf ("can't parse the resource file %q: %s" ,dataResource .Pos .Filename ,diags .Error ())
242267}
243268
244269// Parse root to find "coder_parameter".
245270content ,_ ,diags := file .Body .PartialContent (rootTemplateSchema )
246271if diags .HasErrors () {
247- return nil ,nil , xerrors .Errorf ("can't parse the resource file: %s" ,diags .Error ())
272+ return nil ,xerrors .Errorf ("can't parse the resource file: %s" ,diags .Error ())
248273}
249274
250275// Iterate over blocks to locate the exact "coder_parameter" data resource.
@@ -256,22 +281,22 @@ func loadDefaults(module *tfconfig.Module) (varsDefaults map[string]string, para
256281// Parse "coder_parameter" to find the default value.
257282resContent ,_ ,diags := block .Body .PartialContent (coderParameterSchema )
258283if diags .HasErrors () {
259- return nil ,nil , xerrors .Errorf (`can't parse the coder_parameter: %s` ,diags .Error ())
284+ return nil ,xerrors .Errorf (`can't parse the coder_parameter: %s` ,diags .Error ())
260285}
261286
262287if _ ,ok := resContent .Attributes ["default" ];! ok {
263- paramsDefaults [dataResource .Name ]= ""
288+ defaultsM [dataResource .Name ]= ""
264289}else {
265290expr := resContent .Attributes ["default" ].Expr
266291value ,err := previewFileContent (expr .Range ())
267292if err != nil {
268- return nil ,nil , xerrors .Errorf ("can't preview the resource file: %v" ,err )
293+ return nil ,xerrors .Errorf ("can't preview the resource file: %v" ,err )
269294}
270- paramsDefaults [dataResource .Name ]= strings .Trim (value ,`"` )
295+ defaultsM [dataResource .Name ]= strings .Trim (value ,`"` )
271296}
272297}
273298}
274- return varsDefaults , paramsDefaults ,nil
299+ return defaultsM ,nil
275300}
276301
277302// EvalProvisionerTags evaluates the given workspaceTags based on the given