Skip to content

Commit 42008be

Browse files
committed
Enable automatic updates on autostart
Signed-off-by: Spike Curtis <spike@coder.com>
1 parent 86af82e commit 42008be

File tree

4 files changed

+138
-52
lines changed

4 files changed

+138
-52
lines changed

coderd/autobuild/lifecycle_executor.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ func (e *Executor) runOnce(t time.Time) Stats {
171171
SetLastWorkspaceBuildInTx(&latestBuild).
172172
SetLastWorkspaceBuildJobInTx(&latestJob).
173173
Reason(reason)
174+
log.Debug(e.ctx, "auto building workspace", slog.F("transition", nextTransition))
175+
if nextTransition == database.WorkspaceTransitionStart &&
176+
ws.AutomaticUpdates == database.AutomaticUpdatesAlways {
177+
log.Debug(e.ctx, "autostarting with active version")
178+
builder = builder.ActiveVersion()
179+
}
174180

175181
_, job, err = builder.Build(e.ctx, tx, nil)
176182
if err != nil {

coderd/autobuild/lifecycle_executor_test.go

Lines changed: 123 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"testing"
77
"time"
88

9+
"cdr.dev/slog"
10+
911
"github.com/google/uuid"
1012
"github.com/stretchr/testify/assert"
1113
"github.com/stretchr/testify/require"
@@ -64,50 +66,128 @@ func TestExecutorAutostartOK(t *testing.T) {
6466
func TestExecutorAutostartTemplateUpdated(t *testing.T) {
6567
t.Parallel()
6668

67-
var (
68-
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
69-
ctx = context.Background()
70-
err error
71-
tickCh = make(chan time.Time)
72-
statsCh = make(chan autobuild.Stats)
73-
client = coderdtest.New(t, &coderdtest.Options{
74-
AutobuildTicker: tickCh,
75-
IncludeProvisionerDaemon: true,
76-
AutobuildStats: statsCh,
77-
})
78-
// Given: we have a user with a workspace that has autostart enabled
79-
workspace = mustProvisionWorkspace(t, client, func(cwr *codersdk.CreateWorkspaceRequest) {
80-
cwr.AutostartSchedule = ptr.Ref(sched.String())
81-
})
82-
)
83-
// Given: workspace is stopped
84-
workspace = coderdtest.MustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
85-
86-
// Given: the workspace template has been updated
87-
orgs, err := client.OrganizationsByUser(ctx, workspace.OwnerID.String())
88-
require.NoError(t, err)
89-
require.Len(t, orgs, 1)
90-
91-
newVersion := coderdtest.UpdateTemplateVersion(t, client, orgs[0].ID, nil, workspace.TemplateID)
92-
coderdtest.AwaitTemplateVersionJobCompleted(t, client, newVersion.ID)
93-
require.NoError(t, client.UpdateActiveTemplateVersion(ctx, workspace.TemplateID, codersdk.UpdateActiveTemplateVersion{
94-
ID: newVersion.ID,
95-
}))
96-
97-
// When: the autobuild executor ticks after the scheduled time
98-
go func() {
99-
tickCh <- sched.Next(workspace.LatestBuild.CreatedAt)
100-
close(tickCh)
101-
}()
69+
testCases := []struct {
70+
name string
71+
automaticUpdates codersdk.AutomaticUpdates
72+
compatibleParameters bool
73+
expectStart bool
74+
expectUpdate bool
75+
}{
76+
{
77+
name: "Never",
78+
automaticUpdates: codersdk.AutomaticUpdatesNever,
79+
compatibleParameters: true,
80+
expectStart: true,
81+
expectUpdate: false,
82+
},
83+
{
84+
name: "Always_Compatible",
85+
automaticUpdates: codersdk.AutomaticUpdatesAlways,
86+
compatibleParameters: true,
87+
expectStart: true,
88+
expectUpdate: true,
89+
},
90+
{
91+
name: "Always_Incompatible",
92+
automaticUpdates: codersdk.AutomaticUpdatesAlways,
93+
compatibleParameters: false,
94+
expectStart: false,
95+
expectUpdate: false,
96+
},
97+
}
98+
for _, tc := range testCases {
99+
tc := tc
100+
t.Run(tc.name, func(t *testing.T) {
101+
t.Parallel()
102+
var (
103+
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
104+
ctx = context.Background()
105+
err error
106+
tickCh = make(chan time.Time)
107+
statsCh = make(chan autobuild.Stats)
108+
logger = slogtest.Make(t, &slogtest.Options{IgnoreErrors: !tc.expectStart}).Leveled(slog.LevelDebug)
109+
client = coderdtest.New(t, &coderdtest.Options{
110+
AutobuildTicker: tickCh,
111+
IncludeProvisionerDaemon: true,
112+
AutobuildStats: statsCh,
113+
Logger: &logger,
114+
})
115+
// Given: we have a user with a workspace that has autostart enabled
116+
workspace = mustProvisionWorkspace(t, client, func(cwr *codersdk.CreateWorkspaceRequest) {
117+
cwr.AutostartSchedule = ptr.Ref(sched.String())
118+
// Given: automatic updates from the test case
119+
cwr.AutomaticUpdates = tc.automaticUpdates
120+
})
121+
)
122+
// Given: workspace is stopped
123+
workspace = coderdtest.MustTransitionWorkspace(
124+
t, client, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
125+
126+
// Given: the workspace template has been updated
127+
orgs, err := client.OrganizationsByUser(ctx, workspace.OwnerID.String())
128+
require.NoError(t, err)
129+
require.Len(t, orgs, 1)
130+
131+
var res *echo.Responses
132+
if !tc.compatibleParameters {
133+
// Given, parameters of the new version are not compatible.
134+
// Since initial version has no parameters, any parameters in the new version will be incompatible
135+
res = &echo.Responses{
136+
Parse: echo.ParseComplete,
137+
ProvisionApply: []*proto.Response{{
138+
Type: &proto.Response_Apply{
139+
Apply: &proto.ApplyComplete{
140+
Parameters: []*proto.RichParameter{
141+
{
142+
Name: "new",
143+
Mutable: false,
144+
Required: true,
145+
},
146+
},
147+
},
148+
},
149+
}},
150+
}
151+
}
102152

103-
// Then: the workspace should be started using the previous template version, and not the updated version.
104-
stats := <-statsCh
105-
assert.NoError(t, stats.Error)
106-
assert.Len(t, stats.Transitions, 1)
107-
assert.Contains(t, stats.Transitions, workspace.ID)
108-
assert.Equal(t, database.WorkspaceTransitionStart, stats.Transitions[workspace.ID])
109-
ws := coderdtest.MustWorkspace(t, client, workspace.ID)
110-
assert.Equal(t, workspace.LatestBuild.TemplateVersionID, ws.LatestBuild.TemplateVersionID, "expected workspace build to be using the old template version")
153+
newVersion := coderdtest.UpdateTemplateVersion(t, client, orgs[0].ID, res, workspace.TemplateID)
154+
coderdtest.AwaitTemplateVersionJobCompleted(t, client, newVersion.ID)
155+
require.NoError(t, client.UpdateActiveTemplateVersion(
156+
ctx, workspace.TemplateID, codersdk.UpdateActiveTemplateVersion{
157+
ID: newVersion.ID,
158+
},
159+
))
160+
161+
t.Log("sending autobuild tick")
162+
// When: the autobuild executor ticks after the scheduled time
163+
go func() {
164+
tickCh <- sched.Next(workspace.LatestBuild.CreatedAt)
165+
close(tickCh)
166+
}()
167+
168+
stats := <-statsCh
169+
assert.NoError(t, stats.Error)
170+
if tc.expectStart {
171+
// Then: the workspace should be started
172+
assert.Len(t, stats.Transitions, 1)
173+
assert.Contains(t, stats.Transitions, workspace.ID)
174+
assert.Equal(t, database.WorkspaceTransitionStart, stats.Transitions[workspace.ID])
175+
ws := coderdtest.MustWorkspace(t, client, workspace.ID)
176+
if tc.expectUpdate {
177+
// Then: uses the updated version
178+
assert.Equal(t, newVersion.ID, ws.LatestBuild.TemplateVersionID,
179+
"expected workspace build to be using the updated template version")
180+
} else {
181+
// Then: uses the previous template version
182+
assert.Equal(t, workspace.LatestBuild.TemplateVersionID, ws.LatestBuild.TemplateVersionID,
183+
"expected workspace build to be using the old template version")
184+
}
185+
} else {
186+
// Then: the workspace should not be started
187+
assert.Len(t, stats.Transitions, 0)
188+
}
189+
})
190+
}
111191
}
112192

113193
func TestExecutorAutostartAlreadyRunning(t *testing.T) {

coderd/coderdtest/coderdtest.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
182182
if options == nil {
183183
options = &Options{}
184184
}
185+
if options.Logger == nil {
186+
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
187+
options.Logger = &logger
188+
}
185189
if options.GoogleTokenValidator == nil {
186190
ctx, cancelFunc := context.WithCancel(context.Background())
187191
t.Cleanup(cancelFunc)
@@ -214,7 +218,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
214218

215219
if options.Database == nil {
216220
options.Database, options.Pubsub = dbtestutil.NewDB(t)
217-
options.Database = dbauthz.New(options.Database, options.Authorizer, slogtest.Make(t, nil).Leveled(slog.LevelDebug))
221+
options.Database = dbauthz.New(options.Database, options.Authorizer, options.Logger.Leveled(slog.LevelDebug))
218222
}
219223

220224
// Some routes expect a deployment ID, so just make sure one exists.
@@ -268,14 +272,14 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
268272
options.Database,
269273
options.Pubsub,
270274
&templateScheduleStore,
271-
slogtest.Make(t, nil).Named("autobuild.executor").Leveled(slog.LevelDebug),
275+
*options.Logger,
272276
options.AutobuildTicker,
273277
).WithStatsChannel(options.AutobuildStats)
274278
lifecycleExecutor.Run()
275279

276280
hangDetectorTicker := time.NewTicker(options.DeploymentValues.JobHangDetectorInterval.Value())
277281
defer hangDetectorTicker.Stop()
278-
hangDetector := unhanger.New(ctx, options.Database, options.Pubsub, slogtest.Make(t, nil).Named("unhanger.detector"), hangDetectorTicker.C)
282+
hangDetector := unhanger.New(ctx, options.Database, options.Pubsub, options.Logger.Named("unhanger.detector"), hangDetectorTicker.C)
279283
hangDetector.Start()
280284
t.Cleanup(hangDetector.Close)
281285

@@ -334,7 +338,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
334338
stunAddresses = options.DeploymentValues.DERP.Server.STUNAddresses.Value()
335339
}
336340

337-
derpServer := derp.NewServer(key.NewNode(), tailnet.Logger(slogtest.Make(t, nil).Named("derp").Leveled(slog.LevelDebug)))
341+
derpServer := derp.NewServer(key.NewNode(), tailnet.Logger(options.Logger.Named("derp").Leveled(slog.LevelDebug)))
338342
derpServer.SetMeshKey("test-key")
339343

340344
// match default with cli default
@@ -349,10 +353,6 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
349353
require.NoError(t, err)
350354
}
351355

352-
if options.Logger == nil {
353-
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
354-
options.Logger = &logger
355-
}
356356
region := &tailcfg.DERPRegion{
357357
EmbeddedRelay: true,
358358
RegionID: int(options.DeploymentValues.DERP.Server.RegionID.Value()),

coderd/database/db2sdk/db2sdk_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ func TestProvisionerJobStatus(t *testing.T) {
139139
// Make sure the inserted job has the right values.
140140
require.Equal(t, tc.job.StartedAt.Time.UTC(), inserted.StartedAt.Time.UTC(), "started at")
141141
require.Equal(t, tc.job.CompletedAt.Time.UTC(), inserted.CompletedAt.Time.UTC(), "completed at")
142-
require.Equal(t, tc.job.CanceledAt.Time.UTC(), inserted.CanceledAt.Time.UTC(), "cancelled at")
142+
require.Equal(t, tc.job.CanceledAt.Time.UTC(), inserted.CanceledAt.Time.UTC(), "canceled at")
143143
require.Equal(t, tc.job.Error, inserted.Error, "error")
144144
require.Equal(t, tc.job.ErrorCode, inserted.ErrorCode, "error code")
145145

0 commit comments

Comments
 (0)