mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
feat: add API support for workspace automatic updates (#10099)
* Added automatic_updates to workspaces table Signed-off-by: Spike Curtis <spike@coder.com> * Queries and API updates Signed-off-by: Spike Curtis <spike@coder.com> * Golden files Signed-off-by: Spike Curtis <spike@coder.com> * Enable automatic updates on autostart Signed-off-by: Spike Curtis <spike@coder.com> * db migration number Signed-off-by: Spike Curtis <spike@coder.com> * fix imports and ts mock Signed-off-by: Spike Curtis <spike@coder.com> * code review updates Signed-off-by: Spike Curtis <spike@coder.com> --------- Signed-off-by: Spike Curtis <spike@coder.com>
This commit is contained in:
@ -11,6 +11,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/goleak"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"cdr.dev/slog/sloggers/slogtest"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/autobuild"
|
||||
@ -64,50 +65,129 @@ func TestExecutorAutostartOK(t *testing.T) {
|
||||
func TestExecutorAutostartTemplateUpdated(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
|
||||
ctx = context.Background()
|
||||
err error
|
||||
tickCh = make(chan time.Time)
|
||||
statsCh = make(chan autobuild.Stats)
|
||||
client = coderdtest.New(t, &coderdtest.Options{
|
||||
AutobuildTicker: tickCh,
|
||||
IncludeProvisionerDaemon: true,
|
||||
AutobuildStats: statsCh,
|
||||
testCases := []struct {
|
||||
name string
|
||||
automaticUpdates codersdk.AutomaticUpdates
|
||||
compatibleParameters bool
|
||||
expectStart bool
|
||||
expectUpdate bool
|
||||
}{
|
||||
{
|
||||
name: "Never",
|
||||
automaticUpdates: codersdk.AutomaticUpdatesNever,
|
||||
compatibleParameters: true,
|
||||
expectStart: true,
|
||||
expectUpdate: false,
|
||||
},
|
||||
{
|
||||
name: "Always_Compatible",
|
||||
automaticUpdates: codersdk.AutomaticUpdatesAlways,
|
||||
compatibleParameters: true,
|
||||
expectStart: true,
|
||||
expectUpdate: true,
|
||||
},
|
||||
{
|
||||
name: "Always_Incompatible",
|
||||
automaticUpdates: codersdk.AutomaticUpdatesAlways,
|
||||
compatibleParameters: false,
|
||||
expectStart: false,
|
||||
expectUpdate: false,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
var (
|
||||
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
|
||||
ctx = context.Background()
|
||||
err error
|
||||
tickCh = make(chan time.Time)
|
||||
statsCh = make(chan autobuild.Stats)
|
||||
logger = slogtest.Make(t, &slogtest.Options{IgnoreErrors: !tc.expectStart}).Leveled(slog.LevelDebug)
|
||||
client = coderdtest.New(t, &coderdtest.Options{
|
||||
AutobuildTicker: tickCh,
|
||||
IncludeProvisionerDaemon: true,
|
||||
AutobuildStats: statsCh,
|
||||
Logger: &logger,
|
||||
})
|
||||
// Given: we have a user with a workspace that has autostart enabled
|
||||
workspace = mustProvisionWorkspace(t, client, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||
cwr.AutostartSchedule = ptr.Ref(sched.String())
|
||||
// Given: automatic updates from the test case
|
||||
cwr.AutomaticUpdates = tc.automaticUpdates
|
||||
})
|
||||
)
|
||||
// Given: workspace is stopped
|
||||
workspace = coderdtest.MustTransitionWorkspace(
|
||||
t, client, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
|
||||
|
||||
orgs, err := client.OrganizationsByUser(ctx, workspace.OwnerID.String())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, orgs, 1)
|
||||
|
||||
var res *echo.Responses
|
||||
if !tc.compatibleParameters {
|
||||
// Given, parameters of the new version are not compatible.
|
||||
// Since initial version has no parameters, any parameters in the new version will be incompatible
|
||||
res = &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: []*proto.Response{{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Parameters: []*proto.RichParameter{
|
||||
{
|
||||
Name: "new",
|
||||
Mutable: false,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
// Given: the workspace template has been updated
|
||||
newVersion := coderdtest.UpdateTemplateVersion(t, client, orgs[0].ID, res, workspace.TemplateID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, newVersion.ID)
|
||||
require.NoError(t, client.UpdateActiveTemplateVersion(
|
||||
ctx, workspace.TemplateID, codersdk.UpdateActiveTemplateVersion{
|
||||
ID: newVersion.ID,
|
||||
},
|
||||
))
|
||||
|
||||
t.Log("sending autobuild tick")
|
||||
// When: the autobuild executor ticks after the scheduled time
|
||||
go func() {
|
||||
tickCh <- sched.Next(workspace.LatestBuild.CreatedAt)
|
||||
close(tickCh)
|
||||
}()
|
||||
|
||||
stats := <-statsCh
|
||||
assert.NoError(t, stats.Error)
|
||||
if !tc.expectStart {
|
||||
// Then: the workspace should not be started
|
||||
assert.Len(t, stats.Transitions, 0)
|
||||
return
|
||||
}
|
||||
|
||||
// Then: the workspace should be started
|
||||
assert.Len(t, stats.Transitions, 1)
|
||||
assert.Contains(t, stats.Transitions, workspace.ID)
|
||||
assert.Equal(t, database.WorkspaceTransitionStart, stats.Transitions[workspace.ID])
|
||||
ws := coderdtest.MustWorkspace(t, client, workspace.ID)
|
||||
if tc.expectUpdate {
|
||||
// Then: uses the updated version
|
||||
assert.Equal(t, newVersion.ID, ws.LatestBuild.TemplateVersionID,
|
||||
"expected workspace build to be using the updated template version")
|
||||
} else {
|
||||
// Then: uses the previous template version
|
||||
assert.Equal(t, workspace.LatestBuild.TemplateVersionID, ws.LatestBuild.TemplateVersionID,
|
||||
"expected workspace build to be using the old template version")
|
||||
}
|
||||
})
|
||||
// Given: we have a user with a workspace that has autostart enabled
|
||||
workspace = mustProvisionWorkspace(t, client, func(cwr *codersdk.CreateWorkspaceRequest) {
|
||||
cwr.AutostartSchedule = ptr.Ref(sched.String())
|
||||
})
|
||||
)
|
||||
// Given: workspace is stopped
|
||||
workspace = coderdtest.MustTransitionWorkspace(t, client, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
|
||||
|
||||
// Given: the workspace template has been updated
|
||||
orgs, err := client.OrganizationsByUser(ctx, workspace.OwnerID.String())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, orgs, 1)
|
||||
|
||||
newVersion := coderdtest.UpdateTemplateVersion(t, client, orgs[0].ID, nil, workspace.TemplateID)
|
||||
coderdtest.AwaitTemplateVersionJobCompleted(t, client, newVersion.ID)
|
||||
require.NoError(t, client.UpdateActiveTemplateVersion(ctx, workspace.TemplateID, codersdk.UpdateActiveTemplateVersion{
|
||||
ID: newVersion.ID,
|
||||
}))
|
||||
|
||||
// When: the autobuild executor ticks after the scheduled time
|
||||
go func() {
|
||||
tickCh <- sched.Next(workspace.LatestBuild.CreatedAt)
|
||||
close(tickCh)
|
||||
}()
|
||||
|
||||
// Then: the workspace should be started using the previous template version, and not the updated version.
|
||||
stats := <-statsCh
|
||||
assert.NoError(t, stats.Error)
|
||||
assert.Len(t, stats.Transitions, 1)
|
||||
assert.Contains(t, stats.Transitions, workspace.ID)
|
||||
assert.Equal(t, database.WorkspaceTransitionStart, stats.Transitions[workspace.ID])
|
||||
ws := coderdtest.MustWorkspace(t, client, workspace.ID)
|
||||
assert.Equal(t, workspace.LatestBuild.TemplateVersionID, ws.LatestBuild.TemplateVersionID, "expected workspace build to be using the old template version")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecutorAutostartAlreadyRunning(t *testing.T) {
|
||||
|
Reference in New Issue
Block a user