mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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:
+162
-72
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user