Skip to content

Commit 84f6fe5

Browse files
committed
hcl
1 parent 58fc371 commit 84f6fe5

File tree

2 files changed

+122
-19
lines changed

2 files changed

+122
-19
lines changed

provisioner/terraform/parse.go

Lines changed: 108 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,41 @@ import (
44
"encoding/json"
55
"fmt"
66
"os"
7+
"path"
78
"path/filepath"
89
"sort"
10+
"strconv"
911
"strings"
1012

13+
"github.com/hashicorp/hcl/v2"
14+
"github.com/hashicorp/hcl/v2/gohcl"
15+
"github.com/hashicorp/hcl/v2/hclparse"
1116
"github.com/hashicorp/terraform-config-inspect/tfconfig"
1217
"github.com/mitchellh/go-wordwrap"
1318
"golang.org/x/xerrors"
1419

1520
"github.com/coder/coder/provisionersdk/proto"
1621
)
1722

23+
const featureUseManagedVariables = "feature_use_managed_variables"
24+
25+
var terraformWithFeaturesSchema = &hcl.BodySchema{
26+
Blocks: []hcl.BlockHeaderSchema{
27+
{
28+
Type: "provider",
29+
LabelNames: []string{"type"},
30+
},
31+
},
32+
}
33+
34+
var providerFeaturesConfigSchema = &hcl.BodySchema{
35+
Attributes: []hcl.AttributeSchema{
36+
{
37+
Name: featureUseManagedVariables,
38+
},
39+
},
40+
}
41+
1842
// Parse extracts Terraform variables from source-code.
1943
func (*server) Parse(request *proto.Parse_Request, stream proto.DRPCProvisioner_ParseStream) error {
2044
// Load the module and print any parse errors.
@@ -23,7 +47,13 @@ func (*server) Parse(request *proto.Parse_Request, stream proto.DRPCProvisioner_
2347
return xerrors.Errorf("load module: %s", formatDiagnostics(request.Directory, diags))
2448
}
2549

26-
fmt.Println(module.ProviderConfigs["coder"].Name)
50+
flags, flagsDiags, err := loadEnabledFeatures(request.Directory)
51+
if flagsDiags.HasErrors() {
52+
return xerrors.Errorf("load coder provider features: %s", formatDiagnostics(request.Directory, diags))
53+
}
54+
if err != nil {
55+
return xerrors.Errorf("load coder provider features: %w", err)
56+
}
2757

2858
// Sort variables by (filename, line) to make the ordering consistent
2959
variables := make([]*tfconfig.Variable, 0, len(module.Variables))
@@ -34,25 +64,69 @@ func (*server) Parse(request *proto.Parse_Request, stream proto.DRPCProvisioner_
3464
return compareSourcePos(variables[i].Pos, variables[j].Pos)
3565
})
3666

37-
parameters := make([]*proto.ParameterSchema, 0, len(variables))
38-
for _, v := range variables {
39-
schema, err := convertVariableToParameter(v)
40-
if err != nil {
41-
return xerrors.Errorf("convert variable %q: %w", v.Name, err)
67+
var parameters []*proto.ParameterSchema
68+
var templateVariables []*proto.TemplateVariable
69+
70+
useManagedVariables := flags[featureUseManagedVariables]
71+
if useManagedVariables {
72+
for _, v := range variables {
73+
mv, err := convertTerraformVariableToManagedVariable(v)
74+
if err != nil {
75+
return xerrors.Errorf("can't convert the Terraform variable to a managed one: %w", err)
76+
}
77+
templateVariables = append(templateVariables, mv)
4278
}
79+
} else {
80+
for _, v := range variables {
81+
schema, err := convertVariableToParameter(v)
82+
if err != nil {
83+
return xerrors.Errorf("convert variable %q: %w", v.Name, err)
84+
}
4385

44-
parameters = append(parameters, schema)
86+
parameters = append(parameters, schema)
87+
}
4588
}
46-
4789
return stream.Send(&proto.Parse_Response{
4890
Type: &proto.Parse_Response_Complete{
4991
Complete: &proto.Parse_Complete{
50-
ParameterSchemas: parameters,
92+
ParameterSchemas: parameters,
93+
TemplateVariables: templateVariables,
5194
},
5295
},
5396
})
5497
}
5598

99+
func loadEnabledFeatures(moduleDir string) (map[string]bool, hcl.Diagnostics, error) {
100+
parser := hclparse.NewParser()
101+
mainFile, err := parser.ParseHCLFile(path.Join(moduleDir, "main.tf"))
102+
if err != nil {
103+
return nil, nil, xerrors.Errorf("can't parse main.tf file: %w", err)
104+
}
105+
106+
flags := map[string]bool{}
107+
var diags hcl.Diagnostics
108+
109+
content, _ := mainFile.Body.Content(terraformWithFeaturesSchema)
110+
for _, block := range content.Blocks {
111+
if block.Type == "provider" && block.Labels[0] == "coder" {
112+
content, _, partialDiags := block.Body.PartialContent(providerFeaturesConfigSchema)
113+
diags = append(diags, partialDiags...)
114+
if attr, defined := content.Attributes[featureUseManagedVariables]; defined {
115+
var useManagedVariables string
116+
partialDiags := gohcl.DecodeExpression(attr.Expr, nil, &useManagedVariables)
117+
diags = append(diags, partialDiags...)
118+
119+
b, err := strconv.ParseBool(useManagedVariables)
120+
if err != nil {
121+
return nil, nil, xerrors.Errorf("can't parse %s flag as boolean: %w", featureUseManagedVariables, err)
122+
}
123+
flags[featureUseManagedVariables] = b
124+
}
125+
}
126+
}
127+
return flags, diags, nil
128+
}
129+
56130
// Converts a Terraform variable to a provisioner parameter.
57131
func convertVariableToParameter(variable *tfconfig.Variable) (*proto.ParameterSchema, error) {
58132
schema := &proto.ParameterSchema{
@@ -98,6 +172,31 @@ func convertVariableToParameter(variable *tfconfig.Variable) (*proto.ParameterSc
98172
return schema, nil
99173
}
100174

175+
// Converts a Terraform variable to a managed variable.
176+
func convertTerraformVariableToManagedVariable(variable *tfconfig.Variable) (*proto.TemplateVariable, error) {
177+
var defaultData string
178+
if variable.Default != nil {
179+
var valid bool
180+
defaultData, valid = variable.Default.(string)
181+
if !valid {
182+
defaultDataRaw, err := json.Marshal(variable.Default)
183+
if err != nil {
184+
return nil, xerrors.Errorf("parse variable %q default: %w", variable.Name, err)
185+
}
186+
defaultData = string(defaultDataRaw)
187+
}
188+
}
189+
190+
return &proto.TemplateVariable{
191+
Name: variable.Name,
192+
Description: variable.Description,
193+
Type: variable.Type,
194+
DefaultValue: defaultData,
195+
Required: variable.Required,
196+
Sensitive: variable.Sensitive,
197+
}, nil
198+
}
199+
101200
// formatDiagnostics returns a nicely formatted string containing all of the
102201
// error details within the tfconfig.Diagnostics. We need to use this because
103202
// the default format doesn't provide much useful information.

provisioner/terraform/parse_test.go

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -164,28 +164,32 @@ func TestParse(t *testing.T) {
164164
Files: map[string]string{
165165
"main.tf": `variable "A" {
166166
description = "Testing!"
167+
type = "string"
168+
default = "abc"
169+
required = true
170+
sensitive = true
167171
}
168172
169173
provider "coder" {
170-
enable_managed_variables = "true"
174+
feature_use_managed_variables = true
171175
}`,
172176
},
173177
Response: &proto.Parse_Response{
174178
Type: &proto.Parse_Response_Complete{
175179
Complete: &proto.Parse_Complete{
176-
ParameterSchemas: []*proto.ParameterSchema{{
177-
Name: "A",
178-
RedisplayValue: true,
179-
AllowOverrideSource: true,
180-
Description: "Testing!",
181-
DefaultDestination: &proto.ParameterDestination{
182-
Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE,
180+
TemplateVariables: []*proto.TemplateVariable{
181+
{
182+
Name: "A",
183+
Description: "Testing!",
184+
Type: "string",
185+
DefaultValue: "abc",
186+
Required: false,
187+
Sensitive: true,
183188
},
184-
}},
189+
},
185190
},
186191
},
187192
},
188-
ErrorContains: "blah",
189193
},
190194
}
191195

0 commit comments

Comments
 (0)