Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
58fc371
WIP
mtojek Feb 9, 2023
84f6fe5
hcl
mtojek Feb 9, 2023
b29ab11
useManagedVariables
mtojek Feb 9, 2023
2124c54
fix
mtojek Feb 9, 2023
9f89e17
Fix
mtojek Feb 9, 2023
fc583ae
Fix
mtojek Feb 9, 2023
86a1c1c
Merge branch 'main' into 5980-manage-temp-variables
mtojek Feb 9, 2023
183fdb7
fix
mtojek Feb 9, 2023
3e9dd7e
go:build
mtojek Feb 9, 2023
42f8d41
Fix
mtojek Feb 9, 2023
c194087
fix: bool flag
mtojek Feb 10, 2023
f84fbf6
Insert template variables
mtojek Feb 10, 2023
33e75d9
API
mtojek Feb 10, 2023
85bc72f
fix
mtojek Feb 10, 2023
99c5fde
Expose via API
mtojek Feb 10, 2023
e4ee7f9
More wiring
mtojek Feb 10, 2023
2a60174
CLI for testing purposes
mtojek Feb 10, 2023
ffb0b94
WIP
mtojek Feb 13, 2023
c0ba41b
Delete FIXME
mtojek Feb 13, 2023
9e23196
planVars
mtojek Feb 13, 2023
80fdf07
Merge branch 'main' into 5980-manage-temp-variables
mtojek Feb 13, 2023
3a510ea
WIP
mtojek Feb 13, 2023
72f76b1
WIP
mtojek Feb 13, 2023
78d7252
UserVariableValues
mtojek Feb 13, 2023
845dd92
no dry run
mtojek Feb 13, 2023
e021e52
Dry run
mtojek Feb 13, 2023
54dc685
Done FIXME
mtojek Feb 13, 2023
81490a2
Fix
mtojek Feb 14, 2023
6aa97ab
Fix: CLI
mtojek Feb 14, 2023
920bf7c
Merge branch 'main' into 5980-manage-temp-variables
mtojek Feb 14, 2023
56f08f9
Fix: migration
mtojek Feb 14, 2023
2c57c96
API tests
mtojek Feb 14, 2023
bc18624
Test info
mtojek Feb 14, 2023
ae6f072
Tests
mtojek Feb 14, 2023
f9b4349
More tests
mtojek Feb 14, 2023
af4adec
fix: lint
mtojek Feb 14, 2023
864052c
Merge branch 'main' into 5980-manage-temp-variables
mtojek Feb 14, 2023
4bac517
Fix: authz
mtojek Feb 14, 2023
930624a
Merge branch 'main' into 5980-manage-temp-variables
mtojek Feb 14, 2023
23226fa
Address PR comments
mtojek Feb 15, 2023
ca23476
Fix
mtojek Feb 15, 2023
953b9a7
Merge branch 'main' into 5980-manage-temp-variables
mtojek Feb 15, 2023
bb8b500
fix
mtojek Feb 15, 2023
78ac8f5
fix
mtojek Feb 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
hcl
  • Loading branch information
mtojek committed Feb 9, 2023
commit 84f6fe5d53e5ef962aff92a9fe008a79aafda232
117 changes: 108 additions & 9 deletions provisioner/terraform/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,41 @@ import (
"encoding/json"
"fmt"
"os"
"path"
"path/filepath"
"sort"
"strconv"
"strings"

"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
"github.com/hashicorp/hcl/v2/hclparse"
"github.com/hashicorp/terraform-config-inspect/tfconfig"
"github.com/mitchellh/go-wordwrap"
"golang.org/x/xerrors"

"github.com/coder/coder/provisionersdk/proto"
)

const featureUseManagedVariables = "feature_use_managed_variables"

var terraformWithFeaturesSchema = &hcl.BodySchema{
Blocks: []hcl.BlockHeaderSchema{
{
Type: "provider",
LabelNames: []string{"type"},
},
},
}

var providerFeaturesConfigSchema = &hcl.BodySchema{
Attributes: []hcl.AttributeSchema{
{
Name: featureUseManagedVariables,
},
},
}

// Parse extracts Terraform variables from source-code.
func (*server) Parse(request *proto.Parse_Request, stream proto.DRPCProvisioner_ParseStream) error {
// Load the module and print any parse errors.
Expand All @@ -23,7 +47,13 @@ func (*server) Parse(request *proto.Parse_Request, stream proto.DRPCProvisioner_
return xerrors.Errorf("load module: %s", formatDiagnostics(request.Directory, diags))
}

fmt.Println(module.ProviderConfigs["coder"].Name)
flags, flagsDiags, err := loadEnabledFeatures(request.Directory)
if flagsDiags.HasErrors() {
return xerrors.Errorf("load coder provider features: %s", formatDiagnostics(request.Directory, diags))
}
if err != nil {
return xerrors.Errorf("load coder provider features: %w", err)
}

// Sort variables by (filename, line) to make the ordering consistent
variables := make([]*tfconfig.Variable, 0, len(module.Variables))
Expand All @@ -34,25 +64,69 @@ func (*server) Parse(request *proto.Parse_Request, stream proto.DRPCProvisioner_
return compareSourcePos(variables[i].Pos, variables[j].Pos)
})

parameters := make([]*proto.ParameterSchema, 0, len(variables))
for _, v := range variables {
schema, err := convertVariableToParameter(v)
if err != nil {
return xerrors.Errorf("convert variable %q: %w", v.Name, err)
var parameters []*proto.ParameterSchema
var templateVariables []*proto.TemplateVariable

useManagedVariables := flags[featureUseManagedVariables]
if useManagedVariables {
for _, v := range variables {
mv, err := convertTerraformVariableToManagedVariable(v)
if err != nil {
return xerrors.Errorf("can't convert the Terraform variable to a managed one: %w", err)
}
templateVariables = append(templateVariables, mv)
}
} else {
for _, v := range variables {
schema, err := convertVariableToParameter(v)
if err != nil {
return xerrors.Errorf("convert variable %q: %w", v.Name, err)
}

parameters = append(parameters, schema)
parameters = append(parameters, schema)
}
}

return stream.Send(&proto.Parse_Response{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
ParameterSchemas: parameters,
ParameterSchemas: parameters,
TemplateVariables: templateVariables,
},
},
})
}

func loadEnabledFeatures(moduleDir string) (map[string]bool, hcl.Diagnostics, error) {
parser := hclparse.NewParser()
mainFile, err := parser.ParseHCLFile(path.Join(moduleDir, "main.tf"))
if err != nil {
return nil, nil, xerrors.Errorf("can't parse main.tf file: %w", err)
}

flags := map[string]bool{}
var diags hcl.Diagnostics

content, _ := mainFile.Body.Content(terraformWithFeaturesSchema)
for _, block := range content.Blocks {
if block.Type == "provider" && block.Labels[0] == "coder" {
content, _, partialDiags := block.Body.PartialContent(providerFeaturesConfigSchema)
diags = append(diags, partialDiags...)
if attr, defined := content.Attributes[featureUseManagedVariables]; defined {
var useManagedVariables string
partialDiags := gohcl.DecodeExpression(attr.Expr, nil, &useManagedVariables)
diags = append(diags, partialDiags...)

b, err := strconv.ParseBool(useManagedVariables)
if err != nil {
return nil, nil, xerrors.Errorf("can't parse %s flag as boolean: %w", featureUseManagedVariables, err)
}
flags[featureUseManagedVariables] = b
}
}
}
return flags, diags, nil
}

// Converts a Terraform variable to a provisioner parameter.
func convertVariableToParameter(variable *tfconfig.Variable) (*proto.ParameterSchema, error) {
schema := &proto.ParameterSchema{
Expand Down Expand Up @@ -98,6 +172,31 @@ func convertVariableToParameter(variable *tfconfig.Variable) (*proto.ParameterSc
return schema, nil
}

// Converts a Terraform variable to a managed variable.
func convertTerraformVariableToManagedVariable(variable *tfconfig.Variable) (*proto.TemplateVariable, error) {
var defaultData string
if variable.Default != nil {
var valid bool
defaultData, valid = variable.Default.(string)
if !valid {
defaultDataRaw, err := json.Marshal(variable.Default)
if err != nil {
return nil, xerrors.Errorf("parse variable %q default: %w", variable.Name, err)
}
defaultData = string(defaultDataRaw)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what variable.Default looks like, so wondering if the idea with json marshaling here is to turn a string value like hello into "hello"? Or maybe the purpose is entirely different 😄

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to Terraform, the variable can have simple types like strings... but also lists, arrays, etc. hence marshaling. Anyway, the idea is not new but borrowed from convertVariableToParameter.

}
}

return &proto.TemplateVariable{
Name: variable.Name,
Description: variable.Description,
Type: variable.Type,
DefaultValue: defaultData,
Required: variable.Required,
Sensitive: variable.Sensitive,
}, nil
}

// formatDiagnostics returns a nicely formatted string containing all of the
// error details within the tfconfig.Diagnostics. We need to use this because
// the default format doesn't provide much useful information.
Expand Down
24 changes: 14 additions & 10 deletions provisioner/terraform/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,28 +164,32 @@ func TestParse(t *testing.T) {
Files: map[string]string{
"main.tf": `variable "A" {
description = "Testing!"
type = "string"
default = "abc"
required = true
sensitive = true
}

provider "coder" {
enable_managed_variables = "true"
feature_use_managed_variables = true
}`,
},
Response: &proto.Parse_Response{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
ParameterSchemas: []*proto.ParameterSchema{{
Name: "A",
RedisplayValue: true,
AllowOverrideSource: true,
Description: "Testing!",
DefaultDestination: &proto.ParameterDestination{
Scheme: proto.ParameterDestination_PROVISIONER_VARIABLE,
TemplateVariables: []*proto.TemplateVariable{
{
Name: "A",
Description: "Testing!",
Type: "string",
DefaultValue: "abc",
Required: false,
Sensitive: true,
},
}},
},
},
},
},
ErrorContains: "blah",
},
}

Expand Down