8000 use caching of hcl.NewParser · coder/coder@14a5901 · GitHub
[go: up one dir, main page]

Skip to content
Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

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)
0