Skip to content

Commit 8db4817

Browse files
committed
add tests for autobuild
1 parent 44ea963 commit 8db4817

File tree

4 files changed

+123
-2
lines changed

4 files changed

+123
-2
lines changed

coderd/database/dbauthz/dbauthz.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2200,7 +2200,7 @@ func (q *querier) InsertWorkspaceAppStats(ctx context.Context, arg database.Inse
22002200
func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertWorkspaceBuildParams) error {
22012201
w, err := q.db.GetWorkspaceByID(ctx, arg.WorkspaceID)
22022202
if err != nil {
2203-
return err
2203+
return xerrors.Errorf("get workspace by id: %w", err)
22042204
}
22052205

22062206
var action rbac.Action = rbac.ActionUpdate
@@ -2209,7 +2209,28 @@ func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertW
22092209
}
22102210

22112211
if err = q.authorizeContext(ctx, action, w.WorkspaceBuildRBAC(arg.Transition)); err != nil {
2212-
return err
2212+
return xerrors.Errorf("authorize context: %w", err)
2213+
}
2214+
2215+
// If we're starting a workspace we need to check the template.
2216+
if arg.Transition == database.WorkspaceTransitionStart {
2217+
// Fetch the template. We have to ensure that the request
2218+
// abides by the update policy of the templates.
2219+
t, err := q.db.GetTemplateByID(ctx, w.TemplateID)
2220+
if err != nil {
2221+
return xerrors.Errorf("get template by id: %w", err)
2222+
}
2223+
2224+
// If the template requires the promoted version we need to check if
2225+
// the user is a template admin. If they aren't and are attempting
2226+
// to use a non-promoted version then we must fail the request.
2227+
if t.RequirePromotedVersion {
2228+
if arg.TemplateVersionID != t.ActiveVersionID {
2229+
if err = q.authorizeContext(ctx, rbac.ActionUpdate, t); err != nil {
2230+
return xerrors.Errorf("cannot use non-active version: %w", err)
2231+
}
2232+
}
2233+
}
22132234
}
22142235

22152236
return q.db.InsertWorkspaceBuild(ctx, arg)

coderd/wsbuilder/wsbuilder.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ func (b *Builder) Build(
213213
var provisionerJob *database.ProvisionerJob
214214
err := database.ReadModifyUpdate(store, func(tx database.Store) error {
215215
var err error
216+
b.store = tx
216217
workspaceBuild, provisionerJob, err = b.buildTx(authFunc)
217218
return err
218219
})

enterprise/audit/table.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ var auditableResourcesTypes = map[any]map[string]Action{
8484
"failure_ttl": ActionTrack,
8585
"time_til_dormant": ActionTrack,
8686
"time_til_dormant_autodelete": ActionTrack,
87+
"require_promoted_version": ActionTrack,
8788
},
8889
&database.TemplateVersion{}: {
8990
"id": ActionTrack,

enterprise/coderd/workspaces_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,8 @@ func TestWorkspaceAutobuild(t *testing.T) {
685685
ws := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
686686
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)
687687

688+
coderdtest.MustTransitionWorkspace(t, client, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
689+
688690
// Create a new version that will fail when we try to delete a workspace.
689691
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
690692
Parse: echo.ParseComplete,
@@ -734,6 +736,102 @@ func TestWorkspaceAutobuild(t *testing.T) {
734736
require.Len(t, stats.Transitions, 1)
735737
require.Equal(t, database.WorkspaceTransitionDelete, stats.Transitions[ws.ID])
736738
})
739+
740+
t.Run("RequirePromotedVersion", func(t *testing.T) {
741+
t.Parallel()
742+
743+
var (
744+
tickCh = make(chan time.Time)
745+
statsCh = make(chan autobuild.Stats)
746+
ctx = testutil.Context(t, testutil.WaitMedium)
747+
)
748+
749+
client, user := coderdenttest.New(t, &coderdenttest.Options{
750+
Options: &coderdtest.Options{
751+
AutobuildTicker: tickCh,
752+
IncludeProvisionerDaemon: true,
753+
AutobuildStats: statsCh,
754+
TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore()),
755+
},
756+
LicenseOptions: &coderdenttest.LicenseOptions{
757+
Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1},
758+
},
759+
})
760+
761+
sched, err := cron.Weekly("CRON_TZ=UTC 0 * * * *")
762+
require.NoError(t, err)
763+
764+
// Create a template version1 that passes to get a functioning workspace.
765+
version1 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
766+
Parse: echo.ParseComplete,
767+
ProvisionPlan: echo.PlanComplete,
768+
ProvisionApply: echo.ApplyComplete,
769+
})
770+
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version1.ID)
771+
772+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version1.ID)
773+
require.Equal(t, version1.ID, template.ActiveVersionID)
774+
775+
ws := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
776+
cwr.AutostartSchedule = ptr.Ref(sched.String())
777+
})
778+
779+
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, ws.LatestBuild.ID)
780+
ws = coderdtest.MustTransitionWorkspace(t, client, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
781+
782+
// Create a new version so that we can assert we don't update
783+
// to the latest by default.
784+
version2 := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
785+
Parse: echo.ParseComplete,
786+
ProvisionPlan: echo.PlanComplete,
787+
ProvisionApply: echo.ApplyComplete,
788+
}, func(ctvr *codersdk.CreateTemplateVersionRequest) {
789+
ctvr.TemplateID = template.ID
790+
})
791+
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version2.ID)
792+
793+
// Make sure to promote it.
794+
err = client.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{
795+
ID: version2.ID,
796+
})
797+
require.NoError(t, err)
798+
799+
// Kick of an autostart build.
800+
tickCh <- sched.Next(ws.LatestBuild.CreatedAt)
801+
stats := <-statsCh
802+
require.NoError(t, stats.Error)
803+
require.Len(t, stats.Transitions, 1)
804+
require.Contains(t, stats.Transitions, ws.ID)
805+
require.Equal(t, database.WorkspaceTransitionStart, stats.Transitions[ws.ID])
806+
807+
// Validate that we didn't update to the promoted version.
808+
started := coderdtest.MustWorkspace(t, client, ws.ID)
809+
firstBuild := coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, started.LatestBuild.ID)
810+
require.Equal(t, version1.ID, ws.LatestBuild.TemplateVersionID)
811+
812+
// Update the template to require the promoted version.
813+
_, err = client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
814+
RequirePromotedVersion: true,
815+
AllowUserAutostart: true,
816+
})
817+
require.NoError(t, err)
818+
819+
// Reset the workspace to the stopped state so we can try
820+
// to autostart again.
821+
coderdtest.MustTransitionWorkspace(t, client, ws.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
822+
823+
// Force an autostart transition again.
824+
tickCh <- sched.Next(firstBuild.CreatedAt)
825+
stats = <-statsCh
826+
require.NoError(t, stats.Error)
827+
require.Len(t, stats.Transitions, 1)
828+
require.Contains(t, stats.Transitions, ws.ID)
829+
require.Equal(t, database.WorkspaceTransitionStart, stats.Transitions[ws.ID])
830+
831+
// Validate that we are using the promoted version.
832+
ws = coderdtest.MustWorkspace(t, client, ws.ID)
833+
require.Equal(t, version2.ID, ws.LatestBuild.TemplateVersionID)
834+
})
737835
}
738836

739837
func TestWorkspacesFiltering(t *testing.T) {

0 commit comments

Comments
 (0)