feat: implement dynamic parameter validation (#18482)

# What does this do?

This does parameter validation for dynamic parameters in `wsbuilder`. All input parameters are validated in `coder/coder` before being sent to terraform.

The heart of this PR is [`ResolveParameters`](https://github.com/coder/coder/blob/b65001e89c0577199a8e470c138c51e91cf2350c/coderd/dynamicparameters/resolver.go#L30-L30).

# What else changes?

`wsbuilder` now needs to load the terraform files into memory to succeed. This does add a larger memory requirement to workspace builds.

# Future work

- Sort autostart handling workspaces by template version id. So workspaces with the same template version only load the terraform files once from the db, and store them in the cache.
This commit is contained in:
Steven Masley
2025-06-23 12:35:15 -05:00
committed by GitHub
parent 7254c08af4
commit 82af2e019d
23 changed files with 961 additions and 159 deletions
+162 -72
View File
@@ -13,8 +13,8 @@ import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/coder/coder/v2/apiversion"
"github.com/coder/coder/v2/coderd/dynamicparameters"
"github.com/coder/coder/v2/coderd/files"
"github.com/coder/coder/v2/coderd/rbac/policy"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/provisioner/terraform/tfparse"
@@ -65,8 +65,9 @@ type Builder struct {
templateVersionPresetID uuid.UUID
// used during build, makes function arguments less verbose
ctx context.Context
store database.Store
ctx context.Context
store database.Store
fileCache *files.CacheCloser
// cache of objects, so we only fetch once
template *database.Template
@@ -82,7 +83,8 @@ type Builder struct {
lastBuildJob *database.ProvisionerJob
parameterNames *[]string
parameterValues *[]string
templateVersionPresetParameterValues []database.TemplateVersionPresetParameter
templateVersionPresetParameterValues *[]database.TemplateVersionPresetParameter
parameterRender dynamicparameters.Renderer
prebuiltWorkspaceBuildStage sdkproto.PrebuiltWorkspaceBuildStage
verifyNoLegacyParametersOnce bool
@@ -258,6 +260,7 @@ func (e BuildError) Unwrap() error {
func (b *Builder) Build(
ctx context.Context,
store database.Store,
fileCache *files.Cache,
authFunc func(action policy.Action, object rbac.Objecter) bool,
auditBaggage audit.WorkspaceBuildBaggage,
) (
@@ -269,6 +272,10 @@ func (b *Builder) Build(
return nil, nil, nil, xerrors.Errorf("create audit baggage: %w", err)
}
b.fileCache = files.NewCacheCloser(fileCache)
// Always close opened files during the build
defer b.fileCache.Close()
// Run the build in a transaction with RepeatableRead isolation, and retries.
// RepeatableRead isolation ensures that we get a consistent view of the database while
// computing the new build. This simplifies the logic so that we do not need to worry if
@@ -592,10 +599,54 @@ func (b *Builder) getTemplateTerraformValues() (*database.TemplateVersionTerrafo
}
vals, err := b.store.GetTemplateVersionTerraformValues(b.ctx, v.ID)
if err != nil {
return nil, xerrors.Errorf("get template version terraform values %s: %w", v.JobID, err)
if !xerrors.Is(err, sql.ErrNoRows) {
return nil, xerrors.Errorf("builder get template version terraform values %s: %w", v.JobID, err)
}
// Old versions do not have terraform values, so we can ignore ErrNoRows and use an empty value.
vals = database.TemplateVersionTerraformValue{
TemplateVersionID: v.ID,
UpdatedAt: time.Time{},
CachedPlan: nil,
CachedModuleFiles: uuid.NullUUID{},
ProvisionerdVersion: "",
}
}
b.terraformValues = &vals
return b.terraformValues, err
return b.terraformValues, nil
}
func (b *Builder) getDynamicParameterRenderer() (dynamicparameters.Renderer, error) {
if b.parameterRender != nil {
return b.parameterRender, nil
}
tv, err := b.getTemplateVersion()
if err != nil {
return nil, xerrors.Errorf("get template version to get parameters: %w", err)
}
job, err := b.getTemplateVersionJob()
if err != nil {
return nil, xerrors.Errorf("get template version job to get parameters: %w", err)
}
tfVals, err := b.getTemplateTerraformValues()
if err != nil {
return nil, xerrors.Errorf("get template version terraform values: %w", err)
}
renderer, err := dynamicparameters.Prepare(b.ctx, b.store, b.fileCache, tv.ID,
dynamicparameters.WithTemplateVersion(*tv),
dynamicparameters.WithProvisionerJob(*job),
dynamicparameters.WithTerraformValues(*tfVals),
)
if err != nil {
return nil, xerrors.Errorf("get template version renderer: %w", err)
}
b.parameterRender = renderer
return renderer, nil
}
func (b *Builder) getLastBuild() (*database.WorkspaceBuild, error) {
@@ -617,6 +668,19 @@ func (b *Builder) getLastBuild() (*database.WorkspaceBuild, error) {
return b.lastBuild, nil
}
// firstBuild returns true if this is the first build of the workspace, i.e. there are no prior builds.
func (b *Builder) firstBuild() (bool, error) {
_, err := b.getLastBuild()
if xerrors.Is(err, sql.ErrNoRows) {
// first build!
return true, nil
}
if err != nil {
return false, err
}
return false, nil
}
func (b *Builder) getBuildNumber() (int32, error) {
bld, err := b.getLastBuild()
if xerrors.Is(err, sql.ErrNoRows) {
@@ -654,6 +718,75 @@ func (b *Builder) getParameters() (names, values []string, err error) {
return *b.parameterNames, *b.parameterValues, nil
}
// Always reject legacy parameters.
err = b.verifyNoLegacyParameters()
if err != nil {
return nil, nil, BuildError{http.StatusBadRequest, "Unable to build workspace with unsupported parameters", err}
}
if b.usingDynamicParameters() {
names, values, err = b.getDynamicParameters()
} else {
names, values, err = b.getClassicParameters()
}
if err != nil {
return nil, nil, xerrors.Errorf("get parameters: %w", err)
}
b.parameterNames = &names
b.parameterValues = &values
return names, values, nil
}
func (b *Builder) getDynamicParameters() (names, values []string, err error) {
lastBuildParameters, err := b.getLastBuildParameters()
if err != nil {
return nil, nil, BuildError{http.StatusInternalServerError, "failed to fetch last build parameters", err}
}
presetParameterValues, err := b.getPresetParameterValues()
if err != nil {
return nil, nil, BuildError{http.StatusInternalServerError, "failed to fetch preset parameter values", err}
}
render, err := b.getDynamicParameterRenderer()
if err != nil {
return nil, nil, BuildError{http.StatusInternalServerError, "failed to get dynamic parameter renderer", err}
}
firstBuild, err := b.firstBuild()
if err != nil {
return nil, nil, BuildError{http.StatusInternalServerError, "failed to check if first build", err}
}
buildValues, diagnostics := dynamicparameters.ResolveParameters(b.ctx, b.workspace.OwnerID, render, firstBuild,
lastBuildParameters,
b.richParameterValues,
presetParameterValues)
if diagnostics.HasErrors() {
// TODO: Improve the error response. The response should include the validations for each failed
// parameter. The response should also indicate it's a validation error or a more general form failure.
// For now, any error is sufficient.
return nil, nil, BuildError{
Status: http.StatusBadRequest,
Message: fmt.Sprintf("%d errors occurred while resolving parameters", len(diagnostics)),
Wrapped: diagnostics,
}
}
names = make([]string, 0, len(buildValues))
values = make([]string, 0, len(buildValues))
for k, v := range buildValues {
names = append(names, k)
values = append(values, v)
}
return names, values, nil
}
func (b *Builder) getClassicParameters() (names, values []string, err error) {
templateVersionParameters, err := b.getTemplateVersionParameters()
if err != nil {
return nil, nil, BuildError{http.StatusInternalServerError, "failed to fetch template version parameters", err}
@@ -662,17 +795,9 @@ func (b *Builder) getParameters() (names, values []string, err error) {
if err != nil {
return nil, nil, BuildError{http.StatusInternalServerError, "failed to fetch last build parameters", err}
}
if b.templateVersionPresetID != uuid.Nil {
// Fetch and cache these, since we'll need them to override requested values if a preset was chosen
presetParameters, err := b.store.GetPresetParametersByPresetID(b.ctx, b.templateVersionPresetID)
if err != nil {
return nil, nil, BuildError{http.StatusInternalServerError, "failed to get preset parameters", err}
}
b.templateVersionPresetParameterValues = presetParameters
}
err = b.verifyNoLegacyParameters()
presetParameterValues, err := b.getPresetParameterValues()
if err != nil {
return nil, nil, BuildError{http.StatusBadRequest, "Unable to build workspace with unsupported parameters", err}
return nil, nil, BuildError{http.StatusInternalServerError, "failed to fetch preset parameter values", err}
}
lastBuildParameterValues := db2sdk.WorkspaceBuildParameters(lastBuildParameters)
@@ -680,43 +805,6 @@ func (b *Builder) getParameters() (names, values []string, err error) {
Rich: lastBuildParameterValues,
}
// Dynamic parameters skip all parameter validation.
// Deleting a workspace also should skip parameter validation.
// Pass the user's input as is.
if b.usingDynamicParameters() {
// TODO: The previous behavior was only to pass param values
// for parameters that exist. Since dynamic params can have
// conditional parameter existence, the static frame of reference
// is not sufficient. So assume the user is correct, or pull in the
// dynamic param code to find the actual parameters.
latestValues := make(map[string]string, len(b.richParameterValues))
for _, latest := range b.richParameterValues {
latestValues[latest.Name] = latest.Value
}
// Merge the inputs with values from the previous build.
for _, last := range lastBuildParameterValues {
// TODO: Ideally we use the resolver here and look at parameter
// fields such as 'ephemeral'. This requires loading the terraform
// files. For now, just send the previous inputs as is.
if _, exists := latestValues[last.Name]; exists {
// latestValues take priority, so skip this previous value.
continue
}
names = append(names, last.Name)
values = append(values, last.Value)
}
for _, value := range b.richParameterValues {
names = append(names, value.Name)
values = append(values, value.Value)
}
b.parameterNames = &names
b.parameterValues = &values
return names, values, nil
}
for _, templateVersionParameter := range templateVersionParameters {
tvp, err := db2sdk.TemplateVersionParameterFromPreview(templateVersionParameter)
if err != nil {
@@ -725,7 +813,7 @@ func (b *Builder) getParameters() (names, values []string, err error) {
value, err := resolver.ValidateResolve(
tvp,
b.findNewBuildParameterValue(templateVersionParameter.Name),
b.findNewBuildParameterValue(templateVersionParameter.Name, presetParameterValues),
)
if err != nil {
// At this point, we've queried all the data we need from the database,
@@ -743,8 +831,8 @@ func (b *Builder) getParameters() (names, values []string, err error) {
return names, values, nil
}
func (b *Builder) findNewBuildParameterValue(name string) *codersdk.WorkspaceBuildParameter {
for _, v := range b.templateVersionPresetParameterValues {
func (b *Builder) findNewBuildParameterValue(name string, presets []database.TemplateVersionPresetParameter) *codersdk.WorkspaceBuildParameter {
for _, v := range presets {
if v.Name == name {
return &codersdk.WorkspaceBuildParameter{
Name: v.Name,
@@ -949,6 +1037,24 @@ func (b *Builder) getTemplateVersionWorkspaceTags() ([]database.TemplateVersionW
return *b.templateVersionWorkspaceTags, nil
}
func (b *Builder) getPresetParameterValues() ([]database.TemplateVersionPresetParameter, error) {
if b.templateVersionPresetParameterValues != nil {
return *b.templateVersionPresetParameterValues, nil
}
if b.templateVersionPresetID == uuid.Nil {
return []database.TemplateVersionPresetParameter{}, nil
}
// Fetch and cache these, since we'll need them to override requested values if a preset was chosen
presetParameters, err := b.store.GetPresetParametersByPresetID(b.ctx, b.templateVersionPresetID)
if err != nil {
return nil, xerrors.Errorf("failed to get preset parameters: %w", err)
}
b.templateVersionPresetParameterValues = ptr.Ref(presetParameters)
return *b.templateVersionPresetParameterValues, nil
}
// authorize performs build authorization pre-checks using the provided authFunc
func (b *Builder) authorize(authFunc func(action policy.Action, object rbac.Objecter) bool) error {
// Doing this up front saves a lot of work if the user doesn't have permission.
@@ -1117,21 +1223,5 @@ func (b *Builder) usingDynamicParameters() bool {
return false
}
vals, err := b.getTemplateTerraformValues()
if err != nil {
return false
}
if !ProvisionerVersionSupportsDynamicParameters(vals.ProvisionerdVersion) {
return false
}
return true
}
func ProvisionerVersionSupportsDynamicParameters(version string) bool {
major, minor, err := apiversion.Parse(version)
// If the api version is not valid or less than 1.6, we need to use the static parameters
useStaticParams := err != nil || major < 1 || (major == 1 && minor < 6)
return !useStaticParams
}
+53 -46
View File
@@ -8,6 +8,10 @@ import (
"testing"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/files"
"github.com/coder/coder/v2/provisionersdk"
"github.com/google/uuid"
@@ -94,11 +98,12 @@ func TestBuilder_NoOptions(t *testing.T) {
asrt.Empty(params.Value)
}),
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart)
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
}
@@ -133,11 +138,12 @@ func TestBuilder_Initiator(t *testing.T) {
}),
withBuild,
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).Initiator(otherUserID)
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
}
@@ -178,11 +184,12 @@ func TestBuilder_Baggage(t *testing.T) {
}),
withBuild,
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).Initiator(otherUserID)
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{IP: "127.0.0.1"})
_, _, _, err := uut.Build(ctx, mDB, fc, nil, audit.WorkspaceBuildBaggage{IP: "127.0.0.1"})
req.NoError(err)
}
@@ -216,11 +223,12 @@ func TestBuilder_Reason(t *testing.T) {
}),
withBuild,
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).Reason(database.BuildReasonAutostart)
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
}
@@ -259,11 +267,12 @@ func TestBuilder_ActiveVersion(t *testing.T) {
}),
withBuild,
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).ActiveVersion()
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
}
@@ -373,11 +382,12 @@ func TestWorkspaceBuildWithTags(t *testing.T) {
}),
withBuild,
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).RichParameterValues(buildParameters)
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
}
@@ -455,11 +465,12 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
}),
withBuild,
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).RichParameterValues(nextBuildParameters)
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
t.Run("UsePreviousParameterValues", func(t *testing.T) {
@@ -502,11 +513,12 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
}),
withBuild,
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).RichParameterValues(nextBuildParameters)
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
@@ -533,17 +545,17 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
mDB := expectDB(t,
// Inputs
withTemplate,
withInactiveVersion(richParameters),
withInactiveVersionNoParams(),
withLastBuildFound,
withTemplateVersionVariables(inactiveVersionID, nil),
withRichParameters(nil),
withParameterSchemas(inactiveJobID, schemas),
withWorkspaceTags(inactiveVersionID, nil),
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart)
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, fc, nil, audit.WorkspaceBuildBaggage{})
bldErr := wsbuilder.BuildError{}
req.ErrorAs(err, &bldErr)
asrt.Equal(http.StatusBadRequest, bldErr.Status)
@@ -575,11 +587,12 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
// Outputs
// no transaction, since we failed fast while validation build parameters
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).RichParameterValues(nextBuildParameters)
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, fc, nil, audit.WorkspaceBuildBaggage{})
bldErr := wsbuilder.BuildError{}
req.ErrorAs(err, &bldErr)
asrt.Equal(http.StatusBadRequest, bldErr.Status)
@@ -639,12 +652,13 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
}),
withBuild,
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).
RichParameterValues(nextBuildParameters).
VersionID(activeVersionID)
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
@@ -702,12 +716,13 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
}),
withBuild,
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).
RichParameterValues(nextBuildParameters).
VersionID(activeVersionID)
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
@@ -763,13 +778,14 @@ func TestWorkspaceBuildWithRichParameters(t *testing.T) {
}),
withBuild,
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).
RichParameterValues(nextBuildParameters).
VersionID(activeVersionID)
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
}
@@ -829,13 +845,14 @@ func TestWorkspaceBuildWithPreset(t *testing.T) {
asrt.Empty(params.Value)
}),
)
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart).
ActiveVersion().
TemplateVersionPresetID(presetID)
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
}
@@ -899,8 +916,10 @@ func TestWorkspaceBuildDeleteOrphan(t *testing.T) {
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionDelete).Orphan()
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
@@ -974,38 +993,13 @@ func TestWorkspaceBuildDeleteOrphan(t *testing.T) {
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionDelete).Orphan()
fc := files.New(prometheus.NewRegistry(), &coderdtest.FakeAuthorizer{})
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, nil, audit.WorkspaceBuildBaggage{})
_, _, _, err := uut.Build(ctx, mDB, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
}
func TestProvisionerVersionSupportsDynamicParameters(t *testing.T) {
t.Parallel()
for v, dyn := range map[string]bool{
"": false,
"na": false,
"0.0": false,
"0.10": false,
"1.4": false,
"1.5": false,
"1.6": true,
"1.7": true,
"1.8": true,
"2.0": true,
"2.17": true,
"4.0": true,
} {
t.Run(v, func(t *testing.T) {
t.Parallel()
does := wsbuilder.ProvisionerVersionSupportsDynamicParameters(v)
require.Equal(t, dyn, does)
})
}
}
type txExpect func(mTx *dbmock.MockStore)
func expectDB(t *testing.T, opts ...txExpect) *dbmock.MockStore {
@@ -1052,7 +1046,7 @@ func withInTx(mTx *dbmock.MockStore) {
)
}
func withActiveVersion(params []database.TemplateVersionParameter) func(mTx *dbmock.MockStore) {
func withActiveVersionNoParams() func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
mTx.EXPECT().GetTemplateVersionByID(gomock.Any(), activeVersionID).
Times(1).
@@ -1082,6 +1076,12 @@ func withActiveVersion(params []database.TemplateVersionParameter) func(mTx *dbm
UpdatedAt: time.Now(),
CompletedAt: sql.NullTime{Time: dbtime.Now(), Valid: true},
}, nil)
}
}
func withActiveVersion(params []database.TemplateVersionParameter) func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
withActiveVersionNoParams()(mTx)
paramsCall := mTx.EXPECT().GetTemplateVersionParameters(gomock.Any(), activeVersionID).
Times(1)
if len(params) > 0 {
@@ -1092,7 +1092,7 @@ func withActiveVersion(params []database.TemplateVersionParameter) func(mTx *dbm
}
}
func withInactiveVersion(params []database.TemplateVersionParameter) func(mTx *dbmock.MockStore) {
func withInactiveVersionNoParams() func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
mTx.EXPECT().GetTemplateVersionByID(gomock.Any(), inactiveVersionID).
Times(1).
@@ -1122,6 +1122,13 @@ func withInactiveVersion(params []database.TemplateVersionParameter) func(mTx *d
UpdatedAt: time.Now(),
CompletedAt: sql.NullTime{Time: dbtime.Now(), Valid: true},
}, nil)
}
}
func withInactiveVersion(params []database.TemplateVersionParameter) func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
withInactiveVersionNoParams()(mTx)
paramsCall := mTx.EXPECT().GetTemplateVersionParameters(gomock.Any(), inactiveVersionID).
Times(1)
if len(params) > 0 {