mirror of https://github.com/coder/coder.git
feat(provisioner): add support for .tf.json templates (#7835)
Co-authored-by: Colin Adler <colin1adler@gmail.com>
This commit is contained in:
parent
f0c5201617
commit
52ead3d933
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue