Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: include template variables in dynamic parameter rendering#18819

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Emyrk merged 14 commits intomainfromstevenmasley/template_vars
Jul 21, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
14 commits
Select commitHold shift + click to select a range
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
30 changes: 29 additions & 1 deletioncoderd/coderdtest/dynamicparameters.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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) {
Expand All@@ -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
Expand All@@ -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)

Expand Down
31 changes: 27 additions & 4 deletionscoderd/dynamicparameters/render.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
Expand DownExpand Up@@ -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 &
Expand All@@ -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 {
Expand DownExpand Up@@ -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
}

Expand DownExpand Up@@ -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 {
Expand All@@ -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
}

Expand All@@ -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()
Expand DownExpand Up@@ -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.
Expand Down
65 changes: 65 additions & 0 deletionscoderd/dynamicparameters/variablevalues.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -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.
funcVariableValues(vals []database.TemplateVersionVariable) (map[string]cty.Value,error) {
ctyVals:=make(map[string]cty.Value,len(vals))
for_,v:=rangevals {
value:=v.Value
ifvalue==""&&v.DefaultValue!="" {
value=v.DefaultValue
}

ifvalue=="" {
// Empty strings are unsupported I guess?
continue// omit non-set vals
}

varerrerror
switchv.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)
Comment on lines +30 to +36
Copy link
MemberAuthor

Choose a reason for hiding this comment

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

This is not ideal, will resolve in another PR

case"number":
ctyVals[v.Name],err=cty.ParseNumberVal(value)
iferr!=nil {
returnnil,xerrors.Errorf("parse variable %q: %w",v.Name,err)
}
case"bool":
parsed,err:=strconv.ParseBool(value)
iferr!=nil {
returnnil,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))
iferr!=nil {
returnnil,xerrors.Errorf("implied type for variable %q: %w",v.Name,err)
}

jv,err:=json.Unmarshal([]byte(value),ty)
iferr!=nil {
returnnil,xerrors.Errorf("unmarshal variable %q: %w",v.Name,err)
}
ctyVals[v.Name]=jv
}
}

returnctyVals,nil
}
32 changes: 32 additions & 0 deletionscoderd/parameters_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 {
Expand All@@ -355,6 +385,7 @@ type setupDynamicParamsTestParams struct {

static []*proto.RichParameter
expectWebsocketError bool
variables []codersdk.TemplateVersionVariable
}

type dynamicParamsTest struct {
Expand All@@ -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)
Expand Down
13 changes: 11 additions & 2 deletionscoderd/templateversions.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"
Expand DownExpand Up@@ -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
}
Expand DownExpand Up@@ -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) {
Expand DownExpand Up@@ -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)
Copy link
Member

Choose a reason for hiding this comment

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

AFAICTVariableValue.Value will always be a string?

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

It is, which I just found strange. This is working for now, I will test complex behavior as I edit the source of this (the parse function)

}

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 {
Expand Down
30 changes: 30 additions & 0 deletionscoderd/testdata/parameters/variables/main.tf
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -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 changes: 6 additions & 0 deletionscoderd/wsbuilder/wsbuilder.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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)
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp