Files
coder/coderd/wsbuilder/wsbuilder_test.go
Steven Masley 82af2e019d 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`](b65001e89c/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.
2025-06-23 12:35:15 -05:00

1351 lines
46 KiB
Go

package wsbuilder_test
import (
"context"
"database/sql"
"encoding/json"
"net/http"
"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"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
"go.uber.org/mock/gomock"
"github.com/coder/coder/v2/coderd/audit"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbmock"
"github.com/coder/coder/v2/coderd/database/dbtime"
"github.com/coder/coder/v2/coderd/provisionerdserver"
"github.com/coder/coder/v2/coderd/wsbuilder"
"github.com/coder/coder/v2/codersdk"
)
var (
// use fixed IDs so logs are easier to read
templateID = uuid.MustParse("12341234-0000-0000-0001-000000000000")
activeVersionID = uuid.MustParse("12341234-0000-0000-0002-000000000000")
inactiveVersionID = uuid.MustParse("12341234-0000-0000-0003-000000000000")
activeJobID = uuid.MustParse("12341234-0000-0000-0004-000000000000")
inactiveJobID = uuid.MustParse("12341234-0000-0000-0005-000000000000")
orgID = uuid.MustParse("12341234-0000-0000-0006-000000000000")
workspaceID = uuid.MustParse("12341234-0000-0000-0007-000000000000")
userID = uuid.MustParse("12341234-0000-0000-0008-000000000000")
activeFileID = uuid.MustParse("12341234-0000-0000-0009-000000000000")
inactiveFileID = uuid.MustParse("12341234-0000-0000-000a-000000000000")
lastBuildID = uuid.MustParse("12341234-0000-0000-000b-000000000000")
lastBuildJobID = uuid.MustParse("12341234-0000-0000-000c-000000000000")
otherUserID = uuid.MustParse("12341234-0000-0000-000d-000000000000")
presetID = uuid.MustParse("12341234-0000-0000-000e-000000000000")
)
func TestBuilder_NoOptions(t *testing.T) {
t.Parallel()
req := require.New(t)
asrt := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var buildID uuid.UUID
mDB := expectDB(t,
// Inputs
withTemplate,
withInactiveVersion(nil),
withLastBuildFound,
withTemplateVersionVariables(inactiveVersionID, nil),
withRichParameters(nil),
withParameterSchemas(inactiveJobID, nil),
withWorkspaceTags(inactiveVersionID, nil),
withProvisionerDaemons([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {
asrt.Equal(userID, job.InitiatorID)
asrt.Equal(inactiveFileID, job.FileID)
input := provisionerdserver.WorkspaceProvisionJob{}
err := json.Unmarshal(job.Input, &input)
req.NoError(err)
// store build ID for later
buildID = input.WorkspaceBuildID
}),
withInTx,
expectBuild(func(bld database.InsertWorkspaceBuildParams) {
asrt.Equal(inactiveVersionID, bld.TemplateVersionID)
asrt.Equal(workspaceID, bld.WorkspaceID)
asrt.Equal(int32(2), bld.BuildNumber)
asrt.Equal("last build state", string(bld.ProvisionerState))
asrt.Equal(userID, bld.InitiatorID)
asrt.Equal(database.WorkspaceTransitionStart, bld.Transition)
asrt.Equal(database.BuildReasonInitiator, bld.Reason)
asrt.Equal(buildID, bld.ID)
}),
withBuild,
expectBuildParameters(func(params database.InsertWorkspaceBuildParametersParams) {
asrt.Equal(buildID, params.WorkspaceBuildID)
asrt.Empty(params.Name)
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, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
}
func TestBuilder_Initiator(t *testing.T) {
t.Parallel()
req := require.New(t)
asrt := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
mDB := expectDB(t,
// Inputs
withTemplate,
withInactiveVersion(nil),
withLastBuildFound,
withTemplateVersionVariables(inactiveVersionID, nil),
withRichParameters(nil),
withParameterSchemas(inactiveJobID, nil),
withWorkspaceTags(inactiveVersionID, nil),
withProvisionerDaemons([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {
asrt.Equal(otherUserID, job.InitiatorID)
}),
withInTx,
expectBuild(func(bld database.InsertWorkspaceBuildParams) {
asrt.Equal(otherUserID, bld.InitiatorID)
}),
expectBuildParameters(func(params database.InsertWorkspaceBuildParametersParams) {
}),
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, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
}
func TestBuilder_Baggage(t *testing.T) {
t.Parallel()
req := require.New(t)
asrt := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
otel.SetTextMapPropagator(
propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
),
)
mDB := expectDB(t,
// Inputs
withTemplate,
withInactiveVersion(nil),
withLastBuildFound,
withTemplateVersionVariables(inactiveVersionID, nil),
withRichParameters(nil),
withParameterSchemas(inactiveJobID, nil),
withWorkspaceTags(inactiveVersionID, nil),
withProvisionerDaemons([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {
asrt.Contains(string(job.TraceMetadata.RawMessage), "ip=127.0.0.1")
}),
withInTx,
expectBuild(func(bld database.InsertWorkspaceBuildParams) {
}),
expectBuildParameters(func(params database.InsertWorkspaceBuildParametersParams) {
}),
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, fc, nil, audit.WorkspaceBuildBaggage{IP: "127.0.0.1"})
req.NoError(err)
}
func TestBuilder_Reason(t *testing.T) {
t.Parallel()
req := require.New(t)
asrt := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
mDB := expectDB(t,
// Inputs
withTemplate,
withInactiveVersion(nil),
withLastBuildFound,
withTemplateVersionVariables(inactiveVersionID, nil),
withRichParameters(nil),
withParameterSchemas(inactiveJobID, nil),
withWorkspaceTags(inactiveVersionID, nil),
withProvisionerDaemons([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{}),
// Outputs
expectProvisionerJob(func(_ database.InsertProvisionerJobParams) {
}),
withInTx,
expectBuild(func(bld database.InsertWorkspaceBuildParams) {
asrt.Equal(database.BuildReasonAutostart, bld.Reason)
}),
expectBuildParameters(func(params database.InsertWorkspaceBuildParametersParams) {
}),
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, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
}
func TestBuilder_ActiveVersion(t *testing.T) {
t.Parallel()
req := require.New(t)
asrt := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
mDB := expectDB(t,
// Inputs
withTemplate,
withActiveVersion(nil),
withLastBuildNotFound,
withTemplateVersionVariables(activeVersionID, nil),
withParameterSchemas(activeJobID, nil),
withWorkspaceTags(activeVersionID, nil),
withProvisionerDaemons([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{}),
// previous rich parameters are not queried because there is no previous build.
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {
asrt.Equal(activeFileID, job.FileID)
}),
withInTx,
expectBuild(func(bld database.InsertWorkspaceBuildParams) {
asrt.Equal(activeVersionID, bld.TemplateVersionID)
// no previous build...
asrt.Equal(int32(1), bld.BuildNumber)
asrt.Len(bld.ProvisionerState, 0)
}),
expectBuildParameters(func(params database.InsertWorkspaceBuildParametersParams) {
}),
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, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
}
func TestWorkspaceBuildWithTags(t *testing.T) {
t.Parallel()
asrt := assert.New(t)
req := require.New(t)
workspaceTags := []database.TemplateVersionWorkspaceTag{
{
Key: "fruits_tag",
Value: "data.coder_parameter.number_of_apples.value + data.coder_parameter.number_of_oranges.value",
},
{
Key: "cluster_tag",
Value: `"best_developers"`,
},
{
Key: "project_tag",
Value: `"${data.coder_parameter.project.value}+12345"`,
},
{
Key: "team_tag",
Value: `data.coder_parameter.team.value`,
},
{
Key: "yes_or_no",
Value: `data.coder_parameter.is_debug_build.value`,
},
{
Key: "actually_no",
Value: `!data.coder_parameter.is_debug_build.value`,
},
{
Key: "is_debug_build",
Value: `data.coder_parameter.is_debug_build.value == "true" ? "in-debug-mode" : "no-debug"`,
},
{
Key: "variable_tag",
Value: `var.tag`,
},
{
Key: "another_variable_tag",
Value: `var.tag2`,
},
}
richParameters := []database.TemplateVersionParameter{
// Parameters can be mutable although it is discouraged as the workspace can be moved between provisioner nodes.
{Name: "project", Description: "This is first parameter", Mutable: true, Options: json.RawMessage("[]")},
{Name: "team", Description: "This is second parameter", Mutable: true, DefaultValue: "godzilla", Options: json.RawMessage("[]")},
{Name: "is_debug_build", Type: "bool", Description: "This is third parameter", Mutable: false, DefaultValue: "false", Options: json.RawMessage("[]")},
{Name: "number_of_apples", Type: "number", Description: "This is fourth parameter", Mutable: false, DefaultValue: "4", Options: json.RawMessage("[]")},
{Name: "number_of_oranges", Type: "number", Description: "This is fifth parameter", Mutable: false, DefaultValue: "6", Options: json.RawMessage("[]")},
}
templateVersionVariables := []database.TemplateVersionVariable{
{Name: "tag", Description: "This is a variable tag", TemplateVersionID: inactiveVersionID, Type: "string", DefaultValue: "default-value", Value: "my-value"},
{Name: "tag2", Description: "This is another variable tag", TemplateVersionID: inactiveVersionID, Type: "string", DefaultValue: "default-value-2", Value: ""},
}
buildParameters := []codersdk.WorkspaceBuildParameter{
{Name: "project", Value: "foobar-foobaz"},
{Name: "is_debug_build", Value: "true"},
// Parameters "team", "number_of_apples", "number_of_oranges" are skipped, so default value is selected
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
mDB := expectDB(t,
// Inputs
withTemplate,
withInactiveVersion(richParameters),
withLastBuildFound,
withTemplateVersionVariables(inactiveVersionID, templateVersionVariables),
withRichParameters(nil),
withParameterSchemas(inactiveJobID, nil),
withWorkspaceTags(inactiveVersionID, workspaceTags),
withProvisionerDaemons([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {
asrt.Len(job.Tags, 12)
expected := database.StringMap{
"actually_no": "false",
"cluster_tag": "best_developers",
"fruits_tag": "10",
"is_debug_build": "in-debug-mode",
"project_tag": "foobar-foobaz+12345",
"team_tag": "godzilla",
"yes_or_no": "true",
"variable_tag": "my-value",
"another_variable_tag": "default-value-2",
"scope": "user",
"version": "inactive",
"owner": userID.String(),
}
asrt.Equal(job.Tags, expected)
}),
withInTx,
expectBuild(func(_ database.InsertWorkspaceBuildParams) {}),
expectBuildParameters(func(_ database.InsertWorkspaceBuildParametersParams) {
}),
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, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
}
func TestWorkspaceBuildWithRichParameters(t *testing.T) {
t.Parallel()
const (
firstParameterName = "first_parameter"
firstParameterDescription = "This is first parameter"
firstParameterValue = "1"
secondParameterName = "second_parameter"
secondParameterDescription = "This is second parameter"
secondParameterValue = "2"
immutableParameterName = "immutable_parameter"
immutableParameterDescription = "This is immutable parameter"
immutableParameterValue = "3"
)
initialBuildParameters := []database.WorkspaceBuildParameter{
{Name: firstParameterName, Value: firstParameterValue},
{Name: secondParameterName, Value: secondParameterValue},
{Name: immutableParameterName, Value: immutableParameterValue},
}
richParameters := []database.TemplateVersionParameter{
{Name: firstParameterName, Description: firstParameterDescription, Mutable: true, Options: json.RawMessage("[]")},
{Name: secondParameterName, Description: secondParameterDescription, Mutable: true, Options: json.RawMessage("[]")},
{Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false, Options: json.RawMessage("[]")},
}
t.Run("UpdateParameterValues", func(t *testing.T) {
t.Parallel()
req := require.New(t)
asrt := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
const updatedParameterValue = "3"
nextBuildParameters := []codersdk.WorkspaceBuildParameter{
{Name: firstParameterName, Value: firstParameterValue},
{Name: secondParameterName, Value: updatedParameterValue},
}
expectedParams := map[string]string{
firstParameterName: firstParameterValue,
secondParameterName: updatedParameterValue,
immutableParameterName: immutableParameterValue,
}
mDB := expectDB(t,
// Inputs
withTemplate,
withInactiveVersion(richParameters),
withLastBuildFound,
withTemplateVersionVariables(inactiveVersionID, nil),
withRichParameters(initialBuildParameters),
withParameterSchemas(inactiveJobID, nil),
withWorkspaceTags(inactiveVersionID, nil),
withProvisionerDaemons([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {}),
withInTx,
expectBuild(func(bld database.InsertWorkspaceBuildParams) {}),
expectBuildParameters(func(params database.InsertWorkspaceBuildParametersParams) {
asrt.Len(params.Name, len(expectedParams))
for i := range params.Name {
value, ok := expectedParams[params.Name[i]]
asrt.True(ok, "unexpected name %s", params.Name[i])
asrt.Equal(value, params.Value[i])
}
}),
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, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
t.Run("UsePreviousParameterValues", func(t *testing.T) {
t.Parallel()
req := require.New(t)
asrt := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
nextBuildParameters := []codersdk.WorkspaceBuildParameter{}
expectedParams := map[string]string{}
for _, p := range initialBuildParameters {
expectedParams[p.Name] = p.Value
}
mDB := expectDB(t,
// Inputs
withTemplate,
withInactiveVersion(richParameters),
withLastBuildFound,
withTemplateVersionVariables(inactiveVersionID, nil),
withRichParameters(initialBuildParameters),
withParameterSchemas(inactiveJobID, nil),
withWorkspaceTags(inactiveVersionID, nil),
withProvisionerDaemons([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {}),
withInTx,
expectBuild(func(bld database.InsertWorkspaceBuildParams) {}),
expectBuildParameters(func(params database.InsertWorkspaceBuildParametersParams) {
asrt.Len(params.Name, len(expectedParams))
for i := range params.Name {
value, ok := expectedParams[params.Name[i]]
asrt.True(ok, "unexpected name %s", params.Name[i])
asrt.Equal(value, params.Value[i])
}
}),
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, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
t.Run("StartWorkspaceWithLegacyParameterValues", func(t *testing.T) {
t.Parallel()
req := require.New(t)
asrt := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
schemas := []database.ParameterSchema{
{
Name: "not-replaced",
DefaultDestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable,
},
{
Name: "replaced",
DefaultDestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable,
},
}
mDB := expectDB(t,
// Inputs
withTemplate,
withInactiveVersionNoParams(),
withLastBuildFound,
withTemplateVersionVariables(inactiveVersionID, 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, fc, nil, audit.WorkspaceBuildBaggage{})
bldErr := wsbuilder.BuildError{}
req.ErrorAs(err, &bldErr)
asrt.Equal(http.StatusBadRequest, bldErr.Status)
})
t.Run("DoNotModifyImmutables", func(t *testing.T) {
t.Parallel()
req := require.New(t)
asrt := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
nextBuildParameters := []codersdk.WorkspaceBuildParameter{
{Name: immutableParameterName, Value: "BAD"},
}
mDB := expectDB(t,
// Inputs
withTemplate,
withInactiveVersion(richParameters),
withLastBuildFound,
withTemplateVersionVariables(inactiveVersionID, nil),
withRichParameters(initialBuildParameters),
withParameterSchemas(inactiveJobID, nil),
withWorkspaceTags(inactiveVersionID, nil),
// 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, fc, nil, audit.WorkspaceBuildBaggage{})
bldErr := wsbuilder.BuildError{}
req.ErrorAs(err, &bldErr)
asrt.Equal(http.StatusBadRequest, bldErr.Status)
})
t.Run("NewImmutableRequiredParameterAdded", func(t *testing.T) {
t.Parallel()
req := require.New(t)
asrt := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// new template revision
const newImmutableParameterName = "new_immutable_parameter"
const newImmutableParameterDescription = "This is also an immutable parameter"
version2params := []database.TemplateVersionParameter{
{Name: firstParameterName, Description: firstParameterDescription, Mutable: true, Options: json.RawMessage("[]")},
{Name: secondParameterName, Description: secondParameterDescription, Mutable: true, Options: json.RawMessage("[]")},
{Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false, Options: json.RawMessage("[]")},
{Name: newImmutableParameterName, Description: newImmutableParameterDescription, Mutable: false, Required: true, Options: json.RawMessage("[]")},
}
nextBuildParameters := []codersdk.WorkspaceBuildParameter{
{Name: newImmutableParameterName, Value: "good"},
}
expectedParams := map[string]string{
firstParameterName: firstParameterValue,
secondParameterName: secondParameterValue,
immutableParameterName: immutableParameterValue,
newImmutableParameterName: "good",
}
mDB := expectDB(t,
// Inputs
withTemplate,
withActiveVersion(version2params),
withLastBuildFound,
withTemplateVersionVariables(activeVersionID, nil),
withRichParameters(initialBuildParameters),
withParameterSchemas(activeJobID, nil),
withWorkspaceTags(activeVersionID, nil),
withProvisionerDaemons([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {}),
withInTx,
expectBuild(func(bld database.InsertWorkspaceBuildParams) {}),
expectBuildParameters(func(params database.InsertWorkspaceBuildParametersParams) {
asrt.Len(params.Name, len(expectedParams))
for i := range params.Name {
value, ok := expectedParams[params.Name[i]]
asrt.True(ok, "unexpected name %s", params.Name[i])
asrt.Equal(value, params.Value[i])
}
}),
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, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
t.Run("NewImmutableOptionalParameterAdded", func(t *testing.T) {
t.Parallel()
req := require.New(t)
asrt := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// new template revision
const newImmutableParameterName = "new_immutable_parameter"
const newImmutableParameterDescription = "This is also an immutable parameter"
version2params := []database.TemplateVersionParameter{
{Name: firstParameterName, Description: firstParameterDescription, Mutable: true, Options: json.RawMessage("[]")},
{Name: secondParameterName, Description: secondParameterDescription, Mutable: true, Options: json.RawMessage("[]")},
{Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false, Options: json.RawMessage("[]")},
{Name: newImmutableParameterName, Description: newImmutableParameterDescription, Mutable: false, DefaultValue: "12345", Options: json.RawMessage("[]")},
}
nextBuildParameters := []codersdk.WorkspaceBuildParameter{
{Name: newImmutableParameterName, Value: "good"},
}
expectedParams := map[string]string{
firstParameterName: firstParameterValue,
secondParameterName: secondParameterValue,
immutableParameterName: immutableParameterValue,
newImmutableParameterName: "good",
}
mDB := expectDB(t,
// Inputs
withTemplate,
withActiveVersion(version2params),
withLastBuildFound,
withTemplateVersionVariables(activeVersionID, nil),
withRichParameters(initialBuildParameters),
withParameterSchemas(activeJobID, nil),
withWorkspaceTags(activeVersionID, nil),
withProvisionerDaemons([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {}),
withInTx,
expectBuild(func(bld database.InsertWorkspaceBuildParams) {}),
expectBuildParameters(func(params database.InsertWorkspaceBuildParametersParams) {
asrt.Len(params.Name, len(expectedParams))
for i := range params.Name {
value, ok := expectedParams[params.Name[i]]
asrt.True(ok, "unexpected name %s", params.Name[i])
asrt.Equal(value, params.Value[i])
}
}),
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, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
t.Run("NewImmutableOptionalParameterUsesDefault", func(t *testing.T) {
t.Parallel()
req := require.New(t)
asrt := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// new template revision
const newImmutableParameterName = "new_immutable_parameter"
const newImmutableParameterDescription = "This is also an immutable parameter"
version2params := []database.TemplateVersionParameter{
{Name: firstParameterName, Description: firstParameterDescription, Mutable: true, Options: json.RawMessage("[]")},
{Name: secondParameterName, Description: secondParameterDescription, Mutable: true, Options: json.RawMessage("[]")},
{Name: immutableParameterName, Description: immutableParameterDescription, Mutable: false, Options: json.RawMessage("[]")},
{Name: newImmutableParameterName, Description: newImmutableParameterDescription, Mutable: false, DefaultValue: "12345", Options: json.RawMessage("[]")},
}
nextBuildParameters := []codersdk.WorkspaceBuildParameter{}
expectedParams := map[string]string{
firstParameterName: firstParameterValue,
secondParameterName: secondParameterValue,
immutableParameterName: immutableParameterValue,
newImmutableParameterName: "12345",
}
mDB := expectDB(t,
// Inputs
withTemplate,
withActiveVersion(version2params),
withLastBuildFound,
withTemplateVersionVariables(activeVersionID, nil),
withRichParameters(initialBuildParameters),
withParameterSchemas(activeJobID, nil),
withWorkspaceTags(activeVersionID, nil),
withProvisionerDaemons([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {}),
withInTx,
expectBuild(func(bld database.InsertWorkspaceBuildParams) {}),
expectBuildParameters(func(params database.InsertWorkspaceBuildParametersParams) {
asrt.Len(params.Name, len(expectedParams))
for i := range params.Name {
value, ok := expectedParams[params.Name[i]]
asrt.True(ok, "unexpected name %s", params.Name[i])
asrt.Equal(value, params.Value[i])
}
}),
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, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
}
func TestWorkspaceBuildWithPreset(t *testing.T) {
t.Parallel()
req := require.New(t)
asrt := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var buildID uuid.UUID
mDB := expectDB(t,
// Inputs
withTemplate,
withActiveVersion(nil),
// building workspaces using presets with different combinations of parameters
// is tested at the API layer, in TestWorkspace. Here, it is sufficient to
// test that the preset is used when provided.
withTemplateVersionPresetParameters(presetID, nil),
withLastBuildNotFound,
withTemplateVersionVariables(activeVersionID, nil),
withParameterSchemas(activeJobID, nil),
withWorkspaceTags(activeVersionID, nil),
withProvisionerDaemons([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {
asrt.Equal(userID, job.InitiatorID)
asrt.Equal(activeFileID, job.FileID)
input := provisionerdserver.WorkspaceProvisionJob{}
err := json.Unmarshal(job.Input, &input)
req.NoError(err)
// store build ID for later
buildID = input.WorkspaceBuildID
}),
withInTx,
expectBuild(func(bld database.InsertWorkspaceBuildParams) {
asrt.Equal(activeVersionID, bld.TemplateVersionID)
asrt.Equal(workspaceID, bld.WorkspaceID)
asrt.Equal(int32(1), bld.BuildNumber)
asrt.Equal(userID, bld.InitiatorID)
asrt.Equal(database.WorkspaceTransitionStart, bld.Transition)
asrt.Equal(database.BuildReasonInitiator, bld.Reason)
asrt.Equal(buildID, bld.ID)
asrt.True(bld.TemplateVersionPresetID.Valid)
asrt.Equal(presetID, bld.TemplateVersionPresetID.UUID)
}),
withBuild,
expectBuildParameters(func(params database.InsertWorkspaceBuildParametersParams) {
asrt.Equal(buildID, params.WorkspaceBuildID)
asrt.Empty(params.Name)
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, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
}
func TestWorkspaceBuildDeleteOrphan(t *testing.T) {
t.Parallel()
t.Run("WithActiveProvisioners", func(t *testing.T) {
t.Parallel()
req := require.New(t)
asrt := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var buildID uuid.UUID
mDB := expectDB(t,
// Inputs
withTemplate,
withInactiveVersion(nil),
withLastBuildFound,
withTemplateVersionVariables(inactiveVersionID, nil),
withRichParameters(nil),
withWorkspaceTags(inactiveVersionID, nil),
withProvisionerDaemons([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{{
JobID: inactiveJobID,
ProvisionerDaemon: database.ProvisionerDaemon{
LastSeenAt: sql.NullTime{Valid: true, Time: dbtime.Now()},
},
}}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {
asrt.Equal(userID, job.InitiatorID)
asrt.Equal(inactiveFileID, job.FileID)
input := provisionerdserver.WorkspaceProvisionJob{}
err := json.Unmarshal(job.Input, &input)
req.NoError(err)
// store build ID for later
buildID = input.WorkspaceBuildID
}),
withInTx,
expectBuild(func(bld database.InsertWorkspaceBuildParams) {
asrt.Equal(inactiveVersionID, bld.TemplateVersionID)
asrt.Equal(workspaceID, bld.WorkspaceID)
asrt.Equal(int32(2), bld.BuildNumber)
asrt.Empty(string(bld.ProvisionerState))
asrt.Equal(userID, bld.InitiatorID)
asrt.Equal(database.WorkspaceTransitionDelete, bld.Transition)
asrt.Equal(database.BuildReasonInitiator, bld.Reason)
asrt.Equal(buildID, bld.ID)
}),
withBuild,
expectBuildParameters(func(params database.InsertWorkspaceBuildParametersParams) {
asrt.Equal(buildID, params.WorkspaceBuildID)
asrt.Empty(params.Name)
asrt.Empty(params.Value)
}),
)
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, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
t.Run("NoActiveProvisioners", func(t *testing.T) {
t.Parallel()
req := require.New(t)
asrt := assert.New(t)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
var buildID uuid.UUID
var jobID uuid.UUID
mDB := expectDB(t,
// Inputs
withTemplate,
withInactiveVersion(nil),
withLastBuildFound,
withTemplateVersionVariables(inactiveVersionID, nil),
withRichParameters(nil),
withWorkspaceTags(inactiveVersionID, nil),
withProvisionerDaemons([]database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow{}),
// Outputs
expectProvisionerJob(func(job database.InsertProvisionerJobParams) {
asrt.Equal(userID, job.InitiatorID)
asrt.Equal(inactiveFileID, job.FileID)
input := provisionerdserver.WorkspaceProvisionJob{}
err := json.Unmarshal(job.Input, &input)
req.NoError(err)
// store build ID for later
buildID = input.WorkspaceBuildID
// store job ID for later
jobID = job.ID
}),
withInTx,
expectBuild(func(bld database.InsertWorkspaceBuildParams) {
asrt.Equal(inactiveVersionID, bld.TemplateVersionID)
asrt.Equal(workspaceID, bld.WorkspaceID)
asrt.Equal(int32(2), bld.BuildNumber)
asrt.Empty(string(bld.ProvisionerState))
asrt.Equal(userID, bld.InitiatorID)
asrt.Equal(database.WorkspaceTransitionDelete, bld.Transition)
asrt.Equal(database.BuildReasonInitiator, bld.Reason)
asrt.Equal(buildID, bld.ID)
}),
withBuild,
expectBuildParameters(func(params database.InsertWorkspaceBuildParametersParams) {
asrt.Equal(buildID, params.WorkspaceBuildID)
asrt.Empty(params.Name)
asrt.Empty(params.Value)
}),
// Because no provisioners were available and the request was to delete --orphan
expectUpdateProvisionerJobWithCompleteWithStartedAtByID(func(params database.UpdateProvisionerJobWithCompleteWithStartedAtByIDParams) {
asrt.Equal(jobID, params.ID)
asrt.False(params.Error.Valid)
asrt.True(params.CompletedAt.Valid)
asrt.True(params.StartedAt.Valid)
}),
expectUpdateWorkspaceDeletedByID(func(params database.UpdateWorkspaceDeletedByIDParams) {
asrt.Equal(workspaceID, params.ID)
asrt.True(params.Deleted)
}),
expectGetProvisionerJobByID(func(job database.ProvisionerJob) {
asrt.Equal(jobID, job.ID)
}),
)
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, fc, nil, audit.WorkspaceBuildBaggage{})
req.NoError(err)
})
}
type txExpect func(mTx *dbmock.MockStore)
func expectDB(t *testing.T, opts ...txExpect) *dbmock.MockStore {
t.Helper()
ctrl := gomock.NewController(t)
mDB := dbmock.NewMockStore(ctrl)
mTx := dbmock.NewMockStore(ctrl)
// we expect to be run in a transaction; we use mTx to record the
// "in transaction" calls.
mDB.EXPECT().InTx(
gomock.Any(), gomock.Eq(&database.TxOptions{Isolation: sql.LevelRepeatableRead}),
).
DoAndReturn(func(f func(database.Store) error, _ *database.TxOptions) error {
err := f(mTx)
return err
})
// txExpect args set up the expectations for what happens in the transaction.
for _, o := range opts {
o(mTx)
}
return mDB
}
func withTemplate(mTx *dbmock.MockStore) {
mTx.EXPECT().GetTemplateByID(gomock.Any(), templateID).
Times(1).
Return(database.Template{
ID: templateID,
OrganizationID: orgID,
Provisioner: database.ProvisionerTypeTerraform,
ActiveVersionID: activeVersionID,
UseClassicParameterFlow: true,
}, nil)
}
// withInTx runs the given functions on the same db mock.
func withInTx(mTx *dbmock.MockStore) {
mTx.EXPECT().InTx(gomock.Any(), gomock.Any()).Times(1).DoAndReturn(
func(f func(store database.Store) error, _ *database.TxOptions) error {
return f(mTx)
},
)
}
func withActiveVersionNoParams() func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
mTx.EXPECT().GetTemplateVersionByID(gomock.Any(), activeVersionID).
Times(1).
Return(database.TemplateVersion{
ID: activeVersionID,
TemplateID: uuid.NullUUID{UUID: templateID, Valid: true},
OrganizationID: orgID,
Name: "active",
JobID: activeJobID,
}, nil)
mTx.EXPECT().GetProvisionerJobByID(gomock.Any(), activeJobID).
Times(1).Return(database.ProvisionerJob{
ID: activeJobID,
OrganizationID: orgID,
InitiatorID: userID,
Provisioner: database.ProvisionerTypeTerraform,
StorageMethod: database.ProvisionerStorageMethodFile,
Type: database.ProvisionerJobTypeTemplateVersionImport,
Input: nil,
Tags: database.StringMap{
"version": "active",
provisionersdk.TagScope: provisionersdk.ScopeUser,
},
FileID: activeFileID,
StartedAt: sql.NullTime{Time: dbtime.Now(), Valid: true},
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 {
paramsCall.Return(params, nil)
} else {
paramsCall.Return(nil, sql.ErrNoRows)
}
}
}
func withInactiveVersionNoParams() func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
mTx.EXPECT().GetTemplateVersionByID(gomock.Any(), inactiveVersionID).
Times(1).
Return(database.TemplateVersion{
ID: inactiveVersionID,
TemplateID: uuid.NullUUID{UUID: templateID, Valid: true},
OrganizationID: orgID,
Name: "inactive",
JobID: inactiveJobID,
}, nil)
mTx.EXPECT().GetProvisionerJobByID(gomock.Any(), inactiveJobID).
Times(1).Return(database.ProvisionerJob{
ID: inactiveJobID,
OrganizationID: orgID,
InitiatorID: userID,
Provisioner: database.ProvisionerTypeTerraform,
StorageMethod: database.ProvisionerStorageMethodFile,
Type: database.ProvisionerJobTypeTemplateVersionImport,
Input: nil,
Tags: database.StringMap{
"version": "inactive",
provisionersdk.TagScope: provisionersdk.ScopeUser,
},
FileID: inactiveFileID,
StartedAt: sql.NullTime{Time: dbtime.Now(), Valid: true},
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 {
paramsCall.Return(params, nil)
} else {
paramsCall.Return(nil, sql.ErrNoRows)
}
}
}
func withTemplateVersionPresetParameters(presetID uuid.UUID, params []database.TemplateVersionPresetParameter) func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
mTx.EXPECT().GetPresetParametersByPresetID(gomock.Any(), presetID).Return(params, nil)
}
}
func withLastBuildFound(mTx *dbmock.MockStore) {
mTx.EXPECT().GetLatestWorkspaceBuildByWorkspaceID(gomock.Any(), workspaceID).
Times(1).
Return(database.WorkspaceBuild{
ID: lastBuildID,
WorkspaceID: workspaceID,
TemplateVersionID: inactiveVersionID,
BuildNumber: 1,
Transition: database.WorkspaceTransitionStart,
InitiatorID: userID,
JobID: lastBuildJobID,
ProvisionerState: []byte("last build state"),
Reason: database.BuildReasonInitiator,
}, nil)
mTx.EXPECT().GetProvisionerJobByID(gomock.Any(), lastBuildJobID).
Times(1).
Return(database.ProvisionerJob{
ID: lastBuildJobID,
OrganizationID: orgID,
InitiatorID: userID,
Provisioner: database.ProvisionerTypeTerraform,
StorageMethod: database.ProvisionerStorageMethodFile,
FileID: inactiveFileID,
Type: database.ProvisionerJobTypeWorkspaceBuild,
StartedAt: sql.NullTime{Time: dbtime.Now(), Valid: true},
UpdatedAt: time.Now(),
CompletedAt: sql.NullTime{Time: dbtime.Now(), Valid: true},
}, nil)
}
func withLastBuildNotFound(mTx *dbmock.MockStore) {
mTx.EXPECT().GetLatestWorkspaceBuildByWorkspaceID(gomock.Any(), workspaceID).
Times(1).
Return(database.WorkspaceBuild{}, sql.ErrNoRows)
}
func withParameterSchemas(jobID uuid.UUID, schemas []database.ParameterSchema) func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
c := mTx.EXPECT().GetParameterSchemasByJobID(
gomock.Any(),
jobID).
Times(1)
if len(schemas) > 0 {
c.Return(schemas, nil)
} else {
c.Return(nil, sql.ErrNoRows)
}
}
}
func withTemplateVersionVariables(versionID uuid.UUID, params []database.TemplateVersionVariable) func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
c := mTx.EXPECT().GetTemplateVersionVariables(gomock.Any(), versionID).
Times(1)
if len(params) > 0 {
c.Return(params, nil)
} else {
c.Return(nil, sql.ErrNoRows)
}
}
}
func withRichParameters(params []database.WorkspaceBuildParameter) func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
c := mTx.EXPECT().GetWorkspaceBuildParameters(gomock.Any(), lastBuildID).
Times(1)
if len(params) > 0 {
c.Return(params, nil)
} else {
c.Return(nil, sql.ErrNoRows)
}
}
}
func withWorkspaceTags(versionID uuid.UUID, tags []database.TemplateVersionWorkspaceTag) func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
c := mTx.EXPECT().GetTemplateVersionWorkspaceTags(gomock.Any(), versionID).
Times(1)
if len(tags) > 0 {
c.Return(tags, nil)
} else {
c.Return(nil, sql.ErrNoRows)
}
}
}
// Since there is expected to be only one each of job, build, and build-parameters inserted, instead
// of building matchers, we match any call and then assert its parameters. This will feel
// more familiar to the way we write other tests.
// expectProvisionerJob captures a call to InsertProvisionerJob and runs the provided assertions
// against it.
func expectProvisionerJob(
assertions func(job database.InsertProvisionerJobParams),
) func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
mTx.EXPECT().InsertProvisionerJob(gomock.Any(), gomock.Any()).
Times(1).
DoAndReturn(
func(ctx context.Context, params database.InsertProvisionerJobParams) (database.ProvisionerJob, error) {
assertions(params)
// there is no point copying anything other than the ID, since this object is just
// returned to our test code, and we've already asserted what we care about.
return database.ProvisionerJob{ID: params.ID}, nil
},
)
}
}
// expectUpdateProvisionerJobWithCompleteWithStartedAtByID asserts a call to
// expectUpdateProvisionerJobWithCompleteWithStartedAtByID and runs the provided
// assertions against it.
func expectUpdateProvisionerJobWithCompleteWithStartedAtByID(assertions func(params database.UpdateProvisionerJobWithCompleteWithStartedAtByIDParams)) func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
mTx.EXPECT().UpdateProvisionerJobWithCompleteWithStartedAtByID(gomock.Any(), gomock.Any()).
Times(1).
DoAndReturn(
func(ctx context.Context, params database.UpdateProvisionerJobWithCompleteWithStartedAtByIDParams) error {
assertions(params)
return nil
},
)
}
}
// expectUpdateWorkspaceDeletedByID asserts a call to UpdateWorkspaceDeletedByID
// and runs the provided assertions against it.
func expectUpdateWorkspaceDeletedByID(assertions func(params database.UpdateWorkspaceDeletedByIDParams)) func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
mTx.EXPECT().UpdateWorkspaceDeletedByID(gomock.Any(), gomock.Any()).
Times(1).
DoAndReturn(
func(ctx context.Context, params database.UpdateWorkspaceDeletedByIDParams) error {
assertions(params)
return nil
},
)
}
}
// expectGetProvisionerJobByID asserts a call to GetProvisionerJobByID
// and runs the provided assertions against it.
func expectGetProvisionerJobByID(assertions func(job database.ProvisionerJob)) func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
mTx.EXPECT().GetProvisionerJobByID(gomock.Any(), gomock.Any()).
Times(1).
DoAndReturn(
func(ctx context.Context, id uuid.UUID) (database.ProvisionerJob, error) {
job := database.ProvisionerJob{ID: id}
assertions(job)
return job, nil
},
)
}
}
func withBuild(mTx *dbmock.MockStore) {
mTx.EXPECT().GetWorkspaceBuildByID(gomock.Any(), gomock.Any()).Times(1).
DoAndReturn(func(ctx context.Context, id uuid.UUID) (database.WorkspaceBuild, error) {
return database.WorkspaceBuild{ID: id}, nil
})
}
// expectBuild captures a call to InsertWorkspaceBuild and runs the provided assertions
// against it.
func expectBuild(
assertions func(job database.InsertWorkspaceBuildParams),
) func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
mTx.EXPECT().InsertWorkspaceBuild(gomock.Any(), gomock.Any()).
Times(1).
DoAndReturn(
func(ctx context.Context, params database.InsertWorkspaceBuildParams) error {
assertions(params)
return nil
},
)
}
}
// expectBuildParameters captures a call to InsertWorkspaceBuildParameters and runs the provided assertions
// against it.
func expectBuildParameters(
assertions func(database.InsertWorkspaceBuildParametersParams),
) func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
mTx.EXPECT().InsertWorkspaceBuildParameters(gomock.Any(), gomock.Any()).
Times(1).
DoAndReturn(
func(ctx context.Context, params database.InsertWorkspaceBuildParametersParams) error {
assertions(params)
return nil
},
)
}
}
func withProvisionerDaemons(provisionerDaemons []database.GetEligibleProvisionerDaemonsByProvisionerJobIDsRow) func(mTx *dbmock.MockStore) {
return func(mTx *dbmock.MockStore) {
mTx.EXPECT().GetEligibleProvisionerDaemonsByProvisionerJobIDs(gomock.Any(), gomock.Any()).Return(provisionerDaemons, nil)
}
}