@@ -4,17 +4,41 @@ import (
4
4
"encoding/json"
5
5
"fmt"
6
6
"os"
7
+ "path"
7
8
"path/filepath"
8
9
"sort"
10
+ "strconv"
9
11
"strings"
10
12
13
+ "github.com/hashicorp/hcl/v2"
14
+ "github.com/hashicorp/hcl/v2/gohcl"
15
+ "github.com/hashicorp/hcl/v2/hclparse"
11
16
"github.com/hashicorp/terraform-config-inspect/tfconfig"
12
17
"github.com/mitchellh/go-wordwrap"
13
18
"golang.org/x/xerrors"
14
19
15
20
"github.com/coder/coder/provisionersdk/proto"
16
21
)
17
22
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
+
18
42
// Parse extracts Terraform variables from source-code.
19
43
func (* server ) Parse (request * proto.Parse_Request , stream proto.DRPCProvisioner_ParseStream ) error {
20
44
// Load the module and print any parse errors.
@@ -23,7 +47,13 @@ func (*server) Parse(request *proto.Parse_Request, stream proto.DRPCProvisioner_
23
47
return xerrors .Errorf ("load module: %s" , formatDiagnostics (request .Directory , diags ))
24
48
}
25
49
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
+ }
27
57
28
58
// Sort variables by (filename, line) to make the ordering consistent
29
59
variables := make ([]* tfconfig.Variable , 0 , len (module .Variables ))
@@ -34,25 +64,69 @@ func (*server) Parse(request *proto.Parse_Request, stream proto.DRPCProvisioner_
34
64
return compareSourcePos (variables [i ].Pos , variables [j ].Pos )
35
65
})
36
66
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 )
42
78
}
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
+ }
43
85
44
- parameters = append (parameters , schema )
86
+ parameters = append (parameters , schema )
87
+ }
45
88
}
46
-
47
89
return stream .Send (& proto.Parse_Response {
48
90
Type : & proto.Parse_Response_Complete {
49
91
Complete : & proto.Parse_Complete {
50
- ParameterSchemas : parameters ,
92
+ ParameterSchemas : parameters ,
93
+ TemplateVariables : templateVariables ,
51
94
},
52
95
},
53
96
})
54
97
}
55
98
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
+
56
130
// Converts a Terraform variable to a provisioner parameter.
57
131
func convertVariableToParameter (variable * tfconfig.Variable ) (* proto.ParameterSchema , error ) {
58
132
schema := & proto.ParameterSchema {
@@ -98,6 +172,31 @@ func convertVariableToParameter(variable *tfconfig.Variable) (*proto.ParameterSc
98
172
return schema , nil
99
173
}
100
174
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
+
101
200
// formatDiagnostics returns a nicely formatted string containing all of the
102
201
// error details within the tfconfig.Diagnostics. We need to use this because
103
202
// the default format doesn't provide much useful information.
0 commit comments