Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

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

Sign up
Appearance settings

Commit5ad4747

Browse files
chore(provisioner/terraform): extract terraform parsing logic to package tfparse (#15230)
Related to#15087Extracts the logic for extracting variables and workspace tagsto a separate package `tfparse`.---------Co-authored-by: Danielle Maywood <danielle@themaywoods.com>
1 parentd9f1aaf commit5ad4747

File tree

2 files changed

+186
-172
lines changed

2 files changed

+186
-172
lines changed

‎provisioner/terraform/parse.go‎

Lines changed: 4 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
11
package terraform
22

33
import (
4-
"context"
5-
"encoding/json"
64
"fmt"
7-
"os"
85
"path/filepath"
9-
"slices"
10-
"sort"
116
"strings"
127

13-
"github.com/hashicorp/hcl/v2"
14-
"github.com/hashicorp/hcl/v2/hclparse"
15-
"github.com/hashicorp/hcl/v2/hclsyntax"
168
"github.com/hashicorp/terraform-config-inspect/tfconfig"
179
"github.com/mitchellh/go-wordwrap"
18-
"golang.org/x/xerrors"
1910

2011
"github.com/coder/coder/v2/coderd/tracing"
12+
"github.com/coder/coder/v2/provisioner/terraform/tfparse"
2113
"github.com/coder/coder/v2/provisionersdk"
2214
"github.com/coder/coder/v2/provisionersdk/proto"
2315
)
@@ -34,12 +26,12 @@ func (s *server) Parse(sess *provisionersdk.Session, _ *proto.ParseRequest, _ <-
3426
returnprovisionersdk.ParseErrorf("load module: %s",formatDiagnostics(sess.WorkDirectory,diags))
3527
}
3628

37-
workspaceTags,err:=s.loadWorkspaceTags(ctx,module)
29+
workspaceTags,err:=tfparse.WorkspaceTags(ctx,s.logger,module)
3830
iferr!=nil {
3931
returnprovisionersdk.ParseErrorf("can't load workspace tags: %v",err)
4032
}
4133

42-
templateVariables,err:=loadTerraformVariables(module)
34+
templateVariables,err:=tfparse.LoadTerraformVariables(module)
4335
iferr!=nil {
4436
returnprovisionersdk.ParseErrorf("can't load template variables: %v",err)
4537
}
@@ -50,160 +42,7 @@ func (s *server) Parse(sess *provisionersdk.Session, _ *proto.ParseRequest, _ <-
5042
}
5143
}
5244

53-
varrootTemplateSchema=&hcl.BodySchema{
54-
Blocks: []hcl.BlockHeaderSchema{
55-
{
56-
Type:"data",
57-
LabelNames: []string{"type","name"},
58-
},
59-
},
60-
}
61-
62-
varcoderWorkspaceTagsSchema=&hcl.BodySchema{
63-
Attributes: []hcl.AttributeSchema{
64-
{
65-
Name:"tags",
66-
},
67-
},
68-
}
69-
70-
func (s*server)loadWorkspaceTags(ctx context.Context,module*tfconfig.Module) (map[string]string,error) {
71-
workspaceTags:=map[string]string{}
72-
73-
for_,dataResource:=rangemodule.DataResources {
74-
ifdataResource.Type!="coder_workspace_tags" {
75-
s.logger.Debug(ctx,"skip resource as it is not a coder_workspace_tags","resource_name",dataResource.Name,"resource_type",dataResource.Type)
76-
continue
77-
}
78-
79-
varfile*hcl.File
80-
vardiags hcl.Diagnostics
81-
parser:=hclparse.NewParser()
82-
83-
if!strings.HasSuffix(dataResource.Pos.Filename,".tf") {
84-
s.logger.Debug(ctx,"only .tf files can be parsed","filename",dataResource.Pos.Filename)
85-
continue
86-
}
87-
// We know in which HCL file is the data resource defined.
88-
file,diags=parser.ParseHCLFile(dataResource.Pos.Filename)
89-
90-
ifdiags.HasErrors() {
91-
returnnil,xerrors.Errorf("can't parse the resource file: %s",diags.Error())
92-
}
93-
94-
// Parse root to find "coder_workspace_tags".
95-
content,_,diags:=file.Body.PartialContent(rootTemplateSchema)
96-
ifdiags.HasErrors() {
97-
returnnil,xerrors.Errorf("can't parse the resource file: %s",diags.Error())
98-
}
99-
100-
// Iterate over blocks to locate the exact "coder_workspace_tags" data resource.
101-
for_,block:=rangecontent.Blocks {
102-
if!slices.Equal(block.Labels, []string{"coder_workspace_tags",dataResource.Name}) {
103-
continue
104-
}
105-
106-
// Parse "coder_workspace_tags" to find all key-value tags.
107-
resContent,_,diags:=block.Body.PartialContent(coderWorkspaceTagsSchema)
108-
ifdiags.HasErrors() {
109-
returnnil,xerrors.Errorf(`can't parse the resource coder_workspace_tags: %s`,diags.Error())
110-
}
111-
112-
ifresContent==nil {
113-
continue// workspace tags are not present
114-
}
115-
116-
if_,ok:=resContent.Attributes["tags"];!ok {
117-
returnnil,xerrors.Errorf(`"tags" attribute is required by coder_workspace_tags`)
118-
}
119-
120-
expr:=resContent.Attributes["tags"].Expr
121-
tagsExpr,ok:=expr.(*hclsyntax.ObjectConsExpr)
122-
if!ok {
123-
returnnil,xerrors.Errorf(`"tags" attribute is expected to be a key-value map`)
124-
}
125-
126-
// Parse key-value entries in "coder_workspace_tags"
127-
for_,tagItem:=rangetagsExpr.Items {
128-
key,err:=previewFileContent(tagItem.KeyExpr.Range())
129-
iferr!=nil {
130-
returnnil,xerrors.Errorf("can't preview the resource file: %v",err)
131-
}
132-
key=strings.Trim(key,`"`)
133-
134-
value,err:=previewFileContent(tagItem.ValueExpr.Range())
135-
iferr!=nil {
136-
returnnil,xerrors.Errorf("can't preview the resource file: %v",err)
137-
}
138-
139-
s.logger.Info(ctx,"workspace tag found","key",key,"value",value)
140-
141-
if_,ok:=workspaceTags[key];ok {
142-
returnnil,xerrors.Errorf(`workspace tag "%s" is defined multiple times`,key)
143-
}
144-
workspaceTags[key]=value
145-
}
146-
}
147-
}
148-
returnworkspaceTags,nil
149-
}
150-
151-
funcpreviewFileContent(fileRange hcl.Range) (string,error) {
152-
body,err:=os.ReadFile(fileRange.Filename)
153-
iferr!=nil {
154-
return"",err
155-
}
156-
returnstring(fileRange.SliceBytes(body)),nil
157-
}
158-
159-
funcloadTerraformVariables(module*tfconfig.Module) ([]*proto.TemplateVariable,error) {
160-
// Sort variables by (filename, line) to make the ordering consistent
161-
variables:=make([]*tfconfig.Variable,0,len(module.Variables))
162-
for_,v:=rangemodule.Variables {
163-
variables=append(variables,v)
164-
}
165-
sort.Slice(variables,func(i,jint)bool {
166-
returncompareSourcePos(variables[i].Pos,variables[j].Pos)
167-
})
168-
169-
vartemplateVariables []*proto.TemplateVariable
170-
for_,v:=rangevariables {
171-
mv,err:=convertTerraformVariable(v)
172-
iferr!=nil {
173-
returnnil,err
174-
}
175-
templateVariables=append(templateVariables,mv)
176-
}
177-
returntemplateVariables,nil
178-
}
179-
180-
// Converts a Terraform variable to a template-wide variable, processed by Coder.
181-
funcconvertTerraformVariable(variable*tfconfig.Variable) (*proto.TemplateVariable,error) {
182-
vardefaultDatastring
183-
ifvariable.Default!=nil {
184-
varvalidbool
185-
defaultData,valid=variable.Default.(string)
186-
if!valid {
187-
defaultDataRaw,err:=json.Marshal(variable.Default)
188-
iferr!=nil {
189-
returnnil,xerrors.Errorf("parse variable %q default: %w",variable.Name,err)
190-
}
191-
defaultData=string(defaultDataRaw)
192-
}
193-
}
194-
195-
return&proto.TemplateVariable{
196-
Name:variable.Name,
197-
Description:variable.Description,
198-
Type:variable.Type,
199-
DefaultValue:defaultData,
200-
// variable.Required is always false. Empty string is a valid default value, so it doesn't enforce required to be "true".
201-
Required:variable.Default==nil,
202-
Sensitive:variable.Sensitive,
203-
},nil
204-
}
205-
206-
// formatDiagnostics returns a nicely formatted string containing all of the
45+
// FormatDiagnostics returns a nicely formatted string containing all of the
20746
// error details within the tfconfig.Diagnostics. We need to use this because
20847
// the default format doesn't provide much useful information.
20948
funcformatDiagnostics(baseDirstring,diags tfconfig.Diagnostics)string {
@@ -246,10 +85,3 @@ func formatDiagnostics(baseDir string, diags tfconfig.Diagnostics) string {
24685

24786
returnspacer+strings.TrimSpace(msgs.String())
24887
}
249-
250-
funccompareSourcePos(x,y tfconfig.SourcePos)bool {
251-
ifx.Filename!=y.Filename {
252-
returnx.Filename<y.Filename
253-
}
254-
returnx.Line<y.Line
255-
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package tfparse
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"os"
7+
"slices"
8+
"sort"
9+
"strings"
10+
11+
"github.com/coder/coder/v2/provisionersdk/proto"
12+
13+
"github.com/hashicorp/hcl/v2"
14+
"github.com/hashicorp/hcl/v2/hclparse"
15+
"github.com/hashicorp/hcl/v2/hclsyntax"
16+
"github.com/hashicorp/terraform-config-inspect/tfconfig"
17+
"golang.org/x/xerrors"
18+
19+
"cdr.dev/slog"
20+
)
21+
22+
// WorkspaceTags extracts tags from coder_workspace_tags data sources defined in module.
23+
funcWorkspaceTags(ctx context.Context,logger slog.Logger,module*tfconfig.Module) (map[string]string,error) {
24+
workspaceTags:=map[string]string{}
25+
26+
for_,dataResource:=rangemodule.DataResources {
27+
ifdataResource.Type!="coder_workspace_tags" {
28+
logger.Debug(ctx,"skip resource as it is not a coder_workspace_tags","resource_name",dataResource.Name,"resource_type",dataResource.Type)
29+
continue
30+
}
31+
32+
varfile*hcl.File
33+
vardiags hcl.Diagnostics
34+
parser:=hclparse.NewParser()
35+
36+
if!strings.HasSuffix(dataResource.Pos.Filename,".tf") {
37+
logger.Debug(ctx,"only .tf files can be parsed","filename",dataResource.Pos.Filename)
38+
continue
39+
}
40+
// We know in which HCL file is the data resource defined.
41+
file,diags=parser.ParseHCLFile(dataResource.Pos.Filename)
42+
ifdiags.HasErrors() {
43+
returnnil,xerrors.Errorf("can't parse the resource file: %s",diags.Error())
44+
}
45+
46+
// Parse root to find "coder_workspace_tags".
47+
content,_,diags:=file.Body.PartialContent(rootTemplateSchema)
48+
ifdiags.HasErrors() {
49+
returnnil,xerrors.Errorf("can't parse the resource file: %s",diags.Error())
50+
}
51+
52+
// Iterate over blocks to locate the exact "coder_workspace_tags" data resource.
53+
for_,block:=rangecontent.Blocks {
54+
if!slices.Equal(block.Labels, []string{"coder_workspace_tags",dataResource.Name}) {
55+
continue
56+
}
57+
58+
// Parse "coder_workspace_tags" to find all key-value tags.
59+
resContent,_,diags:=block.Body.PartialContent(coderWorkspaceTagsSchema)
60+
ifdiags.HasErrors() {
61+
returnnil,xerrors.Errorf(`can't parse the resource coder_workspace_tags: %s`,diags.Error())
62+
}
63+
64+
ifresContent==nil {
65+
continue// workspace tags are not present
66+
}
67+
68+
if_,ok:=resContent.Attributes["tags"];!ok {
69+
returnnil,xerrors.Errorf(`"tags" attribute is required by coder_workspace_tags`)
70+
}
71+
72+
expr:=resContent.Attributes["tags"].Expr
73+
tagsExpr,ok:=expr.(*hclsyntax.ObjectConsExpr)
74+
if!ok {
75+
returnnil,xerrors.Errorf(`"tags" attribute is expected to be a key-value map`)
76+
}
77+
78+
// Parse key-value entries in "coder_workspace_tags"
79+
for_,tagItem:=rangetagsExpr.Items {
80+
key,err:=previewFileContent(tagItem.KeyExpr.Range())
81+
iferr!=nil {
82+
returnnil,xerrors.Errorf("can't preview the resource file: %v",err)
83+
}
84+
key=strings.Trim(key,`"`)
85+
86+
value,err:=previewFileContent(tagItem.ValueExpr.Range())
87+
iferr!=nil {
88+
returnnil,xerrors.Errorf("can't preview the resource file: %v",err)
89+
}
90+
91+
logger.Info(ctx,"workspace tag found","key",key,"value",value)
92+
93+
if_,ok:=workspaceTags[key];ok {
94+
returnnil,xerrors.Errorf(`workspace tag %q is defined multiple times`,key)
95+
}
96+
workspaceTags[key]=value
97+
}
98+
}
99+
}
100+
returnworkspaceTags,nil
101+
}
102+
103+
varrootTemplateSchema=&hcl.BodySchema{
104+
Blocks: []hcl.BlockHeaderSchema{
105+
{
106+
Type:"data",
107+
LabelNames: []string{"type","name"},
108+
},
109+
},
110+
}
111+
112+
varcoderWorkspaceTagsSchema=&hcl.BodySchema{
113+
Attributes: []hcl.AttributeSchema{
114+
{
115+
Name:"tags",
116+
},
117+
},
118+
}
119+
120+
funcpreviewFileContent(fileRange hcl.Range) (string,error) {
121+
body,err:=os.ReadFile(fileRange.Filename)
122+
iferr!=nil {
123+
return"",err
124+
}
125+
returnstring(fileRange.SliceBytes(body)),nil
126+
}
127+
128+
// LoadTerraformVariables extracts all Terraform variables from module and converts them
129+
// to template variables. The variables are sorted by source position.
130+
funcLoadTerraformVariables(module*tfconfig.Module) ([]*proto.TemplateVariable,error) {
131+
// Sort variables by (filename, line) to make the ordering consistent
132+
variables:=make([]*tfconfig.Variable,0,len(module.Variables))
133+
for_,v:=rangemodule.Variables {
134+
variables=append(variables,v)
135+
}
136+
sort.Slice(variables,func(i,jint)bool {
137+
returncompareSourcePos(variables[i].Pos,variables[j].Pos)
138+
})
139+
140+
vartemplateVariables []*proto.TemplateVariable
141+
for_,v:=rangevariables {
142+
mv,err:=convertTerraformVariable(v)
143+
iferr!=nil {
144+
returnnil,err
145+
}
146+
templateVariables=append(templateVariables,mv)
147+
}
148+
returntemplateVariables,nil
149+
}
150+
151+
// convertTerraformVariable converts a Terraform variable to a template-wide variable, processed by Coder.
152+
funcconvertTerraformVariable(variable*tfconfig.Variable) (*proto.TemplateVariable,error) {
153+
vardefaultDatastring
154+
ifvariable.Default!=nil {
155+
varvalidbool
156+
defaultData,valid=variable.Default.(string)
157+
if!valid {
158+
defaultDataRaw,err:=json.Marshal(variable.Default)
159+
iferr!=nil {
160+
returnnil,xerrors.Errorf("parse variable %q default: %w",variable.Name,err)
161+
}
162+
defaultData=string(defaultDataRaw)
163+
}
164+
}
165+
166+
return&proto.TemplateVariable{
167+
Name:variable.Name,
168+
Description:variable.Description,
169+
Type:variable.Type,
170+
DefaultValue:defaultData,
171+
// variable.Required is always false. Empty string is a valid default value, so it doesn't enforce required to be "true".
172+
Required:variable.Default==nil,
173+
Sensitive:variable.Sensitive,
174+
},nil
175+
}
176+
177+
funccompareSourcePos(x,y tfconfig.SourcePos)bool {
178+
ifx.Filename!=y.Filename {
179+
returnx.Filename<y.Filename
180+
}
181+
returnx.Line<y.Line
182+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp