Files
coder/coderd/wsbuilder/wsbuilder_test.go
T
Cian Johnston 2f55e29466 fix: complete job and mark workspace as deleted when no provisioners are available (#18465)
Alternate fix for https://github.com/coder/coder/issues/18080

Modifies wsbuilder to complete the provisioner job and mark the
workspace as deleted if it is clear that no provisioner will be able to
pick up the delete build.

This has a significant advantage of not deviating too much from the
current semantics of `POST /api/v2/workspacebuilds`.
https://github.com/coder/coder/pull/18460 ends up returning a 204 on
orphan delete due to no build being created.

Downside is that we have to duplicate some responsibilities of
provisionerdserver in wsbuilder.

There is a slight gotcha to this approach though: if you stop a
provisioner and then immediately try to orphan-delete, the job will
still be created because of the provisioner heartbeat interval. However
you can cancel it and try again.
2025-06-23 14:07:42 +01:00

1344 lines
45 KiB
Go

package wsbuilder_test
import (
"context"
"database/sql"
"encoding/json"
"net/http"
"testing"
"time"
"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)
}),
)
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{})
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,
)
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{})
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,
)
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"})
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,
)
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{})
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,
)
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{})
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,
)
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{})
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,
)
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{})
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,
)
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{})
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,
withInactiveVersion(richParameters),
withLastBuildFound,
withTemplateVersionVariables(inactiveVersionID, nil),
withRichParameters(nil),
withParameterSchemas(inactiveJobID, schemas),
withWorkspaceTags(inactiveVersionID, nil),
)
ws := database.Workspace{ID: workspaceID, TemplateID: templateID, OwnerID: userID}
uut := wsbuilder.New(ws, database.WorkspaceTransitionStart)
_, _, _, err := uut.Build(ctx, mDB, 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
)
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{})
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,
)
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{})
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,
)
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{})
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,
)
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{})
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)
}),
)
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{})
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()
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, 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()
// nolint: dogsled
_, _, _, err := uut.Build(ctx, mDB, 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 {
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 withActiveVersion(params []database.TemplateVersionParameter) 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)
paramsCall := mTx.EXPECT().GetTemplateVersionParameters(gomock.Any(), activeVersionID).
Times(1)
if len(params) > 0 {
paramsCall.Return(params, nil)
} else {
paramsCall.Return(nil, sql.ErrNoRows)
}
}
}
func withInactiveVersion(params []database.TemplateVersionParameter) 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)
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)
}
}