Skip to content

Commit 52ead3d

Browse files
TECHNOFAB11coadler
andauthored
feat(provisioner): add support for .tf.json templates (#7835)
Co-authored-by: Colin Adler <colin1adler@gmail.com>
1 parent f0c5201 commit 52ead3d

File tree

5 files changed

+152
-14
lines changed

5 files changed

+152
-14
lines changed

provisioner/terraform/parameters.go

+21-6
Original file line numberDiff line numberDiff line change
@@ -27,29 +27,44 @@ func rawRichParameterNames(workdir string) ([]string, error) {
2727

2828
var coderParameterNames []string
2929
for _, entry := range entries {
30-
if !strings.HasSuffix(entry.Name(), ".tf") {
30+
if !strings.HasSuffix(entry.Name(), ".tf") && !strings.HasSuffix(entry.Name(), ".tf.json") {
3131
continue
3232
}
3333

34-
hclFilepath := path.Join(workdir, entry.Name())
35-
parser := hclparse.NewParser()
36-
parsedHCL, diags := parser.ParseHCLFile(hclFilepath)
34+
var (
35+
parsedTF *hcl.File
36+
diags hcl.Diagnostics
37+
tfFilepath = path.Join(workdir, entry.Name())
38+
parser = hclparse.NewParser()
39+
)
40+
41+
// Support .tf.json files.
42+
// Warning: since JSON parsing in Go automatically sorts maps
43+
// alphabetically, we can't preserve the original order of parameters
44+
// like in HCL.
45+
if strings.HasSuffix(entry.Name(), ".tf.json") {
46+
parsedTF, diags = parser.ParseJSONFile(tfFilepath)
47+
} else {
48+
parsedTF, diags = parser.ParseHCLFile(tfFilepath)
49+
}
50+
3751
if diags.HasErrors() {
3852
return nil, hcl.Diagnostics{
3953
{
4054
Severity: hcl.DiagError,
4155
Summary: "Failed to parse HCL file",
42-
Detail: fmt.Sprintf("parser.ParseHCLFile can't parse %q file", hclFilepath),
56+
Detail: fmt.Sprintf("parser.ParseHCLFile can't parse %q file", tfFilepath),
4357
},
4458
}
4559
}
4660

47-
content, _, _ := parsedHCL.Body.PartialContent(terraformWithCoderParametersSchema)
61+
content, _, _ := parsedTF.Body.PartialContent(terraformWithCoderParametersSchema)
4862
for _, block := range content.Blocks {
4963
if block.Type == "data" && block.Labels[0] == "coder_parameter" && len(block.Labels) == 2 {
5064
coderParameterNames = append(coderParameterNames, block.Labels[1])
5165
}
5266
}
5367
}
68+
5469
return coderParameterNames, nil
5570
}

provisioner/terraform/parse.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func loadEnabledFeatures(moduleDir string) (map[string]bool, hcl.Diagnostics) {
103103

104104
var found bool
105105
for _, entry := range entries {
106-
if !strings.HasSuffix(entry.Name(), ".tf") {
106+
if !strings.HasSuffix(entry.Name(), ".tf") && !strings.HasSuffix(entry.Name(), ".tf.json") {
107107
continue
108108
}
109109

@@ -131,7 +131,12 @@ func parseFeatures(hclFilepath string) (map[string]bool, bool, hcl.Diagnostics)
131131
}
132132

133133
parser := hclparse.NewParser()
134-
parsedHCL, diags := parser.ParseHCLFile(hclFilepath)
134+
var parsedHCL *hcl.File
135+
if strings.HasSuffix(hclFilepath, ".tf.json") {
136+
parsedHCL, diags = parser.ParseJSONFile(hclFilepath)
137+
} else {
138+
parsedHCL, diags = parser.ParseHCLFile(hclFilepath)
139+
}
135140
if diags.HasErrors() {
136141
return flags, false, diags
137142
}

provisioner/terraform/provision_test.go

+107
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,31 @@ func TestProvision(t *testing.T) {
254254
},
255255
Apply: true,
256256
},
257+
{
258+
Name: "single-resource-json",
259+
Files: map[string]string{
260+
"main.tf.json": `{
261+
"resource": {
262+
"null_resource": {
263+
"A": [
264+
{}
265+
]
266+
}
267+
}
268+
}`,
269+
},
270+
Response: &proto.Provision_Response{
271+
Type: &proto.Provision_Response_Complete{
272+
Complete: &proto.Provision_Complete{
273+
Resources: []*proto.Resource{{
274+
Name: "A",
275+
Type: "null_resource",
276+
}},
277+
},
278+
},
279+
},
280+
Apply: true,
281+
},
257282
{
258283
Name: "bad-syntax-1",
259284
Files: map[string]string{
@@ -349,6 +374,88 @@ func TestProvision(t *testing.T) {
349374
},
350375
},
351376
},
377+
{
378+
Name: "rich-parameter-with-value-json",
379+
Files: map[string]string{
380+
"main.tf.json": `{
381+
"data": {
382+
"coder_parameter": {
383+
"example": [
384+
{
385+
"default": "foobar",
386+
"name": "Example",
387+
"type": "string"
388+
}
389+
],
390+
"sample": [
391+
{
392+
"default": "foobaz",
393+
"name": "Sample",
394+
"type": "string"
395+
}
396+
]
397+
}
398+
},
399+
"resource": {
400+
"null_resource": {
401+
"example": [
402+
{
403+
"triggers": {
404+
"misc": "${data.coder_parameter.example.value}"
405+
}
406+
}
407+
]
408+
}
409+
},
410+
"terraform": [
411+
{
412+
"required_providers": [
413+
{
414+
"coder": {
415+
"source": "coder/coder",
416+
"version": "0.6.20"
417+
}
418+
}
419+
]
420+
}
421+
]
422+
}`,
423+
},
424+
Request: &proto.Provision_Plan{
425+
RichParameterValues: []*proto.RichParameterValue{
426+
{
427+
Name: "Example",
428+
Value: "foobaz",
429+
},
430+
{
431+
Name: "Sample",
432+
Value: "foofoo",
433+
},
434+
},
435+
},
436+
Response: &proto.Provision_Response{
437+
Type: &proto.Provision_Response_Complete{
438+
Complete: &proto.Provision_Complete{
439+
Parameters: []*proto.RichParameter{
440+
{
441+
Name: "Example",
442+
Type: "string",
443+
DefaultValue: "foobar",
444+
},
445+
{
446+
Name: "Sample",
447+
Type: "string",
448+
DefaultValue: "foobaz",
449+
},
450+
},
451+
Resources: []*proto.Resource{{
452+
Name: "example",
453+
Type: "null_resource",
454+
}},
455+
},
456+
},
457+
},
458+
},
352459
{
353460
Name: "git-auth",
354461
Files: map[string]string{

provisionersdk/archive.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,17 @@ const (
1717
TemplateArchiveLimit = 1 << 20
1818
)
1919

20-
func dirHasExt(dir string, ext string) (bool, error) {
20+
func dirHasExt(dir string, exts ...string) (bool, error) {
2121
dirEnts, err := os.ReadDir(dir)
2222
if err != nil {
2323
return false, err
2424
}
2525

2626
for _, fi := range dirEnts {
27-
if strings.HasSuffix(fi.Name(), ext) {
28-
return true, nil
27+
for _, ext := range exts {
28+
if strings.HasSuffix(fi.Name(), ext) {
29+
return true, nil
30+
}
2931
}
3032
}
3133

@@ -38,8 +40,8 @@ func Tar(w io.Writer, directory string, limit int64) error {
3840
w = xio.NewLimitWriter(w, limit-1)
3941
tarWriter := tar.NewWriter(w)
4042

41-
const tfExt = ".tf"
42-
hasTf, err := dirHasExt(directory, tfExt)
43+
tfExts := []string{".tf", ".tf.json"}
44+
hasTf, err := dirHasExt(directory, tfExts...)
4345
if err != nil {
4446
return err
4547
}
@@ -53,7 +55,7 @@ func Tar(w io.Writer, directory string, limit int64) error {
5355
// useless.
5456
return xerrors.Errorf(
5557
"%s is not a valid template since it has no %s files",
56-
absPath, tfExt,
58+
absPath, tfExts,
5759
)
5860
}
5961

provisionersdk/archive_test.go

+9
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ func TestTar(t *testing.T) {
5959
err = provisionersdk.Tar(io.Discard, dir, 1024)
6060
require.NoError(t, err)
6161
})
62+
t.Run("ValidJSON", func(t *testing.T) {
63+
t.Parallel()
64+
dir := t.TempDir()
65+
file, err := os.CreateTemp(dir, "*.tf.json")
66+
require.NoError(t, err)
67+
_ = file.Close()
68+
err = provisionersdk.Tar(io.Discard, dir, 1024)
69+
require.NoError(t, err)
70+
})
6271
t.Run("HiddenFiles", func(t *testing.T) {
6372
t.Parallel()
6473
dir := t.TempDir()

0 commit comments

Comments
 (0)