mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add link for viewing raw build logs in workspace and template build jobs (#21727)
* Adds support for parameter `format=text` in the following API routes: * `/api/v2/workspaceagents/:id/logs` * `/api/v2/workspacebuilds/:id/logs` * `/api/v2/templateversions/:id/logs` * `/api/v2/templateversions/:id/dry-run/:id/logs` * Adds links to view raw logs on the following pages: * Workspace build page * Template editor page * Template version page * Refactors existing log formatting in `cli/logs.go` to live in `codersdk`. 🤖 Generated with Claude Opus 4.5, reviewed by me. --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -39,6 +40,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/coderd/coderdtest/oidctest"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/db2sdk"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
"github.com/coder/coder/v2/coderd/database/dbfake"
|
||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||
@@ -337,6 +339,97 @@ func TestWorkspaceAgentLogs(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkspaceAgentLogsFormat(t *testing.T) {
|
||||
t.Parallel()
|
||||
client, db := coderdtest.NewWithDatabase(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
|
||||
OrganizationID: user.OrganizationID,
|
||||
OwnerID: user.UserID,
|
||||
}).WithAgent().Do()
|
||||
|
||||
workspaceAgent := r.Agents[0]
|
||||
logSource := dbgen.WorkspaceAgentLogSource(t, db, database.WorkspaceAgentLogSource{
|
||||
WorkspaceAgentID: workspaceAgent.ID,
|
||||
DisplayName: "startup_script",
|
||||
})
|
||||
agentLog := dbgen.WorkspaceAgentLog(t, db, database.WorkspaceAgentLog{
|
||||
AgentID: workspaceAgent.ID,
|
||||
LogSourceID: logSource.ID,
|
||||
Output: "test log output",
|
||||
Level: database.LogLevelInfo,
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
queryParams string
|
||||
expectedStatus int
|
||||
expectedContentType string
|
||||
checkBody func(string)
|
||||
}{
|
||||
{
|
||||
name: "JSON",
|
||||
queryParams: "",
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedContentType: "application/json",
|
||||
checkBody: func(body string) {
|
||||
assert.NotEmpty(t, body)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Text",
|
||||
queryParams: "?format=text",
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedContentType: "text/plain",
|
||||
checkBody: func(body string) {
|
||||
expected := db2sdk.WorkspaceAgentLog(agentLog).Text(workspaceAgent.Name, logSource.DisplayName)
|
||||
assert.Contains(t, body, expected)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "InvalidFormat",
|
||||
queryParams: "?format=invalid",
|
||||
expectedStatus: http.StatusBadRequest,
|
||||
checkBody: func(body string) {
|
||||
assert.Contains(t, body, "Invalid format")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "TextWithFollowFails",
|
||||
queryParams: "?format=text&follow",
|
||||
expectedStatus: http.StatusBadRequest,
|
||||
checkBody: func(body string) {
|
||||
assert.Contains(t, body, "not supported with follow mode")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
urlPath := fmt.Sprintf("/api/v2/workspaceagents/%s/logs%s", workspaceAgent.ID, tt.queryParams)
|
||||
|
||||
res, err := client.Request(ctx, http.MethodGet, urlPath, nil)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
|
||||
require.Equal(t, tt.expectedStatus, res.StatusCode)
|
||||
if tt.expectedContentType != "" {
|
||||
require.Contains(t, res.Header.Get("Content-Type"), tt.expectedContentType)
|
||||
}
|
||||
|
||||
if assert.NotNil(t, tt.checkBody) {
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
tt.checkBody(string(body))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorkspaceAgentAppStatus(t *testing.T) {
|
||||
t.Parallel()
|
||||
client, db := coderdtest.NewWithDatabase(t, nil)
|
||||
|
||||
Reference in New Issue
Block a user