feat: include template variables in dynamic parameter rendering (#18819)

Closes https://github.com/coder/coder/issues/18671

Template variables now loaded into dynamic parameters.
This commit is contained in:
Steven Masley
2025-07-21 13:02:31 -05:00
committed by GitHub
parent 40a6367d4b
commit aedc019b4e
11 changed files with 327 additions and 79 deletions
+29 -1
View File
@@ -29,7 +29,8 @@ type DynamicParameterTemplateParams struct {
// TemplateID is used to update an existing template instead of creating a new one.
TemplateID uuid.UUID
Version func(request *codersdk.CreateTemplateVersionRequest)
Version func(request *codersdk.CreateTemplateVersionRequest)
Variables []codersdk.TemplateVersionVariable
}
func DynamicParameterTemplate(t *testing.T, client *codersdk.Client, org uuid.UUID, args DynamicParameterTemplateParams) (codersdk.Template, codersdk.TemplateVersion) {
@@ -48,6 +49,32 @@ func DynamicParameterTemplate(t *testing.T, client *codersdk.Client, org uuid.UU
},
}}
userVars := make([]codersdk.VariableValue, 0, len(args.Variables))
parseVars := make([]*proto.TemplateVariable, 0, len(args.Variables))
for _, argv := range args.Variables {
parseVars = append(parseVars, &proto.TemplateVariable{
Name: argv.Name,
Description: argv.Description,
Type: argv.Type,
DefaultValue: argv.DefaultValue,
Required: argv.Required,
Sensitive: argv.Sensitive,
})
userVars = append(userVars, codersdk.VariableValue{
Name: argv.Name,
Value: argv.Value,
})
}
files.Parse = []*proto.Response{{
Type: &proto.Response_Parse{
Parse: &proto.ParseComplete{
TemplateVariables: parseVars,
},
},
}}
mime := codersdk.ContentTypeTar
if args.Zip {
mime = codersdk.ContentTypeZip
@@ -59,6 +86,7 @@ func DynamicParameterTemplate(t *testing.T, client *codersdk.Client, org uuid.UU
if args.Version != nil {
args.Version(request)
}
request.UserVariableValues = userVars
})
AwaitTemplateVersionJobCompleted(t, client, version.ID)
+27 -4
View File
@@ -9,6 +9,7 @@ import (
"time"
"github.com/google/uuid"
"github.com/zclconf/go-cty/cty"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/apiversion"
@@ -41,9 +42,10 @@ type loader struct {
templateVersionID uuid.UUID
// cache of objects
templateVersion *database.TemplateVersion
job *database.ProvisionerJob
terraformValues *database.TemplateVersionTerraformValue
templateVersion *database.TemplateVersion
job *database.ProvisionerJob
terraformValues *database.TemplateVersionTerraformValue
templateVariableValues *[]database.TemplateVersionVariable
}
// Prepare is the entrypoint for this package. It loads the necessary objects &
@@ -61,6 +63,12 @@ func Prepare(ctx context.Context, db database.Store, cache files.FileAcquirer, v
return l.Renderer(ctx, db, cache)
}
func WithTemplateVariableValues(vals []database.TemplateVersionVariable) func(r *loader) {
return func(r *loader) {
r.templateVariableValues = &vals
}
}
func WithTemplateVersion(tv database.TemplateVersion) func(r *loader) {
return func(r *loader) {
if tv.ID == r.templateVersionID {
@@ -127,6 +135,14 @@ func (r *loader) loadData(ctx context.Context, db database.Store) error {
r.terraformValues = &values
}
if r.templateVariableValues == nil {
vals, err := db.GetTemplateVersionVariables(ctx, r.templateVersion.ID)
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
return xerrors.Errorf("template version variables: %w", err)
}
r.templateVariableValues = &vals
}
return nil
}
@@ -160,13 +176,17 @@ func (r *loader) dynamicRenderer(ctx context.Context, db database.Store, cache *
}
}()
tfVarValues, err := VariableValues(*r.templateVariableValues)
if err != nil {
return nil, xerrors.Errorf("parse variable values: %w", err)
}
// If they can read the template version, then they can read the file for
// parameter loading purposes.
//nolint:gocritic
fileCtx := dbauthz.AsFileReader(ctx)
var templateFS fs.FS
var err error
templateFS, err = cache.Acquire(fileCtx, db, r.job.FileID)
if err != nil {
@@ -189,6 +209,7 @@ func (r *loader) dynamicRenderer(ctx context.Context, db database.Store, cache *
db: db,
ownerErrors: make(map[uuid.UUID]error),
close: cache.Close,
tfvarValues: tfVarValues,
}, nil
}
@@ -199,6 +220,7 @@ type dynamicRenderer struct {
ownerErrors map[uuid.UUID]error
currentOwner *previewtypes.WorkspaceOwner
tfvarValues map[string]cty.Value
once sync.Once
close func()
@@ -229,6 +251,7 @@ func (r *dynamicRenderer) Render(ctx context.Context, ownerID uuid.UUID, values
PlanJSON: r.data.terraformValues.CachedPlan,
ParameterValues: values,
Owner: *r.currentOwner,
TFVars: r.tfvarValues,
// Do not emit parser logs to coderd output logs.
// TODO: Returning this logs in the output would benefit the caller.
// Unsure how large the logs can be, so for now we just discard them.
@@ -0,0 +1,65 @@
package dynamicparameters
import (
"strconv"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/json"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/database"
)
// VariableValues is a helper function that converts a slice of TemplateVersionVariable
// into a map of cty.Value for use in coder/preview.
func VariableValues(vals []database.TemplateVersionVariable) (map[string]cty.Value, error) {
ctyVals := make(map[string]cty.Value, len(vals))
for _, v := range vals {
value := v.Value
if value == "" && v.DefaultValue != "" {
value = v.DefaultValue
}
if value == "" {
// Empty strings are unsupported I guess?
continue // omit non-set vals
}
var err error
switch v.Type {
// Defaulting the empty type to "string"
// TODO: This does not match the terraform behavior, however it is too late
// at this point in the code to determine this, as the database type stores all values
// as strings. The code needs to be fixed in the `Parse` step of the provisioner.
// That step should determine the type of the variable correctly and store it in the database.
case "string", "":
ctyVals[v.Name] = cty.StringVal(value)
case "number":
ctyVals[v.Name], err = cty.ParseNumberVal(value)
if err != nil {
return nil, xerrors.Errorf("parse variable %q: %w", v.Name, err)
}
case "bool":
parsed, err := strconv.ParseBool(value)
if err != nil {
return nil, xerrors.Errorf("parse variable %q: %w", v.Name, err)
}
ctyVals[v.Name] = cty.BoolVal(parsed)
default:
// If it is a complex type, let the cty json code give it a try.
// TODO: Ideally we parse `list` & `map` and build the type ourselves.
ty, err := json.ImpliedType([]byte(value))
if err != nil {
return nil, xerrors.Errorf("implied type for variable %q: %w", v.Name, err)
}
jv, err := json.Unmarshal([]byte(value), ty)
if err != nil {
return nil, xerrors.Errorf("unmarshal variable %q: %w", v.Name, err)
}
ctyVals[v.Name] = jv
}
}
return ctyVals, nil
}
+32
View File
@@ -343,6 +343,36 @@ func TestDynamicParametersWithTerraformValues(t *testing.T) {
require.Len(t, preview.Diagnostics, 1)
require.Equal(t, preview.Diagnostics[0].Extra.Code, "owner_not_found")
})
t.Run("TemplateVariables", func(t *testing.T) {
t.Parallel()
dynamicParametersTerraformSource, err := os.ReadFile("testdata/parameters/variables/main.tf")
require.NoError(t, err)
setup := setupDynamicParamsTest(t, setupDynamicParamsTestParams{
provisionerDaemonVersion: provProto.CurrentVersion.String(),
mainTF: dynamicParametersTerraformSource,
variables: []codersdk.TemplateVersionVariable{
{Name: "one", Value: "austin", DefaultValue: "alice", Type: "string"},
},
plan: nil,
static: nil,
})
ctx := testutil.Context(t, testutil.WaitShort)
stream := setup.stream
previews := stream.Chan()
// Should see the output of the module represented
preview := testutil.RequireReceive(ctx, t, previews)
require.Equal(t, -1, preview.ID)
require.Empty(t, preview.Diagnostics)
require.Len(t, preview.Parameters, 1)
coderdtest.AssertParameter(t, "variable_values", preview.Parameters).
Exists().Value("austin")
})
}
type setupDynamicParamsTestParams struct {
@@ -355,6 +385,7 @@ type setupDynamicParamsTestParams struct {
static []*proto.RichParameter
expectWebsocketError bool
variables []codersdk.TemplateVersionVariable
}
type dynamicParamsTest struct {
@@ -380,6 +411,7 @@ func setupDynamicParamsTest(t *testing.T, args setupDynamicParamsTestParams) dyn
Plan: args.plan,
ModulesArchive: args.modulesArchive,
StaticParams: args.static,
Variables: args.variables,
})
ctx := testutil.Context(t, testutil.WaitShort)
+11 -2
View File
@@ -18,6 +18,7 @@ import (
"github.com/google/uuid"
"github.com/moby/moby/pkg/namesgenerator"
"github.com/sqlc-dev/pqtype"
"github.com/zclconf/go-cty/cty"
"golang.org/x/xerrors"
"cdr.dev/slog"
@@ -1585,7 +1586,7 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht
var parsedTags map[string]string
var ok bool
if dynamicTemplate {
parsedTags, ok = api.dynamicTemplateVersionTags(ctx, rw, organization.ID, apiKey.UserID, file)
parsedTags, ok = api.dynamicTemplateVersionTags(ctx, rw, organization.ID, apiKey.UserID, file, req.UserVariableValues)
if !ok {
return
}
@@ -1762,7 +1763,7 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht
warnings))
}
func (api *API) dynamicTemplateVersionTags(ctx context.Context, rw http.ResponseWriter, orgID uuid.UUID, owner uuid.UUID, file database.File) (map[string]string, bool) {
func (api *API) dynamicTemplateVersionTags(ctx context.Context, rw http.ResponseWriter, orgID uuid.UUID, owner uuid.UUID, file database.File, templateVariables []codersdk.VariableValue) (map[string]string, bool) {
ownerData, err := dynamicparameters.WorkspaceOwner(ctx, api.Database, orgID, owner)
if err != nil {
if httpapi.Is404Error(err) {
@@ -1800,11 +1801,19 @@ func (api *API) dynamicTemplateVersionTags(ctx context.Context, rw http.Response
return nil, false
}
// Pass in any manually specified template variables as TFVars.
// TODO: Does this break if the type is not a string?
tfVarValues := make(map[string]cty.Value)
for _, variable := range templateVariables {
tfVarValues[variable.Name] = cty.StringVal(variable.Value)
}
output, diags := preview.Preview(ctx, preview.Input{
PlanJSON: nil, // Template versions are before `terraform plan`
ParameterValues: nil, // No user-specified parameters
Owner: *ownerData,
Logger: stdslog.New(stdslog.DiscardHandler),
TFVars: tfVarValues,
}, files)
tagErr := dynamicparameters.CheckTags(output, diags)
if tagErr != nil {
+30
View File
@@ -0,0 +1,30 @@
// Base case for workspace tags + parameters.
terraform {
required_providers {
coder = {
source = "coder/coder"
}
docker = {
source = "kreuzwerker/docker"
version = "3.0.2"
}
}
}
variable "one" {
default = "alice"
type = string
}
data "coder_parameter" "variable_values" {
name = "variable_values"
description = "Just to show the variable values"
type = "string"
default = var.one
option {
name = "one"
value = var.one
}
}
+6
View File
@@ -633,10 +633,16 @@ func (b *Builder) getDynamicParameterRenderer() (dynamicparameters.Renderer, err
return nil, xerrors.Errorf("get template version terraform values: %w", err)
}
variableValues, err := b.getTemplateVersionVariables()
if err != nil {
return nil, xerrors.Errorf("get template version variables: %w", err)
}
renderer, err := dynamicparameters.Prepare(b.ctx, b.store, b.fileCache, tv.ID,
dynamicparameters.WithTemplateVersion(*tv),
dynamicparameters.WithProvisionerJob(*job),
dynamicparameters.WithTerraformValues(*tfVals),
dynamicparameters.WithTemplateVariableValues(variableValues),
)
if err != nil {
return nil, xerrors.Errorf("get template version renderer: %w", err)
+120 -69
View File
@@ -2627,6 +2627,21 @@ func TestWorkspaceTemplateParamsChange(t *testing.T) {
require.Equal(t, codersdk.WorkspaceStatusDeleted, build.Status)
}
type testWorkspaceTagsTerraformCase struct {
name string
// tags to apply to the external provisioner
provisionerTags map[string]string
// tags to apply to the create template version request
createTemplateVersionRequestTags map[string]string
// the coder_workspace_tags bit of main.tf.
// you can add more stuff here if you need
tfWorkspaceTags string
templateImportUserVariableValues []codersdk.VariableValue
// if we need to set parameters on workspace build
workspaceBuildParameters []codersdk.WorkspaceBuildParameter
skipCreateWorkspace bool
}
// TestWorkspaceTagsTerraform tests that a workspace can be created with tags.
// This is an end-to-end-style test, meaning that we actually run the
// real Terraform provisioner and validate that the workspace is created
@@ -2636,7 +2651,7 @@ func TestWorkspaceTemplateParamsChange(t *testing.T) {
// config file so that we only reference those
// nolint:paralleltest // t.Setenv
func TestWorkspaceTagsTerraform(t *testing.T) {
mainTfTemplate := `
coderProviderTemplate := `
terraform {
required_providers {
coder = {
@@ -2644,33 +2659,11 @@ func TestWorkspaceTagsTerraform(t *testing.T) {
}
}
}
provider "coder" {}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
data "coder_parameter" "unrelated" {
name = "unrelated"
type = "list(string)"
default = jsonencode(["a", "b"])
}
%s
`
tfCliConfigPath := downloadProviders(t, fmt.Sprintf(mainTfTemplate, ""))
tfCliConfigPath := downloadProviders(t, coderProviderTemplate)
t.Setenv("TF_CLI_CONFIG_FILE", tfCliConfigPath)
for _, tc := range []struct {
name string
// tags to apply to the external provisioner
provisionerTags map[string]string
// tags to apply to the create template version request
createTemplateVersionRequestTags map[string]string
// the coder_workspace_tags bit of main.tf.
// you can add more stuff here if you need
tfWorkspaceTags string
templateImportUserVariableValues []codersdk.VariableValue
// if we need to set parameters on workspace build
workspaceBuildParameters []codersdk.WorkspaceBuildParameter
skipCreateWorkspace bool
}{
for _, tc := range []testWorkspaceTagsTerraformCase{
{
name: "no tags",
tfWorkspaceTags: ``,
@@ -2803,56 +2796,114 @@ func TestWorkspaceTagsTerraform(t *testing.T) {
},
} {
t.Run(tc.name, func(t *testing.T) {
client, owner := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
// We intentionally do not run a built-in provisioner daemon here.
IncludeProvisionerDaemon: false,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureExternalProvisionerDaemons: 1,
},
},
t.Run("dynamic", func(t *testing.T) {
workspaceTagsTerraform(t, tc, true)
})
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
_ = coderdenttest.NewExternalProvisionerDaemonTerraform(t, client, owner.OrganizationID, tc.provisionerTags)
// This can take a while, so set a relatively long timeout.
ctx := testutil.Context(t, 2*testutil.WaitSuperLong)
// Creating a template as a template admin must succeed
templateFiles := map[string]string{"main.tf": fmt.Sprintf(mainTfTemplate, tc.tfWorkspaceTags)}
tarBytes := testutil.CreateTar(t, templateFiles)
fi, err := templateAdmin.Upload(ctx, "application/x-tar", bytes.NewReader(tarBytes))
require.NoError(t, err, "failed to upload file")
tv, err := templateAdmin.CreateTemplateVersion(ctx, owner.OrganizationID, codersdk.CreateTemplateVersionRequest{
Name: testutil.GetRandomName(t),
FileID: fi.ID,
StorageMethod: codersdk.ProvisionerStorageMethodFile,
Provisioner: codersdk.ProvisionerTypeTerraform,
ProvisionerTags: tc.createTemplateVersionRequestTags,
UserVariableValues: tc.templateImportUserVariableValues,
// classic uses tfparse for tags. This sub test can be
// removed when tf parse is removed.
t.Run("classic", func(t *testing.T) {
workspaceTagsTerraform(t, tc, false)
})
require.NoError(t, err, "failed to create template version")
coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, tv.ID)
tpl := coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, tv.ID)
if !tc.skipCreateWorkspace {
// Creating a workspace as a non-privileged user must succeed
ws, err := member.CreateUserWorkspace(ctx, memberUser.Username, codersdk.CreateWorkspaceRequest{
TemplateID: tpl.ID,
Name: coderdtest.RandomUsername(t),
RichParameterValues: tc.workspaceBuildParameters,
})
require.NoError(t, err, "failed to create workspace")
coderdtest.AwaitWorkspaceBuildJobCompleted(t, member, ws.LatestBuild.ID)
}
})
}
}
func workspaceTagsTerraform(t *testing.T, tc testWorkspaceTagsTerraformCase, dynamic bool) {
mainTfTemplate := `
terraform {
required_providers {
coder = {
source = "coder/coder"
}
}
}
provider "coder" {}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
data "coder_parameter" "unrelated" {
name = "unrelated"
type = "list(string)"
default = jsonencode(["a", "b"])
}
%s
`
client, owner := coderdenttest.New(t, &coderdenttest.Options{
Options: &coderdtest.Options{
// We intentionally do not run a built-in provisioner daemon here.
IncludeProvisionerDaemon: false,
},
LicenseOptions: &coderdenttest.LicenseOptions{
Features: license.Features{
codersdk.FeatureExternalProvisionerDaemons: 1,
},
},
})
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
member, memberUser := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
// This can take a while, so set a relatively long timeout.
ctx := testutil.Context(t, 2*testutil.WaitSuperLong)
emptyTar := testutil.CreateTar(t, map[string]string{"main.tf": ""})
emptyFi, err := templateAdmin.Upload(ctx, "application/x-tar", bytes.NewReader(emptyTar))
require.NoError(t, err)
// This template version does not need to succeed in being created.
// It will be in pending forever. We just need it to create a template.
emptyTv, err := templateAdmin.CreateTemplateVersion(ctx, owner.OrganizationID, codersdk.CreateTemplateVersionRequest{
Name: testutil.GetRandomName(t),
FileID: emptyFi.ID,
StorageMethod: codersdk.ProvisionerStorageMethodFile,
Provisioner: codersdk.ProvisionerTypeTerraform,
})
require.NoError(t, err)
tpl := coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, emptyTv.ID, func(request *codersdk.CreateTemplateRequest) {
request.UseClassicParameterFlow = ptr.Ref(!dynamic)
})
// The provisioner for the next template version
_ = coderdenttest.NewExternalProvisionerDaemonTerraform(t, client, owner.OrganizationID, tc.provisionerTags)
// Creating a template as a template admin must succeed
templateFiles := map[string]string{"main.tf": fmt.Sprintf(mainTfTemplate, tc.tfWorkspaceTags)}
tarBytes := testutil.CreateTar(t, templateFiles)
fi, err := templateAdmin.Upload(ctx, "application/x-tar", bytes.NewReader(tarBytes))
require.NoError(t, err, "failed to upload file")
tv, err := templateAdmin.CreateTemplateVersion(ctx, owner.OrganizationID, codersdk.CreateTemplateVersionRequest{
Name: testutil.GetRandomName(t),
FileID: fi.ID,
StorageMethod: codersdk.ProvisionerStorageMethodFile,
Provisioner: codersdk.ProvisionerTypeTerraform,
ProvisionerTags: tc.createTemplateVersionRequestTags,
UserVariableValues: tc.templateImportUserVariableValues,
TemplateID: tpl.ID,
})
require.NoError(t, err, "failed to create template version")
coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, tv.ID)
err = templateAdmin.UpdateActiveTemplateVersion(ctx, tpl.ID, codersdk.UpdateActiveTemplateVersion{
ID: tv.ID,
})
require.NoError(t, err, "set to active template version")
if !tc.skipCreateWorkspace {
// Creating a workspace as a non-privileged user must succeed
ws, err := member.CreateUserWorkspace(ctx, memberUser.Username, codersdk.CreateWorkspaceRequest{
TemplateID: tpl.ID,
Name: coderdtest.RandomUsername(t),
RichParameterValues: tc.workspaceBuildParameters,
})
require.NoError(t, err, "failed to create workspace")
tagJSON, _ := json.Marshal(ws.LatestBuild.Job.Tags)
t.Logf("Created workspace build [%s] with tags: %s", ws.LatestBuild.Job.Type, tagJSON)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, member, ws.LatestBuild.ID)
}
}
// downloadProviders is a test helper that creates a temporary file and writes a
// terraform CLI config file with a provider_installation stanza for coder/coder
// using dev_overrides. It also fetches the latest provider release from GitHub
@@ -3124,7 +3175,7 @@ func TestWorkspaceLock(t *testing.T) {
require.NotNil(t, workspace.DeletingAt)
require.NotNil(t, workspace.DormantAt)
require.Equal(t, workspace.DormantAt.Add(dormantTTL), *workspace.DeletingAt)
require.WithinRange(t, *workspace.DormantAt, time.Now().Add(-time.Second*10), time.Now())
require.WithinRange(t, *workspace.DormantAt, time.Now().Add(-time.Second), time.Now())
// Locking a workspace shouldn't update the last_used_at.
require.Equal(t, lastUsedAt, workspace.LastUsedAt)
+1 -1
View File
@@ -482,7 +482,7 @@ require (
require (
github.com/coder/agentapi-sdk-go v0.0.0-20250505131810-560d1d88d225
github.com/coder/aisdk-go v0.0.9
github.com/coder/preview v1.0.3-0.20250701142654-c3d6e86b9393
github.com/coder/preview v1.0.3-0.20250714153828-a737d4750448
github.com/fsnotify/fsnotify v1.9.0
github.com/mark3labs/mcp-go v0.34.0
)
+2 -2
View File
@@ -916,8 +916,8 @@ github.com/coder/pq v1.10.5-0.20250630052411-a259f96b6102 h1:ahTJlTRmTogsubgRVGO
github.com/coder/pq v1.10.5-0.20250630052411-a259f96b6102/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0 h1:3A0ES21Ke+FxEM8CXx9n47SZOKOpgSE1bbJzlE4qPVs=
github.com/coder/pretty v0.0.0-20230908205945-e89ba86370e0/go.mod h1:5UuS2Ts+nTToAMeOjNlnHFkPahrtDkmpydBen/3wgZc=
github.com/coder/preview v1.0.3-0.20250701142654-c3d6e86b9393 h1:l+m2liikn8JoEv6C22QIV4qseolUfvNsyUNA6JJsD6Y=
github.com/coder/preview v1.0.3-0.20250701142654-c3d6e86b9393/go.mod h1:efDWGlO/PZPrvdt5QiDhMtTUTkPxejXo9c0wmYYLLjM=
github.com/coder/preview v1.0.3-0.20250714153828-a737d4750448 h1:S86sFp4Dr4dUn++fXOMOTu6ClnEZ/NrGCYv7bxZjYYc=
github.com/coder/preview v1.0.3-0.20250714153828-a737d4750448/go.mod h1:hQtBEqOFMJ3SHl9Q9pVvDA9CpeCEXBwbONNK29+3MLk=
github.com/coder/quartz v0.2.1 h1:QgQ2Vc1+mvzewg2uD/nj8MJ9p9gE+QhGJm+Z+NGnrSE=
github.com/coder/quartz v0.2.1/go.mod h1:vsiCc+AHViMKH2CQpGIpFgdHIEQsxwm8yCscqKmzbRA=
github.com/coder/retry v1.5.1 h1:iWu8YnD8YqHs3XwqrqsjoBTAVqT9ml6z9ViJ2wlMiqc=
+4
View File
@@ -15,6 +15,10 @@ import (
)
// Parse extracts Terraform variables from source-code.
// TODO: This Parse is incomplete. It uses tfparse instead of terraform.
// The inputs are incomplete, as values such as the user context, parameters,
// etc are all important to the parsing process. This should be replaced with
// preview and have all inputs.
func (s *server) Parse(sess *provisionersdk.Session, _ *proto.ParseRequest, _ <-chan struct{}) *proto.ParseComplete {
ctx := sess.Context()
_, span := s.startTrace(ctx, tracing.FuncName())