Skip to content

Commit 5954844

Browse files
committed
feat: Support rich parameters in autobuilds
1 parent 896158c commit 5954844

File tree

3 files changed

+156
-31
lines changed

3 files changed

+156
-31
lines changed

coderd/autobuild/executor/lifecycle_executor.go

+53-31
Original file line numberDiff line numberDiff line change
@@ -268,37 +268,59 @@ func build(ctx context.Context, store database.Store, workspace database.Workspa
268268
return xerrors.Errorf("Unsupported transition: %q", trans)
269269
}
270270

271-
newProvisionerJob, err := store.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
272-
ID: provisionerJobID,
273-
CreatedAt: now,
274-
UpdatedAt: now,
275-
InitiatorID: workspace.OwnerID,
276-
OrganizationID: template.OrganizationID,
277-
Provisioner: template.Provisioner,
278-
Type: database.ProvisionerJobTypeWorkspaceBuild,
279-
StorageMethod: priorJob.StorageMethod,
280-
FileID: priorJob.FileID,
281-
Tags: priorJob.Tags,
282-
Input: input,
283-
})
284-
if err != nil {
285-
return xerrors.Errorf("insert provisioner job: %w", err)
286-
}
287-
_, err = store.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{
288-
ID: workspaceBuildID,
289-
CreatedAt: now,
290-
UpdatedAt: now,
291-
WorkspaceID: workspace.ID,
292-
TemplateVersionID: priorHistory.TemplateVersionID,
293-
BuildNumber: priorBuildNumber + 1,
294-
ProvisionerState: priorHistory.ProvisionerState,
295-
InitiatorID: workspace.OwnerID,
296-
Transition: trans,
297-
JobID: newProvisionerJob.ID,
298-
Reason: buildReason,
299-
})
271+
lastBuildParameters, err := store.GetWorkspaceBuildParameters(ctx, priorHistory.ID)
300272
if err != nil {
301-
return xerrors.Errorf("insert workspace build: %w", err)
273+
return xerrors.Errorf("fetch prior workspace build parameters: %w", err)
302274
}
303-
return nil
275+
276+
return store.InTx(func(db database.Store) error {
277+
newProvisionerJob, err := store.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
278+
ID: provisionerJobID,
279+
CreatedAt: now,
280+
UpdatedAt: now,
281+
InitiatorID: workspace.OwnerID,
282+
OrganizationID: template.OrganizationID,
283+
Provisioner: template.Provisioner,
284+
Type: database.ProvisionerJobTypeWorkspaceBuild,
285+
StorageMethod: priorJob.StorageMethod,
286+
FileID: priorJob.FileID,
287+
Tags: priorJob.Tags,
288+
Input: input,
289+
})
290+
if err != nil {
291+
return xerrors.Errorf("insert provisioner job: %w", err)
292+
}
293+
workspaceBuild, err := store.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{
294+
ID: workspaceBuildID,
295+
CreatedAt: now,
296+
UpdatedAt: now,
297+
WorkspaceID: workspace.ID,
298+
TemplateVersionID: priorHistory.TemplateVersionID,
299+
BuildNumber: priorBuildNumber + 1,
300+
ProvisionerState: priorHistory.ProvisionerState,
301+
InitiatorID: workspace.OwnerID,
302+
Transition: trans,
303+
JobID: newProvisionerJob.ID,
304+
Reason: buildReason,
305+
})
306+
if err != nil {
307+
return xerrors.Errorf("insert workspace build: %w", err)
308+
}
309+
310+
names := make([]string, 0, len(lastBuildParameters))
311+
values := make([]string, 0, len(lastBuildParameters))
312+
for _, param := range lastBuildParameters {
313+
names = append(names, param.Name)
314+
values = append(values, param.Value)
315+
}
316+
err = db.InsertWorkspaceBuildParameters(ctx, database.InsertWorkspaceBuildParametersParams{
317+
WorkspaceBuildID: workspaceBuild.ID,
318+
Name: names,
319+
Value: values,
320+
})
321+
if err != nil {
322+
return xerrors.Errorf("insert workspace build parameters: %w", err)
323+
}
324+
return nil
325+
}, nil)
304326
}

coderd/autobuild/executor/lifecycle_executor_test.go

+100
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,16 @@ import (
88

99
"go.uber.org/goleak"
1010

11+
"github.com/google/uuid"
12+
1113
"github.com/coder/coder/coderd/autobuild/executor"
1214
"github.com/coder/coder/coderd/autobuild/schedule"
1315
"github.com/coder/coder/coderd/coderdtest"
1416
"github.com/coder/coder/coderd/database"
1517
"github.com/coder/coder/coderd/util/ptr"
1618
"github.com/coder/coder/codersdk"
19+
"github.com/coder/coder/provisioner/echo"
20+
"github.com/coder/coder/provisionersdk/proto"
1721

1822
"github.com/stretchr/testify/assert"
1923
"github.com/stretchr/testify/require"
@@ -540,6 +544,67 @@ func TestExecutorAutostartMultipleOK(t *testing.T) {
540544
assert.Len(t, stats2.Transitions, 0)
541545
}
542546

547+
func TestExecutorAutostartWithParameters(t *testing.T) {
548+
t.Parallel()
549+
550+
const (
551+
stringParameterName = "string_parameter"
552+
stringParameterValue = "abc"
553+
554+
numberParameterName = "number_parameter"
555+
numberParameterValue = "7"
556+
)
557+
558+
var (
559+
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
560+
tickCh = make(chan time.Time)
561+
statsCh = make(chan executor.Stats)
562+
client = coderdtest.New(t, &coderdtest.Options{
563+
AutobuildTicker: tickCh,
564+
IncludeProvisionerDaemon: true,
565+
AutobuildStats: statsCh,
566+
})
567+
568+
richParameters = []*proto.RichParameter{
569+
{Name: stringParameterName, Type: "string", Mutable: true},
570+
{Name: numberParameterName, Type: "number", Mutable: true},
571+
}
572+
573+
// Given: we have a user with a workspace that has autostart enabled
574+
workspace = mustProvisionWorkspaceWithParameters(t, client, richParameters, func(cwr *codersdk.CreateWorkspaceRequest) {
575+
cwr.AutostartSchedule = ptr.Ref(sched.String())
576+
cwr.RichParameterValues = []codersdk.WorkspaceBuildParameter{
577+
{
578+
Name: stringParameterName,
579+
Value: stringParameterValue,
580+
},
581+
{
582+
Name: numberParameterName,
583+
Value: numberParameterValue,
584+
},
585+
}
586+
})
587+
)
588+
// Given: workspace is stopped
589+
workspace = coderdtest.MustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
590+
591+
// When: the autobuild executor ticks after the scheduled time
592+
go func() {
593+
tickCh <- sched.Next(workspace.LatestBuild.CreatedAt)
594+
close(tickCh)
595+
}()
596+
597+
// Then: the workspace with parameters should eventually be started
598+
stats := <-statsCh
599+
assert.NoError(t, stats.Error)
600+
assert.Len(t, stats.Transitions, 1)
601+
assert.Contains(t, stats.Transitions, workspace.ID)
602+
assert.Equal(t, database.WorkspaceTransitionStart, stats.Transitions[workspace.ID])
603+
604+
workspace = coderdtest.MustWorkspace(t, client, workspace.ID)
605+
mustWorkspaceParameters(t, client, workspace.LatestBuild.ID)
606+
}
607+
543608
func mustProvisionWorkspace(t *testing.T, client *codersdk.Client, mut ...func(*codersdk.CreateWorkspaceRequest)) codersdk.Workspace {
544609
t.Helper()
545610
user := coderdtest.CreateFirstUser(t, client)
@@ -551,13 +616,48 @@ func mustProvisionWorkspace(t *testing.T, client *codersdk.Client, mut ...func(*
551616
return coderdtest.MustWorkspace(t, client, ws.ID)
552617
}
553618

619+
func mustProvisionWorkspaceWithParameters(t *testing.T, client *codersdk.Client, richParameters []*proto.RichParameter, mut ...func(*codersdk.CreateWorkspaceRequest)) codersdk.Workspace {
620+
t.Helper()
621+
user := coderdtest.CreateFirstUser(t, client)
622+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
623+
Parse: echo.ParseComplete,
624+
ProvisionPlan: []*proto.Provision_Response{
625+
{
626+
Type: &proto.Provision_Response_Complete{
627+
Complete: &proto.Provision_Complete{
628+
Parameters: richParameters,
629+
},
630+
},
631+
}},
632+
ProvisionApply: []*proto.Provision_Response{
633+
{
634+
Type: &proto.Provision_Response_Complete{
635+
Complete: &proto.Provision_Complete{},
636+
},
637+
},
638+
},
639+
})
640+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
641+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
642+
ws := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, mut...)
643+
coderdtest.AwaitWorkspaceBuildJob(t, client, ws.LatestBuild.ID)
644+
return coderdtest.MustWorkspace(t, client, ws.ID)
645+
}
646+
554647
func mustSchedule(t *testing.T, s string) *schedule.Schedule {
555648
t.Helper()
556649
sched, err := schedule.Weekly(s)
557650
require.NoError(t, err)
558651
return sched
559652
}
560653

654+
func mustWorkspaceParameters(t *testing.T, client *codersdk.Client, workspaceID uuid.UUID) {
655+
ctx := context.Background()
656+
buildParameters, err := client.WorkspaceBuildParameters(ctx, workspaceID)
657+
require.NoError(t, err)
658+
require.NotEmpty(t, buildParameters)
659+
}
660+
561661
func TestMain(m *testing.M) {
562662
goleak.VerifyTestMain(m)
563663
}

codersdk/richparameters.go

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import (
88

99
func ValidateWorkspaceBuildParameters(richParameters []TemplateVersionParameter, buildParameters []WorkspaceBuildParameter) error {
1010
for _, buildParameter := range buildParameters {
11+
if buildParameter.Name == "" {
12+
return xerrors.Errorf(`workspace build parameter name is missing`)
13+
}
1114
richParameter, found := findTemplateVersionParameter(richParameters, buildParameter.Name)
1215
if !found {
1316
return xerrors.Errorf(`workspace build parameter is not defined in the template ("coder_parameter"): %s`, buildParameter.Name)

0 commit comments

Comments
 (0)