Skip to content

Commit 14a5901

Browse files
committed
use caching of hcl.NewParser
1 parent ad2d3a9 commit 14a5901

File tree

3 files changed

+60
-35
lines changed

3 files changed

+60
-35
lines changed

provisioner/terraform/parse.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func (s *server) Parse(sess *provisionersdk.Session, _ *proto.ParseRequest, _ <-
2626
return provisionersdk.ParseErrorf("load module: %s", formatDiagnostics(sess.WorkDirectory, diags))
2727
}
2828

29-
workspaceTags, err := tfparse.WorkspaceTags(ctx, s.logger, module)
29+
workspaceTags, err := tfparse.WorkspaceTags(module)
3030
if err != nil {
3131
return provisionersdk.ParseErrorf("can't load workspace tags: %v", err)
3232
}

provisioner/terraform/tfparse/tfextract.go

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -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
3132
const 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

3954
for _, dataResource := range module.DataResources {
4055
if dataResource.Type != "coder_workspace_tags" {
@@ -43,13 +58,12 @@ func WorkspaceTags(ctx context.Context, module *tfconfig.Module) (map[string]str
4358

4459
var file *hcl.File
4560
var diags hcl.Diagnostics
46-
parser := hclparse.NewParser()
4761

4862
if !strings.HasSuffix(dataResource.Pos.Filename, ".tf") {
4963
continue
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)
5367
if diags.HasErrors() {
5468
return 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
99113
return 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 {
103117
return 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)
135149
if err != nil {
136150
return 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))
142160
if err != nil {
143-
return nil, xerrors.Errorf("load defaults: %w", err)
161+
return nil, xerrors.Errorf("load parameter 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,
205223
return 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+
}
215235
sv, err := interfaceToString(v.Default)
216236
if 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 {
226252
if dataResource.Type != "coder_parameter" {
227253
continue
228254
}
229255

230256
var file *hcl.File
231257
var diags hcl.Diagnostics
232-
parser := hclparse.NewParser()
233258

234259
if !strings.HasSuffix(dataResource.Pos.Filename, ".tf") {
235260
continue
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)
240265
if 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".
245270
content, _, diags := file.Body.PartialContent(rootTemplateSchema)
246271
if 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.
257282
resContent, _, diags := block.Body.PartialContent(coderParameterSchema)
258283
if 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

262287
if _, ok := resContent.Attributes["default"]; !ok {
263-
paramsDefaults[dataResource.Name] = ""
288+
defaultsM[dataResource.Name] = ""
264289
} else {
265290
expr := resContent.Attributes["default"].Expr
266291
value, err := previewFileContent(expr.Range())
267292
if 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

provisioner/terraform/tfparse/tfparse_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -390,13 +390,13 @@ func createZip(t testing.TB, files map[string]string) []byte {
390390
return za
391391
}
392392

393-
// Current benchmark results before any changes / caching.
393+
// Last run results:
394394
// goos: linux
395395
// goarch: amd64
396396
// pkg: github.com/coder/coder/v2/provisioner/terraform/tfparse
397397
// cpu: AMD EPYC 7502P 32-Core Processor
398-
// BenchmarkWorkspaceTagDefaultsFromFile/Tar-16 766 1493850 ns/op 339935 B/op 2238 allocs/op
399-
// BenchmarkWorkspaceTagDefaultsFromFile/Zip-16 706 1633258 ns/op 389421 B/op 2296 allocs/op
398+
// BenchmarkWorkspaceTagDefaultsFromFile/Tar-16 1147 1073487 ns/op 200266 B/op 1309 allocs/op
399+
// BenchmarkWorkspaceTagDefaultsFromFile/Zip-16 991 1030536 ns/op 248063 B/op 1364 allocs/op
400400
// PASS
401401
func BenchmarkWorkspaceTagDefaultsFromFile(b *testing.B) {
402402
files := map[string]string{

0 commit comments

Comments
 (0)