Skip to content

Commit 487bdc2

Browse files
authored
fix(coderd): allow workspaceAgentLogs follow to return on non-latest-build (#9382)
1 parent fea8813 commit 487bdc2

File tree

2 files changed

+109
-1
lines changed

2 files changed

+109
-1
lines changed

coderd/workspaceagents.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/go-chi/chi/v5"
2424
"github.com/google/uuid"
2525
"golang.org/x/exp/maps"
26+
"golang.org/x/exp/slices"
2627
"golang.org/x/mod/semver"
2728
"golang.org/x/sync/errgroup"
2829
"golang.org/x/xerrors"
@@ -481,6 +482,15 @@ func (api *API) workspaceAgentLogs(rw http.ResponseWriter, r *http.Request) {
481482
return
482483
}
483484

485+
workspace, err := api.Database.GetWorkspaceByAgentID(ctx, workspaceAgent.ID)
486+
if err != nil {
487+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
488+
Message: "Internal error fetching workspace by agent id.",
489+
Detail: err.Error(),
490+
})
491+
return
492+
}
493+
484494
api.WebsocketWaitMutex.Lock()
485495
api.WebsocketWaitGroup.Add(1)
486496
api.WebsocketWaitMutex.Unlock()
@@ -556,7 +566,8 @@ func (api *API) workspaceAgentLogs(rw http.ResponseWriter, r *http.Request) {
556566
go func() {
557567
defer close(bufferedLogs)
558568

559-
for {
569+
keepGoing := true
570+
for keepGoing {
560571
select {
561572
case <-ctx.Done():
562573
return
@@ -565,6 +576,18 @@ func (api *API) workspaceAgentLogs(rw http.ResponseWriter, r *http.Request) {
565576
t.Reset(recheckInterval)
566577
}
567578

579+
agents, err := api.Database.GetWorkspaceAgentsInLatestBuildByWorkspaceID(ctx, workspace.ID)
580+
if err != nil {
581+
if xerrors.Is(err, context.Canceled) {
582+
return
583+
}
584+
logger.Warn(ctx, "failed to get workspace agents in latest build", slog.Error(err))
585+
continue
586+
}
587+
// If the agent is no longer in the latest build, we can stop after
588+
// checking once.
589+
keepGoing = slices.ContainsFunc(agents, func(agent database.WorkspaceAgent) bool { return agent.ID == workspaceAgent.ID })
590+
568591
logs, err := api.Database.GetWorkspaceAgentLogsAfter(ctx, database.GetWorkspaceAgentLogsAfterParams{
569592
AgentID: workspaceAgent.ID,
570593
CreatedAfter: lastSentLogID,

coderd/workspaceagents_test.go

+85
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,91 @@ func TestWorkspaceAgentStartupLogs(t *testing.T) {
242242
require.Equal(t, "testing", logChunk[0].Output)
243243
require.Equal(t, "testing2", logChunk[1].Output)
244244
})
245+
t.Run("Close logs on outdated build", func(t *testing.T) {
246+
t.Parallel()
247+
ctx := testutil.Context(t, testutil.WaitMedium)
248+
client := coderdtest.New(t, &coderdtest.Options{
249+
IncludeProvisionerDaemon: true,
250+
})
251+
user := coderdtest.CreateFirstUser(t, client)
252+
authToken := uuid.NewString()
253+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
254+
Parse: echo.ParseComplete,
255+
ProvisionPlan: echo.PlanComplete,
256+
ProvisionApply: []*proto.Response{{
257+
Type: &proto.Response_Apply{
258+
Apply: &proto.ApplyComplete{
259+
Resources: []*proto.Resource{{
260+
Name: "example",
261+
Type: "aws_instance",
262+
Agents: []*proto.Agent{{
263+
Id: uuid.NewString(),
264+
Auth: &proto.Agent_Token{
265+
Token: authToken,
266+
},
267+
}},
268+
}},
269+
},
270+
},
271+
}},
272+
})
273+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
274+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
275+
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
276+
build := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
277+
278+
agentClient := agentsdk.New(client.URL)
279+
agentClient.SetSessionToken(authToken)
280+
err := agentClient.PatchLogs(ctx, agentsdk.PatchLogs{
281+
Logs: []agentsdk.Log{
282+
{
283+
CreatedAt: database.Now(),
284+
Output: "testing",
285+
},
286+
},
287+
})
288+
require.NoError(t, err)
289+
290+
logs, closer, err := client.WorkspaceAgentLogsAfter(ctx, build.Resources[0].Agents[0].ID, 0, true)
291+
require.NoError(t, err)
292+
defer func() {
293+
_ = closer.Close()
294+
}()
295+
296+
first := make(chan struct{})
297+
go func() {
298+
select {
299+
case <-ctx.Done():
300+
assert.Fail(t, "context done while waiting in goroutine")
301+
case <-logs:
302+
close(first)
303+
}
304+
}()
305+
select {
306+
case <-ctx.Done():
307+
require.FailNow(t, "context done while waiting for first log")
308+
case <-first:
309+
}
310+
311+
_ = coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStart)
312+
313+
// Send a new log message to trigger a re-check.
314+
err = agentClient.PatchLogs(ctx, agentsdk.PatchLogs{
315+
Logs: []agentsdk.Log{
316+
{
317+
CreatedAt: database.Now(),
318+
Output: "testing2",
319+
},
320+
},
321+
})
322+
require.NoError(t, err)
323+
324+
select {
325+
case <-ctx.Done():
326+
require.FailNow(t, "context done while waiting for logs close")
327+
case <-logs:
328+
}
329+
})
245330
t.Run("PublishesOnOverflow", func(t *testing.T) {
246331
t.Parallel()
247332
ctx := testutil.Context(t, testutil.WaitMedium)

0 commit comments

Comments
 (0)