feat(provisioner): add support for .tf.json templates (#7835)

Co-authored-by: Colin Adler <colin1adler@gmail.com>
This commit is contained in:
Technofab 2023-06-08 00:06:50 +02:00 committed by GitHub
parent f0c5201617
commit 52ead3d933
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 152 additions and 14 deletions

View File

@ -27,29 +27,44 @@ func rawRichParameterNames(workdir string) ([]string, error) {
var coderParameterNames []string
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".tf") {
if !strings.HasSuffix(entry.Name(), ".tf") && !strings.HasSuffix(entry.Name(), ".tf.json") {
continue
}
hclFilepath := path.Join(workdir, entry.Name())
parser := hclparse.NewParser()
parsedHCL, diags := parser.ParseHCLFile(hclFilepath)
var (
parsedTF *hcl.File
diags hcl.Diagnostics
tfFilepath = path.Join(workdir, entry.Name())
parser = hclparse.NewParser()
)
// Support .tf.json files.
// Warning: since JSON parsing in Go automatically sorts maps
// alphabetically, we can't preserve the original order of parameters
// like in HCL.
if strings.HasSuffix(entry.Name(), ".tf.json") {
parsedTF, diags = parser.ParseJSONFile(tfFilepath)
} else {
parsedTF, diags = parser.ParseHCLFile(tfFilepath)
}
if diags.HasErrors() {
return nil, hcl.Diagnostics{
{
Severity: hcl.DiagError,
Summary: "Failed to parse HCL file",
Detail: fmt.Sprintf("parser.ParseHCLFile can't parse %q file", hclFilepath),
Detail: fmt.Sprintf("parser.ParseHCLFile can't parse %q file", tfFilepath),
},
}
}
content, _, _ := parsedHCL.Body.PartialContent(terraformWithCoderParametersSchema)
content, _, _ := parsedTF.Body.PartialContent(terraformWithCoderParametersSchema)
for _, block := range content.Blocks {
if block.Type == "data" && block.Labels[0] == "coder_parameter" && len(block.Labels) == 2 {
coderParameterNames = append(coderParameterNames, block.Labels[1])
}
}
}
return coderParameterNames, nil
}

View File

@ -103,7 +103,7 @@ func loadEnabledFeatures(moduleDir string) (map[string]bool, hcl.Diagnostics) {
var found bool
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".tf") {
if !strings.HasSuffix(entry.Name(), ".tf") && !strings.HasSuffix(entry.Name(), ".tf.json") {
continue
}
@ -131,7 +131,12 @@ func parseFeatures(hclFilepath string) (map[string]bool, bool, hcl.Diagnostics)
}
parser := hclparse.NewParser()
parsedHCL, diags := parser.ParseHCLFile(hclFilepath)
var parsedHCL *hcl.File
if strings.HasSuffix(hclFilepath, ".tf.json") {
parsedHCL, diags = parser.ParseJSONFile(hclFilepath)
} else {
parsedHCL, diags = parser.ParseHCLFile(hclFilepath)
}
if diags.HasErrors() {
return flags, false, diags
}

View File

@ -254,6 +254,31 @@ func TestProvision(t *testing.T) {
},
Apply: true,
},
{
Name: "single-resource-json",
Files: map[string]string{
"main.tf.json": `{
"resource": {
"null_resource": {
"A": [
{}
]
}
}
}`,
},
Response: &proto.Provision_Response{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Resources: []*proto.Resource{{
Name: "A",
Type: "null_resource",
}},
},
},
},
Apply: true,
},
{
Name: "bad-syntax-1",
Files: map[string]string{
@ -349,6 +374,88 @@ func TestProvision(t *testing.T) {
},
},
},
{
Name: "rich-parameter-with-value-json",
Files: map[string]string{
"main.tf.json": `{
"data": {
"coder_parameter": {
"example": [
{
"default": "foobar",
"name": "Example",
"type": "string"
}
],
"sample": [
{
"default": "foobaz",
"name": "Sample",
"type": "string"
}
]
}
},
"resource": {
"null_resource": {
"example": [
{
"triggers": {
"misc": "${data.coder_parameter.example.value}"
}
}
]
}
},
"terraform": [
{
"required_providers": [
{
"coder": {
"source": "coder/coder",
"version": "0.6.20"
}
}
]
}
]
}`,
},
Request: &proto.Provision_Plan{
RichParameterValues: []*proto.RichParameterValue{
{
Name: "Example",
Value: "foobaz",
},
{
Name: "Sample",
Value: "foofoo",
},
},
},
Response: &proto.Provision_Response{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Parameters: []*proto.RichParameter{
{
Name: "Example",
Type: "string",
DefaultValue: "foobar",
},
{
Name: "Sample",
Type: "string",
DefaultValue: "foobaz",
},
},
Resources: []*proto.Resource{{
Name: "example",
Type: "null_resource",
}},
},
},
},
},
{
Name: "git-auth",
Files: map[string]string{

View File

@ -17,15 +17,17 @@ const (
TemplateArchiveLimit = 1 << 20
)
func dirHasExt(dir string, ext string) (bool, error) {
func dirHasExt(dir string, exts ...string) (bool, error) {
dirEnts, err := os.ReadDir(dir)
if err != nil {
return false, err
}
for _, fi := range dirEnts {
if strings.HasSuffix(fi.Name(), ext) {
return true, nil
for _, ext := range exts {
if strings.HasSuffix(fi.Name(), ext) {
return true, nil
}
}
}
@ -38,8 +40,8 @@ func Tar(w io.Writer, directory string, limit int64) error {
w = xio.NewLimitWriter(w, limit-1)
tarWriter := tar.NewWriter(w)
const tfExt = ".tf"
hasTf, err := dirHasExt(directory, tfExt)
tfExts := []string{".tf", ".tf.json"}
hasTf, err := dirHasExt(directory, tfExts...)
if err != nil {
return err
}
@ -53,7 +55,7 @@ func Tar(w io.Writer, directory string, limit int64) error {
// useless.
return xerrors.Errorf(
"%s is not a valid template since it has no %s files",
absPath, tfExt,
absPath, tfExts,
)
}

View File

@ -59,6 +59,15 @@ func TestTar(t *testing.T) {
err = provisionersdk.Tar(io.Discard, dir, 1024)
require.NoError(t, err)
})
t.Run("ValidJSON", func(t *testing.T) {
t.Parallel()
dir := t.TempDir()
file, err := os.CreateTemp(dir, "*.tf.json")
require.NoError(t, err)
_ = file.Close()
err = provisionersdk.Tar(io.Discard, dir, 1024)
require.NoError(t, err)
})
t.Run("HiddenFiles", func(t *testing.T) {
t.Parallel()
dir := t.TempDir()