coder/provisioner/terraform/parse_test.go

312 lines
6.6 KiB
Go

//go:build linux || darwin
package terraform_test
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/provisionersdk/proto"
)
func TestParse(t *testing.T) {
t.Parallel()
ctx, api := setupProvisioner(t, nil)
testCases := []struct {
Name string
Files map[string]string
Response *proto.Parse_Response
// If ErrorContains is not empty, then response.Recv() should return an
// error containing this string before a Complete response is returned.
ErrorContains string
}{
{
Name: "single-variable",
Files: map[string]string{
"main.tf": `variable "A" {
description = "Testing!"
}
provider "coder" { feature_use_managed_variables = "true" }`,
},
Response: &proto.Parse_Response{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
TemplateVariables: []*proto.TemplateVariable{
{
Name: "A",
Description: "Testing!",
Required: true,
},
},
},
},
},
},
{
Name: "default-variable-value",
Files: map[string]string{
"main.tf": `variable "A" {
default = "wow"
}
provider "coder" { feature_use_managed_variables = "true" }`,
},
Response: &proto.Parse_Response{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
TemplateVariables: []*proto.TemplateVariable{
{
Name: "A",
DefaultValue: "wow",
},
},
},
},
},
},
{
Name: "variable-validation",
Files: map[string]string{
"main.tf": `variable "A" {
validation {
condition = var.A == "value"
}
}
provider "coder" { feature_use_managed_variables = "true" }`,
},
Response: &proto.Parse_Response{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
TemplateVariables: []*proto.TemplateVariable{
{
Name: "A",
Required: true,
},
},
},
},
},
},
{
Name: "bad-syntax",
Files: map[string]string{
"main.tf": "a;sd;ajsd;lajsd;lasjdf;a",
},
ErrorContains: `The ";" character is not valid.`,
},
{
Name: "multiple-variables",
Files: map[string]string{
"main1.tf": `variable "foo" { }
variable "bar" { }
provider "coder" { feature_use_managed_variables = "true" }`,
"main2.tf": `variable "baz" { }
variable "quux" { }`,
},
Response: &proto.Parse_Response{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
TemplateVariables: []*proto.TemplateVariable{
{
Name: "foo",
Required: true,
},
{
Name: "bar",
Required: true,
},
{
Name: "baz",
Required: true,
},
{
Name: "quux",
Required: true,
},
},
},
},
},
},
{
Name: "enable-managed-variables-with-default-bool",
Files: map[string]string{
"main.tf": `variable "A" {
description = "Testing!"
type = bool
default = true
sensitive = true
}
provider "coder" {
feature_use_managed_variables = true
}`,
},
Response: &proto.Parse_Response{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
TemplateVariables: []*proto.TemplateVariable{
{
Name: "A",
Description: "Testing!",
Type: "bool",
DefaultValue: "true",
Required: false,
Sensitive: true,
},
},
},
},
},
},
{
Name: "enable-managed-variables-with-default-string",
Files: map[string]string{
"main.tf": `variable "A" {
description = "Testing!"
type = string
default = "abc"
sensitive = true
}
provider "coder" {
feature_use_managed_variables = true
}`,
},
Response: &proto.Parse_Response{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
TemplateVariables: []*proto.TemplateVariable{
{
Name: "A",
Description: "Testing!",
Type: "string",
DefaultValue: "abc",
Required: false,
Sensitive: true,
},
},
},
},
},
},
{
Name: "enable-managed-variables-with-default-empty-string",
Files: map[string]string{
"main.tf": `variable "A" {
description = "Testing!"
type = string
default = ""
sensitive = true
}
provider "coder" {
feature_use_managed_variables = true
}`,
},
Response: &proto.Parse_Response{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
TemplateVariables: []*proto.TemplateVariable{
{
Name: "A",
Description: "Testing!",
Type: "string",
DefaultValue: "",
Required: false,
Sensitive: true,
},
},
},
},
},
},
{
Name: "enable-managed-variables-without-default",
Files: map[string]string{
"main2.tf": `variable "A" {
description = "Testing!"
type = string
sensitive = true
}
provider "coder" {
feature_use_managed_variables = true
}`,
},
Response: &proto.Parse_Response{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
TemplateVariables: []*proto.TemplateVariable{
{
Name: "A",
Description: "Testing!",
Type: "string",
DefaultValue: "",
Required: true,
Sensitive: true,
},
},
},
},
},
},
}
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.Name, func(t *testing.T) {
t.Parallel()
// Write all files to the temporary test directory.
directory := t.TempDir()
for path, content := range testCase.Files {
err := os.WriteFile(filepath.Join(directory, path), []byte(content), 0o600)
require.NoError(t, err)
}
response, err := api.Parse(ctx, &proto.Parse_Request{
Directory: directory,
})
require.NoError(t, err)
for {
msg, err := response.Recv()
if err != nil {
if testCase.ErrorContains != "" {
require.ErrorContains(t, err, testCase.ErrorContains)
break
}
require.NoError(t, err)
}
if msg.GetComplete() == nil {
continue
}
if testCase.ErrorContains != "" {
t.Fatal("expected error but job completed successfully")
}
// Ensure the want and got are equivalent!
want, err := json.Marshal(testCase.Response)
require.NoError(t, err)
got, err := json.Marshal(msg)
require.NoError(t, err)
require.Equal(t, string(want), string(got))
break
}
})
}
}