diff --git a/agent/agent.go b/agent/agent.go index d736564056791..aafaa3849c109 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -79,7 +79,7 @@ type Client interface { PostAppHealth(ctx context.Context, req agentsdk.PostAppHealthsRequest) error PostStartup(ctx context.Context, req agentsdk.PostStartupRequest) error PostMetadata(ctx context.Context, key string, req agentsdk.PostMetadataRequest) error - PatchStartupLogs(ctx context.Context, req agentsdk.PatchStartupLogs) error + PatchLogs(ctx context.Context, req agentsdk.PatchLogs) error GetServiceBanner(ctx context.Context) (codersdk.ServiceBannerConfig, error) } @@ -1006,7 +1006,7 @@ func (a *agent) runScript(ctx context.Context, lifecycle, script string) (err er var stdout, stderr io.Writer = fileWriter, fileWriter if lifecycle == "startup" { - send, flushAndClose := agentsdk.StartupLogsSender(a.client.PatchStartupLogs, logger) + send, flushAndClose := agentsdk.LogsSender(a.client.PatchLogs, logger) // If ctx is canceled here (or in a writer below), we may be // discarding logs, but that's okay because we're shutting down // anyway. We could consider creating a new context here if we @@ -1017,9 +1017,9 @@ func (a *agent) runScript(ctx context.Context, lifecycle, script string) (err er } }() - infoW := agentsdk.StartupLogsWriter(ctx, send, codersdk.LogLevelInfo) + infoW := agentsdk.StartupLogsWriter(ctx, send, codersdk.WorkspaceAgentLogSourceStartupScript, codersdk.LogLevelInfo) defer infoW.Close() - errW := agentsdk.StartupLogsWriter(ctx, send, codersdk.LogLevelError) + errW := agentsdk.StartupLogsWriter(ctx, send, codersdk.WorkspaceAgentLogSourceStartupScript, codersdk.LogLevelError) defer errW.Close() stdout = io.MultiWriter(fileWriter, infoW) diff --git a/agent/agenttest/client.go b/agent/agenttest/client.go index 81b0fcecb8d22..cc9d365b5331d 100644 --- a/agent/agenttest/client.go +++ b/agent/agenttest/client.go @@ -55,7 +55,7 @@ type Client struct { mu sync.Mutex // Protects following. lifecycleStates []codersdk.WorkspaceAgentLifecycle startup agentsdk.PostStartupRequest - logs []agentsdk.StartupLog + logs []agentsdk.Log derpMapUpdates chan agentsdk.DERPMapUpdate } @@ -161,13 +161,13 @@ func (c *Client) PostStartup(ctx context.Context, startup agentsdk.PostStartupRe return nil } -func (c *Client) GetStartupLogs() []agentsdk.StartupLog { +func (c *Client) GetStartupLogs() []agentsdk.Log { c.mu.Lock() defer c.mu.Unlock() return c.logs } -func (c *Client) PatchStartupLogs(ctx context.Context, logs agentsdk.PatchStartupLogs) error { +func (c *Client) PatchLogs(ctx context.Context, logs agentsdk.PatchLogs) error { c.mu.Lock() defer c.mu.Unlock() if c.PatchWorkspaceLogs != nil { diff --git a/cli/cliui/agent.go b/cli/cliui/agent.go index fcaa1de54ac67..faad31f411f90 100644 --- a/cli/cliui/agent.go +++ b/cli/cliui/agent.go @@ -16,7 +16,7 @@ var errAgentShuttingDown = xerrors.New("agent is shutting down") type AgentOptions struct { FetchInterval time.Duration Fetch func(ctx context.Context, agentID uuid.UUID) (codersdk.WorkspaceAgent, error) - FetchLogs func(ctx context.Context, agentID uuid.UUID, after int64, follow bool) (<-chan []codersdk.WorkspaceAgentStartupLog, io.Closer, error) + FetchLogs func(ctx context.Context, agentID uuid.UUID, after int64, follow bool) (<-chan []codersdk.WorkspaceAgentLog, io.Closer, error) Wait bool // If true, wait for the agent to be ready (startup script). } @@ -29,8 +29,8 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO opts.FetchInterval = 500 * time.Millisecond } if opts.FetchLogs == nil { - opts.FetchLogs = func(_ context.Context, _ uuid.UUID, _ int64, _ bool) (<-chan []codersdk.WorkspaceAgentStartupLog, io.Closer, error) { - c := make(chan []codersdk.WorkspaceAgentStartupLog) + opts.FetchLogs = func(_ context.Context, _ uuid.UUID, _ int64, _ bool) (<-chan []codersdk.WorkspaceAgentLog, io.Closer, error) { + c := make(chan []codersdk.WorkspaceAgentLog) close(c) return c, closeFunc(func() error { return nil }), nil } @@ -137,7 +137,7 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO } defer logsCloser.Close() - var lastLog codersdk.WorkspaceAgentStartupLog + var lastLog codersdk.WorkspaceAgentLog fetchedAgentWhileFollowing := fetchedAgent if !follow { fetchedAgentWhileFollowing = nil diff --git a/cli/cliui/agent_test.go b/cli/cliui/agent_test.go index e0e0485778f41..c910bf11c301a 100644 --- a/cli/cliui/agent_test.go +++ b/cli/cliui/agent_test.go @@ -27,8 +27,8 @@ func TestAgent(t *testing.T) { for _, tc := range []struct { name string - iter []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentStartupLog) error - logs chan []codersdk.WorkspaceAgentStartupLog + iter []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentLog) error + logs chan []codersdk.WorkspaceAgentLog opts cliui.AgentOptions want []string wantErr bool @@ -38,12 +38,12 @@ func TestAgent(t *testing.T) { opts: cliui.AgentOptions{ FetchInterval: time.Millisecond, }, - iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentStartupLog) error{ - func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentStartupLog) error { + iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentLog) error{ + func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentLog) error { agent.Status = codersdk.WorkspaceAgentConnecting return nil }, - func(_ context.Context, agent *codersdk.WorkspaceAgent, logs chan []codersdk.WorkspaceAgentStartupLog) error { + func(_ context.Context, agent *codersdk.WorkspaceAgent, logs chan []codersdk.WorkspaceAgentLog) error { agent.Status = codersdk.WorkspaceAgentConnected agent.FirstConnectedAt = ptr.Ref(time.Now()) return nil @@ -62,18 +62,18 @@ func TestAgent(t *testing.T) { opts: cliui.AgentOptions{ FetchInterval: 1 * time.Millisecond, }, - iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentStartupLog) error{ - func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentStartupLog) error { + iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentLog) error{ + func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentLog) error { agent.Status = codersdk.WorkspaceAgentConnecting agent.LifecycleState = codersdk.WorkspaceAgentLifecycleStarting agent.StartedAt = ptr.Ref(time.Now()) return nil }, - func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentStartupLog) error { + func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentLog) error { agent.Status = codersdk.WorkspaceAgentTimeout return nil }, - func(_ context.Context, agent *codersdk.WorkspaceAgent, logs chan []codersdk.WorkspaceAgentStartupLog) error { + func(_ context.Context, agent *codersdk.WorkspaceAgent, logs chan []codersdk.WorkspaceAgentLog) error { agent.Status = codersdk.WorkspaceAgentConnected agent.FirstConnectedAt = ptr.Ref(time.Now()) agent.LifecycleState = codersdk.WorkspaceAgentLifecycleReady @@ -95,8 +95,8 @@ func TestAgent(t *testing.T) { opts: cliui.AgentOptions{ FetchInterval: 1 * time.Millisecond, }, - iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentStartupLog) error{ - func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentStartupLog) error { + iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentLog) error{ + func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentLog) error { agent.Status = codersdk.WorkspaceAgentDisconnected agent.FirstConnectedAt = ptr.Ref(time.Now().Add(-1 * time.Minute)) agent.LastConnectedAt = ptr.Ref(time.Now().Add(-1 * time.Minute)) @@ -106,7 +106,7 @@ func TestAgent(t *testing.T) { agent.ReadyAt = ptr.Ref(time.Now()) return nil }, - func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentStartupLog) error { + func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentLog) error { agent.Status = codersdk.WorkspaceAgentConnected agent.LastConnectedAt = ptr.Ref(time.Now()) return nil @@ -125,13 +125,13 @@ func TestAgent(t *testing.T) { FetchInterval: time.Millisecond, Wait: true, }, - iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentStartupLog) error{ - func(_ context.Context, agent *codersdk.WorkspaceAgent, logs chan []codersdk.WorkspaceAgentStartupLog) error { + iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentLog) error{ + func(_ context.Context, agent *codersdk.WorkspaceAgent, logs chan []codersdk.WorkspaceAgentLog) error { agent.Status = codersdk.WorkspaceAgentConnected agent.FirstConnectedAt = ptr.Ref(time.Now()) agent.LifecycleState = codersdk.WorkspaceAgentLifecycleStarting agent.StartedAt = ptr.Ref(time.Now()) - logs <- []codersdk.WorkspaceAgentStartupLog{ + logs <- []codersdk.WorkspaceAgentLog{ { CreatedAt: time.Now(), Output: "Hello world", @@ -139,10 +139,10 @@ func TestAgent(t *testing.T) { } return nil }, - func(_ context.Context, agent *codersdk.WorkspaceAgent, logs chan []codersdk.WorkspaceAgentStartupLog) error { + func(_ context.Context, agent *codersdk.WorkspaceAgent, logs chan []codersdk.WorkspaceAgentLog) error { agent.LifecycleState = codersdk.WorkspaceAgentLifecycleReady agent.ReadyAt = ptr.Ref(time.Now()) - logs <- []codersdk.WorkspaceAgentStartupLog{ + logs <- []codersdk.WorkspaceAgentLog{ { CreatedAt: time.Now(), Output: "Bye now", @@ -164,14 +164,14 @@ func TestAgent(t *testing.T) { FetchInterval: time.Millisecond, Wait: true, }, - iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentStartupLog) error{ - func(_ context.Context, agent *codersdk.WorkspaceAgent, logs chan []codersdk.WorkspaceAgentStartupLog) error { + iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentLog) error{ + func(_ context.Context, agent *codersdk.WorkspaceAgent, logs chan []codersdk.WorkspaceAgentLog) error { agent.Status = codersdk.WorkspaceAgentConnected agent.FirstConnectedAt = ptr.Ref(time.Now()) agent.StartedAt = ptr.Ref(time.Now()) agent.LifecycleState = codersdk.WorkspaceAgentLifecycleStartError agent.ReadyAt = ptr.Ref(time.Now()) - logs <- []codersdk.WorkspaceAgentStartupLog{ + logs <- []codersdk.WorkspaceAgentLog{ { CreatedAt: time.Now(), Output: "Hello world", @@ -193,8 +193,8 @@ func TestAgent(t *testing.T) { opts: cliui.AgentOptions{ FetchInterval: time.Millisecond, }, - iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentStartupLog) error{ - func(_ context.Context, agent *codersdk.WorkspaceAgent, logs chan []codersdk.WorkspaceAgentStartupLog) error { + iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentLog) error{ + func(_ context.Context, agent *codersdk.WorkspaceAgent, logs chan []codersdk.WorkspaceAgentLog) error { agent.Status = codersdk.WorkspaceAgentDisconnected agent.LifecycleState = codersdk.WorkspaceAgentLifecycleOff return nil @@ -208,13 +208,13 @@ func TestAgent(t *testing.T) { FetchInterval: time.Millisecond, Wait: true, }, - iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentStartupLog) error{ - func(_ context.Context, agent *codersdk.WorkspaceAgent, logs chan []codersdk.WorkspaceAgentStartupLog) error { + iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentLog) error{ + func(_ context.Context, agent *codersdk.WorkspaceAgent, logs chan []codersdk.WorkspaceAgentLog) error { agent.Status = codersdk.WorkspaceAgentConnected agent.FirstConnectedAt = ptr.Ref(time.Now()) agent.LifecycleState = codersdk.WorkspaceAgentLifecycleStarting agent.StartedAt = ptr.Ref(time.Now()) - logs <- []codersdk.WorkspaceAgentStartupLog{ + logs <- []codersdk.WorkspaceAgentLog{ { CreatedAt: time.Now(), Output: "Hello world", @@ -222,7 +222,7 @@ func TestAgent(t *testing.T) { } return nil }, - func(_ context.Context, agent *codersdk.WorkspaceAgent, logs chan []codersdk.WorkspaceAgentStartupLog) error { + func(_ context.Context, agent *codersdk.WorkspaceAgent, logs chan []codersdk.WorkspaceAgentLog) error { agent.ReadyAt = ptr.Ref(time.Now()) agent.LifecycleState = codersdk.WorkspaceAgentLifecycleShuttingDown return nil @@ -241,12 +241,12 @@ func TestAgent(t *testing.T) { FetchInterval: time.Millisecond, Wait: true, }, - iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentStartupLog) error{ - func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentStartupLog) error { + iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentLog) error{ + func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentLog) error { agent.Status = codersdk.WorkspaceAgentConnecting return nil }, - func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentStartupLog) error { + func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentLog) error { return xerrors.New("bad") }, }, @@ -261,13 +261,13 @@ func TestAgent(t *testing.T) { FetchInterval: time.Millisecond, Wait: true, }, - iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentStartupLog) error{ - func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentStartupLog) error { + iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentLog) error{ + func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentLog) error { agent.Status = codersdk.WorkspaceAgentTimeout agent.TroubleshootingURL = "https://troubleshoot" return nil }, - func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentStartupLog) error { + func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentLog) error { return xerrors.New("bad") }, }, @@ -294,7 +294,7 @@ func TestAgent(t *testing.T) { CreatedAt: time.Now(), LifecycleState: codersdk.WorkspaceAgentLifecycleCreated, } - logs := make(chan []codersdk.WorkspaceAgentStartupLog, 1) + logs := make(chan []codersdk.WorkspaceAgentLog, 1) cmd := &clibase.Cmd{ Handler: func(inv *clibase.Invocation) error { @@ -306,12 +306,12 @@ func TestAgent(t *testing.T) { } return agent, err } - tc.opts.FetchLogs = func(ctx context.Context, _ uuid.UUID, _ int64, follow bool) (<-chan []codersdk.WorkspaceAgentStartupLog, io.Closer, error) { + tc.opts.FetchLogs = func(ctx context.Context, _ uuid.UUID, _ int64, follow bool) (<-chan []codersdk.WorkspaceAgentLog, io.Closer, error) { if follow { return logs, closeFunc(func() error { return nil }), nil } - fetchLogs := make(chan []codersdk.WorkspaceAgentStartupLog, 1) + fetchLogs := make(chan []codersdk.WorkspaceAgentLog, 1) select { case <-ctx.Done(): return nil, nil, ctx.Err() diff --git a/cli/ssh.go b/cli/ssh.go index 4bf6f021b4881..2db1f4b4e2cb4 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -186,7 +186,7 @@ func (r *RootCmd) ssh() *clibase.Cmd { // This is required in "stdio" mode so a connecting indicator can be displayed. err = cliui.Agent(ctx, inv.Stderr, workspaceAgent.ID, cliui.AgentOptions{ Fetch: client.WorkspaceAgent, - FetchLogs: client.WorkspaceAgentStartupLogsAfter, + FetchLogs: client.WorkspaceAgentLogsAfter, Wait: wait, }) if err != nil { diff --git a/cmd/cliui/main.go b/cmd/cliui/main.go index cebf354cf44b9..60e8f6536f7a8 100644 --- a/cmd/cliui/main.go +++ b/cmd/cliui/main.go @@ -167,7 +167,7 @@ func main() { Use: "agent", Handler: func(inv *clibase.Invocation) error { var agent codersdk.WorkspaceAgent - var logs []codersdk.WorkspaceAgentStartupLog + var logs []codersdk.WorkspaceAgentLog fetchSteps := []func(){ func() { @@ -191,7 +191,7 @@ func main() { if rand.Float64() > 0.75 { //nolint:gosec level = codersdk.LogLevelError } - logs = append(logs, codersdk.WorkspaceAgentStartupLog{ + logs = append(logs, codersdk.WorkspaceAgentLog{ CreatedAt: time.Now().Add(-time.Duration(10-i) * 144 * time.Millisecond), Output: fmt.Sprintf("Some log %d", i), Level: level, @@ -226,13 +226,13 @@ func main() { step() return agent, nil }, - FetchLogs: func(_ context.Context, _ uuid.UUID, _ int64, follow bool) (<-chan []codersdk.WorkspaceAgentStartupLog, io.Closer, error) { - logsC := make(chan []codersdk.WorkspaceAgentStartupLog, len(logs)) + FetchLogs: func(_ context.Context, _ uuid.UUID, _ int64, follow bool) (<-chan []codersdk.WorkspaceAgentLog, io.Closer, error) { + logsC := make(chan []codersdk.WorkspaceAgentLog, len(logs)) if follow { go func() { defer close(logsC) for _, log := range logs { - logsC <- []codersdk.WorkspaceAgentStartupLog{log} + logsC <- []codersdk.WorkspaceAgentLog{log} time.Sleep(144 * time.Millisecond) } agent.LifecycleState = codersdk.WorkspaceAgentLifecycleReady diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 19012d4110193..f2f94e2f485a7 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -4565,6 +4565,45 @@ const docTemplate = `{ } } }, + "/workspaceagents/me/logs": { + "patch": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Agents" + ], + "summary": "Patch workspace agent logs", + "operationId": "patch-workspace-agent-logs", + "parameters": [ + { + "description": "logs", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/agentsdk.PatchLogs" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.Response" + } + } + } + } + }, "/workspaceagents/me/manifest": { "get": { "security": [ @@ -4764,16 +4803,16 @@ const docTemplate = `{ "tags": [ "Agents" ], - "summary": "Patch workspace agent startup logs", - "operationId": "patch-workspace-agent-startup-logs", + "summary": "Removed: Patch workspace agent logs", + "operationId": "removed-patch-workspace-agent-logs", "parameters": [ { - "description": "Startup logs", + "description": "logs", "name": "request", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/agentsdk.PatchStartupLogs" + "$ref": "#/definitions/agentsdk.PatchLogs" } } ], @@ -4959,6 +4998,68 @@ const docTemplate = `{ } } }, + "/workspaceagents/{workspaceagent}/logs": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Agents" + ], + "summary": "Get logs by workspace agent", + "operationId": "get-logs-by-workspace-agent", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Workspace agent ID", + "name": "workspaceagent", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Before log id", + "name": "before", + "in": "query" + }, + { + "type": "integer", + "description": "After log id", + "name": "after", + "in": "query" + }, + { + "type": "boolean", + "description": "Follow log stream", + "name": "follow", + "in": "query" + }, + { + "type": "boolean", + "description": "Disable compression for WebSocket connection", + "name": "no_compression", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.WorkspaceAgentLog" + } + } + } + } + } + }, "/workspaceagents/{workspaceagent}/pty": { "get": { "security": [ @@ -5001,8 +5102,8 @@ const docTemplate = `{ "tags": [ "Agents" ], - "summary": "Get startup logs by workspace agent", - "operationId": "get-startup-logs-by-workspace-agent", + "summary": "Removed: Get logs by workspace agent", + "operationId": "removed-get-logs-by-workspace-agent", "parameters": [ { "type": "string", @@ -5043,7 +5144,7 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/codersdk.WorkspaceAgentStartupLog" + "$ref": "#/definitions/codersdk.WorkspaceAgentLog" } } } @@ -6213,6 +6314,23 @@ const docTemplate = `{ } } }, + "agentsdk.Log": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "level": { + "$ref": "#/definitions/codersdk.LogLevel" + }, + "output": { + "type": "string" + }, + "source": { + "$ref": "#/definitions/codersdk.WorkspaceAgentLogSource" + } + } + }, "agentsdk.Manifest": { "type": "object", "properties": { @@ -6270,13 +6388,13 @@ const docTemplate = `{ } } }, - "agentsdk.PatchStartupLogs": { + "agentsdk.PatchLogs": { "type": "object", "properties": { "logs": { "type": "array", "items": { - "$ref": "#/definitions/agentsdk.StartupLog" + "$ref": "#/definitions/agentsdk.Log" } } } @@ -6337,20 +6455,6 @@ const docTemplate = `{ } } }, - "agentsdk.StartupLog": { - "type": "object", - "properties": { - "created_at": { - "type": "string" - }, - "level": { - "$ref": "#/definitions/codersdk.LogLevel" - }, - "output": { - "type": "string" - } - } - }, "agentsdk.Stats": { "type": "object", "properties": { @@ -10325,6 +10429,12 @@ const docTemplate = `{ "description": "Deprecated: Use StartupScriptBehavior instead.", "type": "boolean" }, + "logs_length": { + "type": "integer" + }, + "logs_overflowed": { + "type": "boolean" + }, "name": { "type": "string" }, @@ -10349,12 +10459,6 @@ const docTemplate = `{ "type": "string", "format": "date-time" }, - "startup_logs_length": { - "type": "integer" - }, - "startup_logs_overflowed": { - "type": "boolean" - }, "startup_script": { "type": "string" }, @@ -10462,41 +10566,60 @@ const docTemplate = `{ } } }, - "codersdk.WorkspaceAgentMetadataDescription": { + "codersdk.WorkspaceAgentLog": { "type": "object", "properties": { - "display_name": { - "type": "string" + "created_at": { + "type": "string", + "format": "date-time" }, - "interval": { + "id": { "type": "integer" }, - "key": { - "type": "string" + "level": { + "$ref": "#/definitions/codersdk.LogLevel" }, - "script": { + "output": { "type": "string" - }, - "timeout": { - "type": "integer" } } }, - "codersdk.WorkspaceAgentStartupLog": { + "codersdk.WorkspaceAgentLogSource": { + "type": "string", + "enum": [ + "startup_script", + "shutdown_script", + "kubernetes", + "envbox", + "envbuilder", + "external" + ], + "x-enum-varnames": [ + "WorkspaceAgentLogSourceStartupScript", + "WorkspaceAgentLogSourceShutdownScript", + "WorkspaceAgentLogSourceKubernetes", + "WorkspaceAgentLogSourceEnvbox", + "WorkspaceAgentLogSourceEnvbuilder", + "WorkspaceAgentLogSourceExternal" + ] + }, + "codersdk.WorkspaceAgentMetadataDescription": { "type": "object", "properties": { - "created_at": { - "type": "string", - "format": "date-time" + "display_name": { + "type": "string" }, - "id": { + "interval": { "type": "integer" }, - "level": { - "$ref": "#/definitions/codersdk.LogLevel" + "key": { + "type": "string" }, - "output": { + "script": { "type": "string" + }, + "timeout": { + "type": "integer" } } }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index f5899353f00a6..6e1ea2af7fe44 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -4017,6 +4017,39 @@ } } }, + "/workspaceagents/me/logs": { + "patch": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "consumes": ["application/json"], + "produces": ["application/json"], + "tags": ["Agents"], + "summary": "Patch workspace agent logs", + "operationId": "patch-workspace-agent-logs", + "parameters": [ + { + "description": "logs", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/agentsdk.PatchLogs" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/codersdk.Response" + } + } + } + } + }, "/workspaceagents/me/manifest": { "get": { "security": [ @@ -4186,16 +4219,16 @@ "consumes": ["application/json"], "produces": ["application/json"], "tags": ["Agents"], - "summary": "Patch workspace agent startup logs", - "operationId": "patch-workspace-agent-startup-logs", + "summary": "Removed: Patch workspace agent logs", + "operationId": "removed-patch-workspace-agent-logs", "parameters": [ { - "description": "Startup logs", + "description": "logs", "name": "request", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/agentsdk.PatchStartupLogs" + "$ref": "#/definitions/agentsdk.PatchLogs" } } ], @@ -4363,6 +4396,64 @@ } } }, + "/workspaceagents/{workspaceagent}/logs": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Agents"], + "summary": "Get logs by workspace agent", + "operationId": "get-logs-by-workspace-agent", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Workspace agent ID", + "name": "workspaceagent", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Before log id", + "name": "before", + "in": "query" + }, + { + "type": "integer", + "description": "After log id", + "name": "after", + "in": "query" + }, + { + "type": "boolean", + "description": "Follow log stream", + "name": "follow", + "in": "query" + }, + { + "type": "boolean", + "description": "Disable compression for WebSocket connection", + "name": "no_compression", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.WorkspaceAgentLog" + } + } + } + } + } + }, "/workspaceagents/{workspaceagent}/pty": { "get": { "security": [ @@ -4399,8 +4490,8 @@ ], "produces": ["application/json"], "tags": ["Agents"], - "summary": "Get startup logs by workspace agent", - "operationId": "get-startup-logs-by-workspace-agent", + "summary": "Removed: Get logs by workspace agent", + "operationId": "removed-get-logs-by-workspace-agent", "parameters": [ { "type": "string", @@ -4441,7 +4532,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/codersdk.WorkspaceAgentStartupLog" + "$ref": "#/definitions/codersdk.WorkspaceAgentLog" } } } @@ -5473,6 +5564,23 @@ } } }, + "agentsdk.Log": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "level": { + "$ref": "#/definitions/codersdk.LogLevel" + }, + "output": { + "type": "string" + }, + "source": { + "$ref": "#/definitions/codersdk.WorkspaceAgentLogSource" + } + } + }, "agentsdk.Manifest": { "type": "object", "properties": { @@ -5530,13 +5638,13 @@ } } }, - "agentsdk.PatchStartupLogs": { + "agentsdk.PatchLogs": { "type": "object", "properties": { "logs": { "type": "array", "items": { - "$ref": "#/definitions/agentsdk.StartupLog" + "$ref": "#/definitions/agentsdk.Log" } } } @@ -5597,20 +5705,6 @@ } } }, - "agentsdk.StartupLog": { - "type": "object", - "properties": { - "created_at": { - "type": "string" - }, - "level": { - "$ref": "#/definitions/codersdk.LogLevel" - }, - "output": { - "type": "string" - } - } - }, "agentsdk.Stats": { "type": "object", "properties": { @@ -9354,6 +9448,12 @@ "description": "Deprecated: Use StartupScriptBehavior instead.", "type": "boolean" }, + "logs_length": { + "type": "integer" + }, + "logs_overflowed": { + "type": "boolean" + }, "name": { "type": "string" }, @@ -9378,12 +9478,6 @@ "type": "string", "format": "date-time" }, - "startup_logs_length": { - "type": "integer" - }, - "startup_logs_overflowed": { - "type": "boolean" - }, "startup_script": { "type": "string" }, @@ -9491,41 +9585,60 @@ } } }, - "codersdk.WorkspaceAgentMetadataDescription": { + "codersdk.WorkspaceAgentLog": { "type": "object", "properties": { - "display_name": { - "type": "string" + "created_at": { + "type": "string", + "format": "date-time" }, - "interval": { + "id": { "type": "integer" }, - "key": { - "type": "string" + "level": { + "$ref": "#/definitions/codersdk.LogLevel" }, - "script": { + "output": { "type": "string" - }, - "timeout": { - "type": "integer" } } }, - "codersdk.WorkspaceAgentStartupLog": { + "codersdk.WorkspaceAgentLogSource": { + "type": "string", + "enum": [ + "startup_script", + "shutdown_script", + "kubernetes", + "envbox", + "envbuilder", + "external" + ], + "x-enum-varnames": [ + "WorkspaceAgentLogSourceStartupScript", + "WorkspaceAgentLogSourceShutdownScript", + "WorkspaceAgentLogSourceKubernetes", + "WorkspaceAgentLogSourceEnvbox", + "WorkspaceAgentLogSourceEnvbuilder", + "WorkspaceAgentLogSourceExternal" + ] + }, + "codersdk.WorkspaceAgentMetadataDescription": { "type": "object", "properties": { - "created_at": { - "type": "string", - "format": "date-time" + "display_name": { + "type": "string" }, - "id": { + "interval": { "type": "integer" }, - "level": { - "$ref": "#/definitions/codersdk.LogLevel" + "key": { + "type": "string" }, - "output": { + "script": { "type": "string" + }, + "timeout": { + "type": "integer" } } }, diff --git a/coderd/coderd.go b/coderd/coderd.go index 853d874ce6fa0..d7b80ff273097 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -776,7 +776,8 @@ func New(options *Options) *API { // New agents will use /me/manifest instead. r.Get("/metadata", api.workspaceAgentManifest) r.Post("/startup", api.postWorkspaceAgentStartup) - r.Patch("/startup-logs", api.patchWorkspaceAgentStartupLogs) + r.Patch("/startup-logs", api.patchWorkspaceAgentLogsDeprecated) + r.Patch("/logs", api.patchWorkspaceAgentLogs) r.Post("/app-health", api.postWorkspaceAppHealth) r.Get("/gitauth", api.workspaceAgentsGitAuth) r.Get("/gitsshkey", api.agentGitSSHKey) @@ -800,7 +801,8 @@ func New(options *Options) *API { ) r.Get("/", api.workspaceAgent) r.Get("/watch-metadata", api.watchWorkspaceAgentMetadata) - r.Get("/startup-logs", api.workspaceAgentStartupLogs) + r.Get("/startup-logs", api.workspaceAgentLogsDeprecated) + r.Get("/logs", api.workspaceAgentLogs) r.Get("/listening-ports", api.workspaceAgentListeningPorts) r.Get("/connection", api.workspaceAgentConnection) r.Get("/coordinate", api.workspaceAgentClientCoordinate) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index f59436266c4ed..a548ac0da2947 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -722,11 +722,11 @@ func (q *querier) DeleteLicense(ctx context.Context, id int32) (int32, error) { return id, nil } -func (q *querier) DeleteOldWorkspaceAgentStartupLogs(ctx context.Context) error { +func (q *querier) DeleteOldWorkspaceAgentLogs(ctx context.Context) error { if err := q.authorizeContext(ctx, rbac.ActionDelete, rbac.ResourceSystem); err != nil { return err } - return q.db.DeleteOldWorkspaceAgentStartupLogs(ctx) + return q.db.DeleteOldWorkspaceAgentLogs(ctx) } func (q *querier) DeleteOldWorkspaceAgentStats(ctx context.Context) error { @@ -1444,6 +1444,14 @@ func (q *querier) GetWorkspaceAgentLifecycleStateByID(ctx context.Context, id uu return q.db.GetWorkspaceAgentLifecycleStateByID(ctx, id) } +func (q *querier) GetWorkspaceAgentLogsAfter(ctx context.Context, arg database.GetWorkspaceAgentLogsAfterParams) ([]database.WorkspaceAgentLog, error) { + _, err := q.GetWorkspaceAgentByID(ctx, arg.AgentID) + if err != nil { + return nil, err + } + return q.db.GetWorkspaceAgentLogsAfter(ctx, arg) +} + func (q *querier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAgentID uuid.UUID) ([]database.WorkspaceAgentMetadatum, error) { workspace, err := q.db.GetWorkspaceByAgentID(ctx, workspaceAgentID) if err != nil { @@ -1458,14 +1466,6 @@ func (q *querier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAgentI return q.db.GetWorkspaceAgentMetadata(ctx, workspaceAgentID) } -func (q *querier) GetWorkspaceAgentStartupLogsAfter(ctx context.Context, arg database.GetWorkspaceAgentStartupLogsAfterParams) ([]database.WorkspaceAgentStartupLog, error) { - _, err := q.GetWorkspaceAgentByID(ctx, arg.AgentID) - if err != nil { - return nil, err - } - return q.db.GetWorkspaceAgentStartupLogsAfter(ctx, arg) -} - func (q *querier) GetWorkspaceAgentStats(ctx context.Context, createdAfter time.Time) ([]database.GetWorkspaceAgentStatsRow, error) { return q.db.GetWorkspaceAgentStats(ctx, createdAfter) } @@ -1926,6 +1926,10 @@ func (q *querier) InsertWorkspaceAgent(ctx context.Context, arg database.InsertW return q.db.InsertWorkspaceAgent(ctx, arg) } +func (q *querier) InsertWorkspaceAgentLogs(ctx context.Context, arg database.InsertWorkspaceAgentLogsParams) ([]database.WorkspaceAgentLog, error) { + return q.db.InsertWorkspaceAgentLogs(ctx, arg) +} + func (q *querier) InsertWorkspaceAgentMetadata(ctx context.Context, arg database.InsertWorkspaceAgentMetadataParams) error { // We don't check for workspace ownership here since the agent metadata may // be associated with an orphaned agent used by a dry run build. @@ -1936,10 +1940,6 @@ func (q *querier) InsertWorkspaceAgentMetadata(ctx context.Context, arg database return q.db.InsertWorkspaceAgentMetadata(ctx, arg) } -func (q *querier) InsertWorkspaceAgentStartupLogs(ctx context.Context, arg database.InsertWorkspaceAgentStartupLogsParams) ([]database.WorkspaceAgentStartupLog, error) { - return q.db.InsertWorkspaceAgentStartupLogs(ctx, arg) -} - func (q *querier) InsertWorkspaceAgentStat(ctx context.Context, arg database.InsertWorkspaceAgentStatParams) (database.WorkspaceAgentStat, error) { // TODO: This is a workspace agent operation. Should users be able to query this? // Not really sure what this is for. @@ -2404,39 +2404,39 @@ func (q *querier) UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, ar return q.db.UpdateWorkspaceAgentLifecycleStateByID(ctx, arg) } -func (q *querier) UpdateWorkspaceAgentMetadata(ctx context.Context, arg database.UpdateWorkspaceAgentMetadataParams) error { - workspace, err := q.db.GetWorkspaceByAgentID(ctx, arg.WorkspaceAgentID) +func (q *querier) UpdateWorkspaceAgentLogOverflowByID(ctx context.Context, arg database.UpdateWorkspaceAgentLogOverflowByIDParams) error { + agent, err := q.db.GetWorkspaceAgentByID(ctx, arg.ID) if err != nil { return err } - err = q.authorizeContext(ctx, rbac.ActionUpdate, workspace) + workspace, err := q.db.GetWorkspaceByAgentID(ctx, agent.ID) if err != nil { return err } - return q.db.UpdateWorkspaceAgentMetadata(ctx, arg) -} - -func (q *querier) UpdateWorkspaceAgentStartupByID(ctx context.Context, arg database.UpdateWorkspaceAgentStartupByIDParams) error { - agent, err := q.db.GetWorkspaceAgentByID(ctx, arg.ID) - if err != nil { + if err := q.authorizeContext(ctx, rbac.ActionUpdate, workspace); err != nil { return err } - workspace, err := q.db.GetWorkspaceByAgentID(ctx, agent.ID) + return q.db.UpdateWorkspaceAgentLogOverflowByID(ctx, arg) +} + +func (q *querier) UpdateWorkspaceAgentMetadata(ctx context.Context, arg database.UpdateWorkspaceAgentMetadataParams) error { + workspace, err := q.db.GetWorkspaceByAgentID(ctx, arg.WorkspaceAgentID) if err != nil { return err } - if err := q.authorizeContext(ctx, rbac.ActionUpdate, workspace); err != nil { + err = q.authorizeContext(ctx, rbac.ActionUpdate, workspace) + if err != nil { return err } - return q.db.UpdateWorkspaceAgentStartupByID(ctx, arg) + return q.db.UpdateWorkspaceAgentMetadata(ctx, arg) } -func (q *querier) UpdateWorkspaceAgentStartupLogOverflowByID(ctx context.Context, arg database.UpdateWorkspaceAgentStartupLogOverflowByIDParams) error { +func (q *querier) UpdateWorkspaceAgentStartupByID(ctx context.Context, arg database.UpdateWorkspaceAgentStartupByIDParams) error { agent, err := q.db.GetWorkspaceAgentByID(ctx, arg.ID) if err != nil { return err @@ -2451,7 +2451,7 @@ func (q *querier) UpdateWorkspaceAgentStartupLogOverflowByID(ctx context.Context return err } - return q.db.UpdateWorkspaceAgentStartupLogOverflowByID(ctx, arg) + return q.db.UpdateWorkspaceAgentStartupByID(ctx, arg) } func (q *querier) UpdateWorkspaceAppHealthByID(ctx context.Context, arg database.UpdateWorkspaceAppHealthByIDParams) error { diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index e13c64778f831..76e541f31b013 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -1053,14 +1053,14 @@ func (s *MethodTestSuite) TestWorkspace() { LifecycleState: database.WorkspaceAgentLifecycleStateCreated, }).Asserts(ws, rbac.ActionUpdate).Returns() })) - s.Run("UpdateWorkspaceAgentStartupLogOverflowByID", s.Subtest(func(db database.Store, check *expects) { + s.Run("UpdateWorkspaceAgentLogOverflowByID", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID}) agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID}) - check.Args(database.UpdateWorkspaceAgentStartupLogOverflowByIDParams{ - ID: agt.ID, - StartupLogsOverflowed: true, + check.Args(database.UpdateWorkspaceAgentLogOverflowByIDParams{ + ID: agt.ID, + LogsOverflowed: true, }).Asserts(ws, rbac.ActionUpdate).Returns() })) s.Run("UpdateWorkspaceAgentStartupByID", s.Subtest(func(db database.Store, check *expects) { @@ -1073,14 +1073,14 @@ func (s *MethodTestSuite) TestWorkspace() { Subsystem: database.WorkspaceAgentSubsystemNone, }).Asserts(ws, rbac.ActionUpdate).Returns() })) - s.Run("GetWorkspaceAgentStartupLogsAfter", s.Subtest(func(db database.Store, check *expects) { + s.Run("GetWorkspaceAgentLogsAfter", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()}) res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID}) agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID}) - check.Args(database.GetWorkspaceAgentStartupLogsAfterParams{ + check.Args(database.GetWorkspaceAgentLogsAfterParams{ AgentID: agt.ID, - }).Asserts(ws, rbac.ActionRead).Returns([]database.WorkspaceAgentStartupLog{}) + }).Asserts(ws, rbac.ActionRead).Returns([]database.WorkspaceAgentLog{}) })) s.Run("GetWorkspaceAppByAgentIDAndSlug", s.Subtest(func(db database.Store, check *expects) { ws := dbgen.Workspace(s.T(), db, database.Workspace{}) diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index 07f479bf2fae0..6d8eb2f4e9c34 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -60,7 +60,7 @@ func New() database.Store { templateVersions: make([]database.TemplateVersionTable, 0), templates: make([]database.TemplateTable, 0), workspaceAgentStats: make([]database.WorkspaceAgentStat, 0), - workspaceAgentLogs: make([]database.WorkspaceAgentStartupLog, 0), + workspaceAgentLogs: make([]database.WorkspaceAgentLog, 0), workspaceBuilds: make([]database.WorkspaceBuildTable, 0), workspaceApps: make([]database.WorkspaceApp, 0), workspaces: make([]database.Workspace, 0), @@ -133,7 +133,7 @@ type data struct { templates []database.TemplateTable workspaceAgents []database.WorkspaceAgent workspaceAgentMetadata []database.WorkspaceAgentMetadatum - workspaceAgentLogs []database.WorkspaceAgentStartupLog + workspaceAgentLogs []database.WorkspaceAgentLog workspaceApps []database.WorkspaceApp workspaceBuilds []database.WorkspaceBuildTable workspaceBuildParameters []database.WorkspaceBuildParameter @@ -789,7 +789,7 @@ func (q *FakeQuerier) DeleteLicense(_ context.Context, id int32) (int32, error) return 0, sql.ErrNoRows } -func (*FakeQuerier) DeleteOldWorkspaceAgentStartupLogs(_ context.Context) error { +func (*FakeQuerier) DeleteOldWorkspaceAgentLogs(_ context.Context) error { // noop return nil } @@ -2640,20 +2640,7 @@ func (q *FakeQuerier) GetWorkspaceAgentLifecycleStateByID(ctx context.Context, i }, nil } -func (q *FakeQuerier) GetWorkspaceAgentMetadata(_ context.Context, workspaceAgentID uuid.UUID) ([]database.WorkspaceAgentMetadatum, error) { - q.mutex.RLock() - defer q.mutex.RUnlock() - - metadata := make([]database.WorkspaceAgentMetadatum, 0) - for _, m := range q.workspaceAgentMetadata { - if m.WorkspaceAgentID == workspaceAgentID { - metadata = append(metadata, m) - } - } - return metadata, nil -} - -func (q *FakeQuerier) GetWorkspaceAgentStartupLogsAfter(_ context.Context, arg database.GetWorkspaceAgentStartupLogsAfterParams) ([]database.WorkspaceAgentStartupLog, error) { +func (q *FakeQuerier) GetWorkspaceAgentLogsAfter(_ context.Context, arg database.GetWorkspaceAgentLogsAfterParams) ([]database.WorkspaceAgentLog, error) { if err := validateDatabaseType(arg); err != nil { return nil, err } @@ -2661,7 +2648,7 @@ func (q *FakeQuerier) GetWorkspaceAgentStartupLogsAfter(_ context.Context, arg d q.mutex.RLock() defer q.mutex.RUnlock() - logs := []database.WorkspaceAgentStartupLog{} + logs := []database.WorkspaceAgentLog{} for _, log := range q.workspaceAgentLogs { if log.AgentID != arg.AgentID { continue @@ -2674,6 +2661,19 @@ func (q *FakeQuerier) GetWorkspaceAgentStartupLogsAfter(_ context.Context, arg d return logs, nil } +func (q *FakeQuerier) GetWorkspaceAgentMetadata(_ context.Context, workspaceAgentID uuid.UUID) ([]database.WorkspaceAgentMetadatum, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + metadata := make([]database.WorkspaceAgentMetadatum, 0) + for _, m := range q.workspaceAgentMetadata { + if m.WorkspaceAgentID == workspaceAgentID { + metadata = append(metadata, m) + } + } + return metadata, nil +} + func (q *FakeQuerier) GetWorkspaceAgentStats(_ context.Context, createdAfter time.Time) ([]database.GetWorkspaceAgentStatsRow, error) { q.mutex.RLock() defer q.mutex.RUnlock() @@ -3964,25 +3964,7 @@ func (q *FakeQuerier) InsertWorkspaceAgent(_ context.Context, arg database.Inser return agent, nil } -func (q *FakeQuerier) InsertWorkspaceAgentMetadata(_ context.Context, arg database.InsertWorkspaceAgentMetadataParams) error { - q.mutex.Lock() - defer q.mutex.Unlock() - - //nolint:gosimple - metadatum := database.WorkspaceAgentMetadatum{ - WorkspaceAgentID: arg.WorkspaceAgentID, - Script: arg.Script, - DisplayName: arg.DisplayName, - Key: arg.Key, - Timeout: arg.Timeout, - Interval: arg.Interval, - } - - q.workspaceAgentMetadata = append(q.workspaceAgentMetadata, metadatum) - return nil -} - -func (q *FakeQuerier) InsertWorkspaceAgentStartupLogs(_ context.Context, arg database.InsertWorkspaceAgentStartupLogsParams) ([]database.WorkspaceAgentStartupLog, error) { +func (q *FakeQuerier) InsertWorkspaceAgentLogs(_ context.Context, arg database.InsertWorkspaceAgentLogsParams) ([]database.WorkspaceAgentLog, error) { if err := validateDatabaseType(arg); err != nil { return nil, err } @@ -3990,7 +3972,7 @@ func (q *FakeQuerier) InsertWorkspaceAgentStartupLogs(_ context.Context, arg dat q.mutex.Lock() defer q.mutex.Unlock() - logs := []database.WorkspaceAgentStartupLog{} + logs := []database.WorkspaceAgentLog{} id := int64(0) if len(q.workspaceAgentLogs) > 0 { id = q.workspaceAgentLogs[len(q.workspaceAgentLogs)-1].ID @@ -3998,11 +3980,12 @@ func (q *FakeQuerier) InsertWorkspaceAgentStartupLogs(_ context.Context, arg dat outputLength := int32(0) for index, output := range arg.Output { id++ - logs = append(logs, database.WorkspaceAgentStartupLog{ + logs = append(logs, database.WorkspaceAgentLog{ ID: id, AgentID: arg.AgentID, CreatedAt: arg.CreatedAt[index], Level: arg.Level[index], + Source: arg.Source[index], Output: output, }) outputLength += int32(len(output)) @@ -4012,13 +3995,13 @@ func (q *FakeQuerier) InsertWorkspaceAgentStartupLogs(_ context.Context, arg dat continue } // Greater than 1MB, same as the PostgreSQL constraint! - if agent.StartupLogsLength+outputLength > (1 << 20) { + if agent.LogsLength+outputLength > (1 << 20) { return nil, &pq.Error{ - Constraint: "max_startup_logs_length", + Constraint: "max_logs_length", Table: "workspace_agents", } } - agent.StartupLogsLength += outputLength + agent.LogsLength += outputLength q.workspaceAgents[index] = agent break } @@ -4026,6 +4009,24 @@ func (q *FakeQuerier) InsertWorkspaceAgentStartupLogs(_ context.Context, arg dat return logs, nil } +func (q *FakeQuerier) InsertWorkspaceAgentMetadata(_ context.Context, arg database.InsertWorkspaceAgentMetadataParams) error { + q.mutex.Lock() + defer q.mutex.Unlock() + + //nolint:gosimple + metadatum := database.WorkspaceAgentMetadatum{ + WorkspaceAgentID: arg.WorkspaceAgentID, + Script: arg.Script, + DisplayName: arg.DisplayName, + Key: arg.Key, + Timeout: arg.Timeout, + Interval: arg.Interval, + } + + q.workspaceAgentMetadata = append(q.workspaceAgentMetadata, metadatum) + return nil +} + func (q *FakeQuerier) InsertWorkspaceAgentStat(_ context.Context, p database.InsertWorkspaceAgentStatParams) (database.WorkspaceAgentStat, error) { if err := validateDatabaseType(p); err != nil { return database.WorkspaceAgentStat{}, err @@ -4923,6 +4924,23 @@ func (q *FakeQuerier) UpdateWorkspaceAgentLifecycleStateByID(_ context.Context, return sql.ErrNoRows } +func (q *FakeQuerier) UpdateWorkspaceAgentLogOverflowByID(_ context.Context, arg database.UpdateWorkspaceAgentLogOverflowByIDParams) error { + if err := validateDatabaseType(arg); err != nil { + return err + } + + q.mutex.Lock() + defer q.mutex.Unlock() + for i, agent := range q.workspaceAgents { + if agent.ID == arg.ID { + agent.LogsOverflowed = arg.LogsOverflowed + q.workspaceAgents[i] = agent + return nil + } + } + return sql.ErrNoRows +} + func (q *FakeQuerier) UpdateWorkspaceAgentMetadata(_ context.Context, arg database.UpdateWorkspaceAgentMetadataParams) error { q.mutex.Lock() defer q.mutex.Unlock() @@ -4968,23 +4986,6 @@ func (q *FakeQuerier) UpdateWorkspaceAgentStartupByID(_ context.Context, arg dat return sql.ErrNoRows } -func (q *FakeQuerier) UpdateWorkspaceAgentStartupLogOverflowByID(_ context.Context, arg database.UpdateWorkspaceAgentStartupLogOverflowByIDParams) error { - if err := validateDatabaseType(arg); err != nil { - return err - } - - q.mutex.Lock() - defer q.mutex.Unlock() - for i, agent := range q.workspaceAgents { - if agent.ID == arg.ID { - agent.StartupLogsOverflowed = arg.StartupLogsOverflowed - q.workspaceAgents[i] = agent - return nil - } - } - return sql.ErrNoRows -} - func (q *FakeQuerier) UpdateWorkspaceAppHealthByID(_ context.Context, arg database.UpdateWorkspaceAppHealthByIDParams) error { if err := validateDatabaseType(arg); err != nil { return err diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index 0c30eb354ef65..9d389ef83794f 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -162,11 +162,11 @@ func (m metricsStore) DeleteLicense(ctx context.Context, id int32) (int32, error return licenseID, err } -func (m metricsStore) DeleteOldWorkspaceAgentStartupLogs(ctx context.Context) error { +func (m metricsStore) DeleteOldWorkspaceAgentLogs(ctx context.Context) error { start := time.Now() - err := m.s.DeleteOldWorkspaceAgentStartupLogs(ctx) - m.queryLatencies.WithLabelValues("DeleteOldWorkspaceAgentStartupLogs").Observe(time.Since(start).Seconds()) - return err + r0 := m.s.DeleteOldWorkspaceAgentLogs(ctx) + m.queryLatencies.WithLabelValues("DeleteOldWorkspaceAgentLogs").Observe(time.Since(start).Seconds()) + return r0 } func (m metricsStore) DeleteOldWorkspaceAgentStats(ctx context.Context) error { @@ -781,6 +781,13 @@ func (m metricsStore) GetWorkspaceAgentLifecycleStateByID(ctx context.Context, i return r0, r1 } +func (m metricsStore) GetWorkspaceAgentLogsAfter(ctx context.Context, arg database.GetWorkspaceAgentLogsAfterParams) ([]database.WorkspaceAgentLog, error) { + start := time.Now() + r0, r1 := m.s.GetWorkspaceAgentLogsAfter(ctx, arg) + m.queryLatencies.WithLabelValues("GetWorkspaceAgentLogsAfter").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAgentID uuid.UUID) ([]database.WorkspaceAgentMetadatum, error) { start := time.Now() metadata, err := m.s.GetWorkspaceAgentMetadata(ctx, workspaceAgentID) @@ -788,13 +795,6 @@ func (m metricsStore) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAg return metadata, err } -func (m metricsStore) GetWorkspaceAgentStartupLogsAfter(ctx context.Context, arg database.GetWorkspaceAgentStartupLogsAfterParams) ([]database.WorkspaceAgentStartupLog, error) { - start := time.Now() - logs, err := m.s.GetWorkspaceAgentStartupLogsAfter(ctx, arg) - m.queryLatencies.WithLabelValues("GetWorkspaceAgentStartupLogsAfter").Observe(time.Since(start).Seconds()) - return logs, err -} - func (m metricsStore) GetWorkspaceAgentStats(ctx context.Context, createdAt time.Time) ([]database.GetWorkspaceAgentStatsRow, error) { start := time.Now() stats, err := m.s.GetWorkspaceAgentStats(ctx, createdAt) @@ -1194,6 +1194,13 @@ func (m metricsStore) InsertWorkspaceAgent(ctx context.Context, arg database.Ins return agent, err } +func (m metricsStore) InsertWorkspaceAgentLogs(ctx context.Context, arg database.InsertWorkspaceAgentLogsParams) ([]database.WorkspaceAgentLog, error) { + start := time.Now() + r0, r1 := m.s.InsertWorkspaceAgentLogs(ctx, arg) + m.queryLatencies.WithLabelValues("InsertWorkspaceAgentLogs").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) InsertWorkspaceAgentMetadata(ctx context.Context, arg database.InsertWorkspaceAgentMetadataParams) error { start := time.Now() err := m.s.InsertWorkspaceAgentMetadata(ctx, arg) @@ -1201,13 +1208,6 @@ func (m metricsStore) InsertWorkspaceAgentMetadata(ctx context.Context, arg data return err } -func (m metricsStore) InsertWorkspaceAgentStartupLogs(ctx context.Context, arg database.InsertWorkspaceAgentStartupLogsParams) ([]database.WorkspaceAgentStartupLog, error) { - start := time.Now() - logs, err := m.s.InsertWorkspaceAgentStartupLogs(ctx, arg) - m.queryLatencies.WithLabelValues("InsertWorkspaceAgentStartupLogs").Observe(time.Since(start).Seconds()) - return logs, err -} - func (m metricsStore) InsertWorkspaceAgentStat(ctx context.Context, arg database.InsertWorkspaceAgentStatParams) (database.WorkspaceAgentStat, error) { start := time.Now() stat, err := m.s.InsertWorkspaceAgentStat(ctx, arg) @@ -1481,6 +1481,13 @@ func (m metricsStore) UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context return r0 } +func (m metricsStore) UpdateWorkspaceAgentLogOverflowByID(ctx context.Context, arg database.UpdateWorkspaceAgentLogOverflowByIDParams) error { + start := time.Now() + r0 := m.s.UpdateWorkspaceAgentLogOverflowByID(ctx, arg) + m.queryLatencies.WithLabelValues("UpdateWorkspaceAgentLogOverflowByID").Observe(time.Since(start).Seconds()) + return r0 +} + func (m metricsStore) UpdateWorkspaceAgentMetadata(ctx context.Context, arg database.UpdateWorkspaceAgentMetadataParams) error { start := time.Now() err := m.s.UpdateWorkspaceAgentMetadata(ctx, arg) @@ -1495,13 +1502,6 @@ func (m metricsStore) UpdateWorkspaceAgentStartupByID(ctx context.Context, arg d return err } -func (m metricsStore) UpdateWorkspaceAgentStartupLogOverflowByID(ctx context.Context, arg database.UpdateWorkspaceAgentStartupLogOverflowByIDParams) error { - start := time.Now() - err := m.s.UpdateWorkspaceAgentStartupLogOverflowByID(ctx, arg) - m.queryLatencies.WithLabelValues("UpdateWorkspaceAgentStartupLogOverflowByID").Observe(time.Since(start).Seconds()) - return err -} - func (m metricsStore) UpdateWorkspaceAppHealthByID(ctx context.Context, arg database.UpdateWorkspaceAppHealthByIDParams) error { start := time.Now() err := m.s.UpdateWorkspaceAppHealthByID(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 1b285c7ed384f..5bf58ec5a192f 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -209,18 +209,18 @@ func (mr *MockStoreMockRecorder) DeleteLicense(arg0, arg1 interface{}) *gomock.C return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLicense", reflect.TypeOf((*MockStore)(nil).DeleteLicense), arg0, arg1) } -// DeleteOldWorkspaceAgentStartupLogs mocks base method. -func (m *MockStore) DeleteOldWorkspaceAgentStartupLogs(arg0 context.Context) error { +// DeleteOldWorkspaceAgentLogs mocks base method. +func (m *MockStore) DeleteOldWorkspaceAgentLogs(arg0 context.Context) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DeleteOldWorkspaceAgentStartupLogs", arg0) + ret := m.ctrl.Call(m, "DeleteOldWorkspaceAgentLogs", arg0) ret0, _ := ret[0].(error) return ret0 } -// DeleteOldWorkspaceAgentStartupLogs indicates an expected call of DeleteOldWorkspaceAgentStartupLogs. -func (mr *MockStoreMockRecorder) DeleteOldWorkspaceAgentStartupLogs(arg0 interface{}) *gomock.Call { +// DeleteOldWorkspaceAgentLogs indicates an expected call of DeleteOldWorkspaceAgentLogs. +func (mr *MockStoreMockRecorder) DeleteOldWorkspaceAgentLogs(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOldWorkspaceAgentStartupLogs", reflect.TypeOf((*MockStore)(nil).DeleteOldWorkspaceAgentStartupLogs), arg0) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteOldWorkspaceAgentLogs", reflect.TypeOf((*MockStore)(nil).DeleteOldWorkspaceAgentLogs), arg0) } // DeleteOldWorkspaceAgentStats mocks base method. @@ -1616,34 +1616,34 @@ func (mr *MockStoreMockRecorder) GetWorkspaceAgentLifecycleStateByID(arg0, arg1 return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceAgentLifecycleStateByID", reflect.TypeOf((*MockStore)(nil).GetWorkspaceAgentLifecycleStateByID), arg0, arg1) } -// GetWorkspaceAgentMetadata mocks base method. -func (m *MockStore) GetWorkspaceAgentMetadata(arg0 context.Context, arg1 uuid.UUID) ([]database.WorkspaceAgentMetadatum, error) { +// GetWorkspaceAgentLogsAfter mocks base method. +func (m *MockStore) GetWorkspaceAgentLogsAfter(arg0 context.Context, arg1 database.GetWorkspaceAgentLogsAfterParams) ([]database.WorkspaceAgentLog, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetWorkspaceAgentMetadata", arg0, arg1) - ret0, _ := ret[0].([]database.WorkspaceAgentMetadatum) + ret := m.ctrl.Call(m, "GetWorkspaceAgentLogsAfter", arg0, arg1) + ret0, _ := ret[0].([]database.WorkspaceAgentLog) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetWorkspaceAgentMetadata indicates an expected call of GetWorkspaceAgentMetadata. -func (mr *MockStoreMockRecorder) GetWorkspaceAgentMetadata(arg0, arg1 interface{}) *gomock.Call { +// GetWorkspaceAgentLogsAfter indicates an expected call of GetWorkspaceAgentLogsAfter. +func (mr *MockStoreMockRecorder) GetWorkspaceAgentLogsAfter(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceAgentMetadata", reflect.TypeOf((*MockStore)(nil).GetWorkspaceAgentMetadata), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceAgentLogsAfter", reflect.TypeOf((*MockStore)(nil).GetWorkspaceAgentLogsAfter), arg0, arg1) } -// GetWorkspaceAgentStartupLogsAfter mocks base method. -func (m *MockStore) GetWorkspaceAgentStartupLogsAfter(arg0 context.Context, arg1 database.GetWorkspaceAgentStartupLogsAfterParams) ([]database.WorkspaceAgentStartupLog, error) { +// GetWorkspaceAgentMetadata mocks base method. +func (m *MockStore) GetWorkspaceAgentMetadata(arg0 context.Context, arg1 uuid.UUID) ([]database.WorkspaceAgentMetadatum, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetWorkspaceAgentStartupLogsAfter", arg0, arg1) - ret0, _ := ret[0].([]database.WorkspaceAgentStartupLog) + ret := m.ctrl.Call(m, "GetWorkspaceAgentMetadata", arg0, arg1) + ret0, _ := ret[0].([]database.WorkspaceAgentMetadatum) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetWorkspaceAgentStartupLogsAfter indicates an expected call of GetWorkspaceAgentStartupLogsAfter. -func (mr *MockStoreMockRecorder) GetWorkspaceAgentStartupLogsAfter(arg0, arg1 interface{}) *gomock.Call { +// GetWorkspaceAgentMetadata indicates an expected call of GetWorkspaceAgentMetadata. +func (mr *MockStoreMockRecorder) GetWorkspaceAgentMetadata(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceAgentStartupLogsAfter", reflect.TypeOf((*MockStore)(nil).GetWorkspaceAgentStartupLogsAfter), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceAgentMetadata", reflect.TypeOf((*MockStore)(nil).GetWorkspaceAgentMetadata), arg0, arg1) } // GetWorkspaceAgentStats mocks base method. @@ -2509,6 +2509,21 @@ func (mr *MockStoreMockRecorder) InsertWorkspaceAgent(arg0, arg1 interface{}) *g return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertWorkspaceAgent", reflect.TypeOf((*MockStore)(nil).InsertWorkspaceAgent), arg0, arg1) } +// InsertWorkspaceAgentLogs mocks base method. +func (m *MockStore) InsertWorkspaceAgentLogs(arg0 context.Context, arg1 database.InsertWorkspaceAgentLogsParams) ([]database.WorkspaceAgentLog, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InsertWorkspaceAgentLogs", arg0, arg1) + ret0, _ := ret[0].([]database.WorkspaceAgentLog) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// InsertWorkspaceAgentLogs indicates an expected call of InsertWorkspaceAgentLogs. +func (mr *MockStoreMockRecorder) InsertWorkspaceAgentLogs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertWorkspaceAgentLogs", reflect.TypeOf((*MockStore)(nil).InsertWorkspaceAgentLogs), arg0, arg1) +} + // InsertWorkspaceAgentMetadata mocks base method. func (m *MockStore) InsertWorkspaceAgentMetadata(arg0 context.Context, arg1 database.InsertWorkspaceAgentMetadataParams) error { m.ctrl.T.Helper() @@ -2523,21 +2538,6 @@ func (mr *MockStoreMockRecorder) InsertWorkspaceAgentMetadata(arg0, arg1 interfa return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertWorkspaceAgentMetadata", reflect.TypeOf((*MockStore)(nil).InsertWorkspaceAgentMetadata), arg0, arg1) } -// InsertWorkspaceAgentStartupLogs mocks base method. -func (m *MockStore) InsertWorkspaceAgentStartupLogs(arg0 context.Context, arg1 database.InsertWorkspaceAgentStartupLogsParams) ([]database.WorkspaceAgentStartupLog, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "InsertWorkspaceAgentStartupLogs", arg0, arg1) - ret0, _ := ret[0].([]database.WorkspaceAgentStartupLog) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// InsertWorkspaceAgentStartupLogs indicates an expected call of InsertWorkspaceAgentStartupLogs. -func (mr *MockStoreMockRecorder) InsertWorkspaceAgentStartupLogs(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertWorkspaceAgentStartupLogs", reflect.TypeOf((*MockStore)(nil).InsertWorkspaceAgentStartupLogs), arg0, arg1) -} - // InsertWorkspaceAgentStat mocks base method. func (m *MockStore) InsertWorkspaceAgentStat(arg0 context.Context, arg1 database.InsertWorkspaceAgentStatParams) (database.WorkspaceAgentStat, error) { m.ctrl.T.Helper() @@ -3120,6 +3120,20 @@ func (mr *MockStoreMockRecorder) UpdateWorkspaceAgentLifecycleStateByID(arg0, ar return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceAgentLifecycleStateByID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceAgentLifecycleStateByID), arg0, arg1) } +// UpdateWorkspaceAgentLogOverflowByID mocks base method. +func (m *MockStore) UpdateWorkspaceAgentLogOverflowByID(arg0 context.Context, arg1 database.UpdateWorkspaceAgentLogOverflowByIDParams) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateWorkspaceAgentLogOverflowByID", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateWorkspaceAgentLogOverflowByID indicates an expected call of UpdateWorkspaceAgentLogOverflowByID. +func (mr *MockStoreMockRecorder) UpdateWorkspaceAgentLogOverflowByID(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceAgentLogOverflowByID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceAgentLogOverflowByID), arg0, arg1) +} + // UpdateWorkspaceAgentMetadata mocks base method. func (m *MockStore) UpdateWorkspaceAgentMetadata(arg0 context.Context, arg1 database.UpdateWorkspaceAgentMetadataParams) error { m.ctrl.T.Helper() @@ -3148,20 +3162,6 @@ func (mr *MockStoreMockRecorder) UpdateWorkspaceAgentStartupByID(arg0, arg1 inte return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceAgentStartupByID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceAgentStartupByID), arg0, arg1) } -// UpdateWorkspaceAgentStartupLogOverflowByID mocks base method. -func (m *MockStore) UpdateWorkspaceAgentStartupLogOverflowByID(arg0 context.Context, arg1 database.UpdateWorkspaceAgentStartupLogOverflowByIDParams) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateWorkspaceAgentStartupLogOverflowByID", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// UpdateWorkspaceAgentStartupLogOverflowByID indicates an expected call of UpdateWorkspaceAgentStartupLogOverflowByID. -func (mr *MockStoreMockRecorder) UpdateWorkspaceAgentStartupLogOverflowByID(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceAgentStartupLogOverflowByID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceAgentStartupLogOverflowByID), arg0, arg1) -} - // UpdateWorkspaceAppHealthByID mocks base method. func (m *MockStore) UpdateWorkspaceAppHealthByID(arg0 context.Context, arg1 database.UpdateWorkspaceAppHealthByIDParams) error { m.ctrl.T.Helper() diff --git a/coderd/database/dbpurge/dbpurge.go b/coderd/database/dbpurge/dbpurge.go index abafcb4559f25..de7c2db67ef4b 100644 --- a/coderd/database/dbpurge/dbpurge.go +++ b/coderd/database/dbpurge/dbpurge.go @@ -40,7 +40,7 @@ func New(ctx context.Context, logger slog.Logger, db database.Store) io.Closer { var eg errgroup.Group eg.Go(func() error { - return db.DeleteOldWorkspaceAgentStartupLogs(ctx) + return db.DeleteOldWorkspaceAgentLogs(ctx) }) eg.Go(func() error { return db.DeleteOldWorkspaceAgentStats(ctx) diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 82f1fdf615818..0bf80bbfb536a 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -128,6 +128,15 @@ CREATE TYPE workspace_agent_lifecycle_state AS ENUM ( 'off' ); +CREATE TYPE workspace_agent_log_source AS ENUM ( + 'startup_script', + 'shutdown_script', + 'kubernetes_logs', + 'envbox', + 'envbuilder', + 'external' +); + CREATE TYPE workspace_agent_subsystem AS ENUM ( 'envbuilder', 'envbox', @@ -672,6 +681,15 @@ CREATE TABLE user_links ( oauth_expiry timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL ); +CREATE TABLE workspace_agent_logs ( + agent_id uuid NOT NULL, + created_at timestamp with time zone NOT NULL, + output character varying(1024) NOT NULL, + id bigint NOT NULL, + level log_level DEFAULT 'info'::log_level NOT NULL, + source workspace_agent_log_source DEFAULT 'startup_script'::workspace_agent_log_source NOT NULL +); + CREATE UNLOGGED TABLE workspace_agent_metadata ( workspace_agent_id uuid NOT NULL, display_name character varying(127) NOT NULL, @@ -684,14 +702,6 @@ CREATE UNLOGGED TABLE workspace_agent_metadata ( collected_at timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL ); -CREATE TABLE workspace_agent_startup_logs ( - agent_id uuid NOT NULL, - created_at timestamp with time zone NOT NULL, - output character varying(1024) NOT NULL, - id bigint NOT NULL, - level log_level DEFAULT 'info'::log_level NOT NULL -); - CREATE SEQUENCE workspace_agent_startup_logs_id_seq START WITH 1 INCREMENT BY 1 @@ -699,7 +709,7 @@ CREATE SEQUENCE workspace_agent_startup_logs_id_seq NO MAXVALUE CACHE 1; -ALTER SEQUENCE workspace_agent_startup_logs_id_seq OWNED BY workspace_agent_startup_logs.id; +ALTER SEQUENCE workspace_agent_startup_logs_id_seq OWNED BY workspace_agent_logs.id; CREATE TABLE workspace_agent_stats ( id uuid NOT NULL, @@ -749,13 +759,13 @@ CREATE TABLE workspace_agents ( expanded_directory character varying(4096) DEFAULT ''::character varying NOT NULL, shutdown_script character varying(65534), shutdown_script_timeout_seconds integer DEFAULT 0 NOT NULL, - startup_logs_length integer DEFAULT 0 NOT NULL, - startup_logs_overflowed boolean DEFAULT false NOT NULL, + logs_length integer DEFAULT 0 NOT NULL, + logs_overflowed boolean DEFAULT false NOT NULL, subsystem workspace_agent_subsystem DEFAULT 'none'::workspace_agent_subsystem NOT NULL, startup_script_behavior startup_script_behavior DEFAULT 'non-blocking'::startup_script_behavior NOT NULL, started_at timestamp with time zone, ready_at timestamp with time zone, - CONSTRAINT max_startup_logs_length CHECK ((startup_logs_length <= 1048576)) + CONSTRAINT max_logs_length CHECK ((logs_length <= 1048576)) ); COMMENT ON COLUMN workspace_agents.version IS 'Version tracks the version of the currently running workspace agent. Workspace agents register their version upon start.'; @@ -776,9 +786,9 @@ COMMENT ON COLUMN workspace_agents.shutdown_script IS 'Script that is executed b COMMENT ON COLUMN workspace_agents.shutdown_script_timeout_seconds IS 'The number of seconds to wait for the shutdown script to complete. If the script does not complete within this time, the agent lifecycle will be marked as shutdown_timeout.'; -COMMENT ON COLUMN workspace_agents.startup_logs_length IS 'Total length of startup logs'; +COMMENT ON COLUMN workspace_agents.logs_length IS 'Total length of startup logs'; -COMMENT ON COLUMN workspace_agents.startup_logs_overflowed IS 'Whether the startup logs overflowed in length'; +COMMENT ON COLUMN workspace_agents.logs_overflowed IS 'Whether the startup logs overflowed in length'; COMMENT ON COLUMN workspace_agents.startup_script_behavior IS 'When startup script behavior is non-blocking, the workspace will be ready and accessible upon agent connection, when it is blocking, workspace will wait for the startup script to complete before becoming ready and accessible.'; @@ -938,7 +948,7 @@ ALTER TABLE ONLY licenses ALTER COLUMN id SET DEFAULT nextval('licenses_id_seq': ALTER TABLE ONLY provisioner_job_logs ALTER COLUMN id SET DEFAULT nextval('provisioner_job_logs_id_seq'::regclass); -ALTER TABLE ONLY workspace_agent_startup_logs ALTER COLUMN id SET DEFAULT nextval('workspace_agent_startup_logs_id_seq'::regclass); +ALTER TABLE ONLY workspace_agent_logs ALTER COLUMN id SET DEFAULT nextval('workspace_agent_startup_logs_id_seq'::regclass); ALTER TABLE ONLY workspace_proxies ALTER COLUMN region_id SET DEFAULT nextval('workspace_proxies_region_id_seq'::regclass); @@ -1046,7 +1056,7 @@ ALTER TABLE ONLY users ALTER TABLE ONLY workspace_agent_metadata ADD CONSTRAINT workspace_agent_metadata_pkey PRIMARY KEY (workspace_agent_id, key); -ALTER TABLE ONLY workspace_agent_startup_logs +ALTER TABLE ONLY workspace_agent_logs ADD CONSTRAINT workspace_agent_startup_logs_pkey PRIMARY KEY (id); ALTER TABLE ONLY workspace_agents @@ -1132,7 +1142,7 @@ CREATE UNIQUE INDEX users_email_lower_idx ON users USING btree (lower(email)) WH CREATE UNIQUE INDEX users_username_lower_idx ON users USING btree (lower(username)) WHERE (deleted = false); -CREATE INDEX workspace_agent_startup_logs_id_agent_id_idx ON workspace_agent_startup_logs USING btree (agent_id, id); +CREATE INDEX workspace_agent_startup_logs_id_agent_id_idx ON workspace_agent_logs USING btree (agent_id, id); CREATE INDEX workspace_agents_auth_token_idx ON workspace_agents USING btree (auth_token); @@ -1217,7 +1227,7 @@ ALTER TABLE ONLY user_links ALTER TABLE ONLY workspace_agent_metadata ADD CONSTRAINT workspace_agent_metadata_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; -ALTER TABLE ONLY workspace_agent_startup_logs +ALTER TABLE ONLY workspace_agent_logs ADD CONSTRAINT workspace_agent_startup_logs_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; ALTER TABLE ONLY workspace_agents diff --git a/coderd/database/errors.go b/coderd/database/errors.go index bacddf8223408..af7365ffd916b 100644 --- a/coderd/database/errors.go +++ b/coderd/database/errors.go @@ -49,10 +49,10 @@ func IsQueryCanceledError(err error) bool { return false } -func IsStartupLogsLimitError(err error) bool { +func IsWorkspaceAgentLogsLimitError(err error) bool { var pqErr *pq.Error if errors.As(err, &pqErr) { - return pqErr.Constraint == "max_startup_logs_length" && pqErr.Table == "workspace_agents" + return pqErr.Constraint == "max_logs_length" && pqErr.Table == "workspace_agents" } return false diff --git a/coderd/database/migrations/000143_workspace_agent_logs.down.sql b/coderd/database/migrations/000143_workspace_agent_logs.down.sql new file mode 100644 index 0000000000000..53b0d03f44cfd --- /dev/null +++ b/coderd/database/migrations/000143_workspace_agent_logs.down.sql @@ -0,0 +1,8 @@ +BEGIN; +ALTER TABLE workspace_agent_logs RENAME TO workspace_agent_startup_logs; +ALTER TABLE workspace_agent_startup_logs DROP COLUMN source; +DROP TYPE workspace_agent_log_source; +ALTER TABLE workspace_agents RENAME COLUMN logs_overflowed TO startup_logs_overflowed; +ALTER TABLE workspace_agents RENAME COLUMN logs_length TO startup_logs_length; +ALTER TABLE workspace_agents RENAME CONSTRAINT max_logs_length TO max_startup_logs_length; +COMMIT; diff --git a/coderd/database/migrations/000143_workspace_agent_logs.up.sql b/coderd/database/migrations/000143_workspace_agent_logs.up.sql new file mode 100644 index 0000000000000..7de9cf07aa5da --- /dev/null +++ b/coderd/database/migrations/000143_workspace_agent_logs.up.sql @@ -0,0 +1,8 @@ +BEGIN; +CREATE TYPE workspace_agent_log_source AS ENUM ('startup_script', 'shutdown_script', 'kubernetes_logs', 'envbox', 'envbuilder', 'external'); +ALTER TABLE workspace_agent_startup_logs RENAME TO workspace_agent_logs; +ALTER TABLE workspace_agent_logs ADD COLUMN source workspace_agent_log_source NOT NULL DEFAULT 'startup_script'; +ALTER TABLE workspace_agents RENAME COLUMN startup_logs_overflowed TO logs_overflowed; +ALTER TABLE workspace_agents RENAME COLUMN startup_logs_length TO logs_length; +ALTER TABLE workspace_agents RENAME CONSTRAINT max_startup_logs_length TO max_logs_length; +COMMIT; diff --git a/coderd/database/models.go b/coderd/database/models.go index 32ac5f626eff1..58083303b05d7 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1169,6 +1169,76 @@ func AllWorkspaceAgentLifecycleStateValues() []WorkspaceAgentLifecycleState { } } +type WorkspaceAgentLogSource string + +const ( + WorkspaceAgentLogSourceStartupScript WorkspaceAgentLogSource = "startup_script" + WorkspaceAgentLogSourceShutdownScript WorkspaceAgentLogSource = "shutdown_script" + WorkspaceAgentLogSourceKubernetesLogs WorkspaceAgentLogSource = "kubernetes_logs" + WorkspaceAgentLogSourceEnvbox WorkspaceAgentLogSource = "envbox" + WorkspaceAgentLogSourceEnvbuilder WorkspaceAgentLogSource = "envbuilder" + WorkspaceAgentLogSourceExternal WorkspaceAgentLogSource = "external" +) + +func (e *WorkspaceAgentLogSource) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = WorkspaceAgentLogSource(s) + case string: + *e = WorkspaceAgentLogSource(s) + default: + return fmt.Errorf("unsupported scan type for WorkspaceAgentLogSource: %T", src) + } + return nil +} + +type NullWorkspaceAgentLogSource struct { + WorkspaceAgentLogSource WorkspaceAgentLogSource `json:"workspace_agent_log_source"` + Valid bool `json:"valid"` // Valid is true if WorkspaceAgentLogSource is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullWorkspaceAgentLogSource) Scan(value interface{}) error { + if value == nil { + ns.WorkspaceAgentLogSource, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.WorkspaceAgentLogSource.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullWorkspaceAgentLogSource) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.WorkspaceAgentLogSource), nil +} + +func (e WorkspaceAgentLogSource) Valid() bool { + switch e { + case WorkspaceAgentLogSourceStartupScript, + WorkspaceAgentLogSourceShutdownScript, + WorkspaceAgentLogSourceKubernetesLogs, + WorkspaceAgentLogSourceEnvbox, + WorkspaceAgentLogSourceEnvbuilder, + WorkspaceAgentLogSourceExternal: + return true + } + return false +} + +func AllWorkspaceAgentLogSourceValues() []WorkspaceAgentLogSource { + return []WorkspaceAgentLogSource{ + WorkspaceAgentLogSourceStartupScript, + WorkspaceAgentLogSourceShutdownScript, + WorkspaceAgentLogSourceKubernetesLogs, + WorkspaceAgentLogSourceEnvbox, + WorkspaceAgentLogSourceEnvbuilder, + WorkspaceAgentLogSourceExternal, + } +} + type WorkspaceAgentSubsystem string const ( @@ -1806,10 +1876,10 @@ type WorkspaceAgent struct { // The number of seconds to wait for the shutdown script to complete. If the script does not complete within this time, the agent lifecycle will be marked as shutdown_timeout. ShutdownScriptTimeoutSeconds int32 `db:"shutdown_script_timeout_seconds" json:"shutdown_script_timeout_seconds"` // Total length of startup logs - StartupLogsLength int32 `db:"startup_logs_length" json:"startup_logs_length"` + LogsLength int32 `db:"logs_length" json:"logs_length"` // Whether the startup logs overflowed in length - StartupLogsOverflowed bool `db:"startup_logs_overflowed" json:"startup_logs_overflowed"` - Subsystem WorkspaceAgentSubsystem `db:"subsystem" json:"subsystem"` + LogsOverflowed bool `db:"logs_overflowed" json:"logs_overflowed"` + Subsystem WorkspaceAgentSubsystem `db:"subsystem" json:"subsystem"` // When startup script behavior is non-blocking, the workspace will be ready and accessible upon agent connection, when it is blocking, workspace will wait for the startup script to complete before becoming ready and accessible. StartupScriptBehavior StartupScriptBehavior `db:"startup_script_behavior" json:"startup_script_behavior"` // The time the agent entered the starting lifecycle state @@ -1818,6 +1888,15 @@ type WorkspaceAgent struct { ReadyAt sql.NullTime `db:"ready_at" json:"ready_at"` } +type WorkspaceAgentLog struct { + AgentID uuid.UUID `db:"agent_id" json:"agent_id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + Output string `db:"output" json:"output"` + ID int64 `db:"id" json:"id"` + Level LogLevel `db:"level" json:"level"` + Source WorkspaceAgentLogSource `db:"source" json:"source"` +} + type WorkspaceAgentMetadatum struct { WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` DisplayName string `db:"display_name" json:"display_name"` @@ -1830,14 +1909,6 @@ type WorkspaceAgentMetadatum struct { CollectedAt time.Time `db:"collected_at" json:"collected_at"` } -type WorkspaceAgentStartupLog struct { - AgentID uuid.UUID `db:"agent_id" json:"agent_id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - Output string `db:"output" json:"output"` - ID int64 `db:"id" json:"id"` - Level LogLevel `db:"level" json:"level"` -} - type WorkspaceAgentStat struct { ID uuid.UUID `db:"id" json:"id"` CreatedAt time.Time `db:"created_at" json:"created_at"` diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 08f29ca1bc3ca..0baaab0488d2e 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -36,7 +36,7 @@ type sqlcQuerier interface { DeleteLicense(ctx context.Context, id int32) (int32, error) // If an agent hasn't connected in the last 7 days, we purge it's logs. // Logs can take up a lot of space, so it's important we clean up frequently. - DeleteOldWorkspaceAgentStartupLogs(ctx context.Context) error + DeleteOldWorkspaceAgentLogs(ctx context.Context) error DeleteOldWorkspaceAgentStats(ctx context.Context) error DeleteReplicasUpdatedBefore(ctx context.Context, updatedAt time.Time) error DeleteTailnetAgent(ctx context.Context, arg DeleteTailnetAgentParams) (DeleteTailnetAgentRow, error) @@ -145,8 +145,8 @@ type sqlcQuerier interface { GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (WorkspaceAgent, error) GetWorkspaceAgentByInstanceID(ctx context.Context, authInstanceID string) (WorkspaceAgent, error) GetWorkspaceAgentLifecycleStateByID(ctx context.Context, id uuid.UUID) (GetWorkspaceAgentLifecycleStateByIDRow, error) + GetWorkspaceAgentLogsAfter(ctx context.Context, arg GetWorkspaceAgentLogsAfterParams) ([]WorkspaceAgentLog, error) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAgentID uuid.UUID) ([]WorkspaceAgentMetadatum, error) - GetWorkspaceAgentStartupLogsAfter(ctx context.Context, arg GetWorkspaceAgentStartupLogsAfterParams) ([]WorkspaceAgentStartupLog, error) GetWorkspaceAgentStats(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentStatsRow, error) GetWorkspaceAgentStatsAndLabels(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentStatsAndLabelsRow, error) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgent, error) @@ -215,8 +215,8 @@ type sqlcQuerier interface { InsertUserLink(ctx context.Context, arg InsertUserLinkParams) (UserLink, error) InsertWorkspace(ctx context.Context, arg InsertWorkspaceParams) (Workspace, error) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspaceAgentParams) (WorkspaceAgent, error) + InsertWorkspaceAgentLogs(ctx context.Context, arg InsertWorkspaceAgentLogsParams) ([]WorkspaceAgentLog, error) InsertWorkspaceAgentMetadata(ctx context.Context, arg InsertWorkspaceAgentMetadataParams) error - InsertWorkspaceAgentStartupLogs(ctx context.Context, arg InsertWorkspaceAgentStartupLogsParams) ([]WorkspaceAgentStartupLog, error) InsertWorkspaceAgentStat(ctx context.Context, arg InsertWorkspaceAgentStatParams) (WorkspaceAgentStat, error) InsertWorkspaceApp(ctx context.Context, arg InsertWorkspaceAppParams) (WorkspaceApp, error) InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspaceBuildParams) error @@ -260,9 +260,9 @@ type sqlcQuerier interface { UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams) (Workspace, error) UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg UpdateWorkspaceAgentConnectionByIDParams) error UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, arg UpdateWorkspaceAgentLifecycleStateByIDParams) error + UpdateWorkspaceAgentLogOverflowByID(ctx context.Context, arg UpdateWorkspaceAgentLogOverflowByIDParams) error UpdateWorkspaceAgentMetadata(ctx context.Context, arg UpdateWorkspaceAgentMetadataParams) error UpdateWorkspaceAgentStartupByID(ctx context.Context, arg UpdateWorkspaceAgentStartupByIDParams) error - UpdateWorkspaceAgentStartupLogOverflowByID(ctx context.Context, arg UpdateWorkspaceAgentStartupLogOverflowByIDParams) error UpdateWorkspaceAppHealthByID(ctx context.Context, arg UpdateWorkspaceAppHealthByIDParams) error UpdateWorkspaceAutostart(ctx context.Context, arg UpdateWorkspaceAutostartParams) error UpdateWorkspaceBuildByID(ctx context.Context, arg UpdateWorkspaceBuildByIDParams) error diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index 2d41cb7458c72..07a81111d64c9 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -91,7 +91,7 @@ func TestGetDeploymentWorkspaceAgentStats(t *testing.T) { }) } -func TestInsertWorkspaceAgentStartupLogs(t *testing.T) { +func TestInsertWorkspaceAgentLogs(t *testing.T) { t.Parallel() if testing.Short() { t.SkipNow() @@ -111,25 +111,27 @@ func TestInsertWorkspaceAgentStartupLogs(t *testing.T) { agent := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{ ResourceID: resource.ID, }) - logs, err := db.InsertWorkspaceAgentStartupLogs(ctx, database.InsertWorkspaceAgentStartupLogsParams{ + logs, err := db.InsertWorkspaceAgentLogs(ctx, database.InsertWorkspaceAgentLogsParams{ AgentID: agent.ID, CreatedAt: []time.Time{database.Now()}, Output: []string{"first"}, Level: []database.LogLevel{database.LogLevelInfo}, + Source: []database.WorkspaceAgentLogSource{database.WorkspaceAgentLogSourceExternal}, // 1 MB is the max OutputLength: 1 << 20, }) require.NoError(t, err) require.Equal(t, int64(1), logs[0].ID) - _, err = db.InsertWorkspaceAgentStartupLogs(ctx, database.InsertWorkspaceAgentStartupLogsParams{ + _, err = db.InsertWorkspaceAgentLogs(ctx, database.InsertWorkspaceAgentLogsParams{ AgentID: agent.ID, CreatedAt: []time.Time{database.Now()}, Output: []string{"second"}, Level: []database.LogLevel{database.LogLevelInfo}, + Source: []database.WorkspaceAgentLogSource{database.WorkspaceAgentLogSourceExternal}, OutputLength: 1, }) - require.True(t, database.IsStartupLogsLimitError(err)) + require.True(t, database.IsWorkspaceAgentLogsLimitError(err)) } func TestProxyByHostname(t *testing.T) { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 46502c402c618..ca37955650d42 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -5914,22 +5914,22 @@ func (q *sqlQuerier) UpdateUserStatus(ctx context.Context, arg UpdateUserStatusP return i, err } -const deleteOldWorkspaceAgentStartupLogs = `-- name: DeleteOldWorkspaceAgentStartupLogs :exec -DELETE FROM workspace_agent_startup_logs WHERE agent_id IN +const deleteOldWorkspaceAgentLogs = `-- name: DeleteOldWorkspaceAgentLogs :exec +DELETE FROM workspace_agent_logs WHERE agent_id IN (SELECT id FROM workspace_agents WHERE last_connected_at IS NOT NULL AND last_connected_at < NOW() - INTERVAL '7 day') ` // If an agent hasn't connected in the last 7 days, we purge it's logs. // Logs can take up a lot of space, so it's important we clean up frequently. -func (q *sqlQuerier) DeleteOldWorkspaceAgentStartupLogs(ctx context.Context) error { - _, err := q.db.ExecContext(ctx, deleteOldWorkspaceAgentStartupLogs) +func (q *sqlQuerier) DeleteOldWorkspaceAgentLogs(ctx context.Context) error { + _, err := q.db.ExecContext(ctx, deleteOldWorkspaceAgentLogs) return err } const getWorkspaceAgentByAuthToken = `-- name: GetWorkspaceAgentByAuthToken :one SELECT - id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed, subsystem, startup_script_behavior, started_at, ready_at + id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, logs_length, logs_overflowed, subsystem, startup_script_behavior, started_at, ready_at FROM workspace_agents WHERE @@ -5969,8 +5969,8 @@ func (q *sqlQuerier) GetWorkspaceAgentByAuthToken(ctx context.Context, authToken &i.ExpandedDirectory, &i.ShutdownScript, &i.ShutdownScriptTimeoutSeconds, - &i.StartupLogsLength, - &i.StartupLogsOverflowed, + &i.LogsLength, + &i.LogsOverflowed, &i.Subsystem, &i.StartupScriptBehavior, &i.StartedAt, @@ -5981,7 +5981,7 @@ func (q *sqlQuerier) GetWorkspaceAgentByAuthToken(ctx context.Context, authToken const getWorkspaceAgentByID = `-- name: GetWorkspaceAgentByID :one SELECT - id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed, subsystem, startup_script_behavior, started_at, ready_at + id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, logs_length, logs_overflowed, subsystem, startup_script_behavior, started_at, ready_at FROM workspace_agents WHERE @@ -6019,8 +6019,8 @@ func (q *sqlQuerier) GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (W &i.ExpandedDirectory, &i.ShutdownScript, &i.ShutdownScriptTimeoutSeconds, - &i.StartupLogsLength, - &i.StartupLogsOverflowed, + &i.LogsLength, + &i.LogsOverflowed, &i.Subsystem, &i.StartupScriptBehavior, &i.StartedAt, @@ -6031,7 +6031,7 @@ func (q *sqlQuerier) GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (W const getWorkspaceAgentByInstanceID = `-- name: GetWorkspaceAgentByInstanceID :one SELECT - id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed, subsystem, startup_script_behavior, started_at, ready_at + id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, logs_length, logs_overflowed, subsystem, startup_script_behavior, started_at, ready_at FROM workspace_agents WHERE @@ -6071,8 +6071,8 @@ func (q *sqlQuerier) GetWorkspaceAgentByInstanceID(ctx context.Context, authInst &i.ExpandedDirectory, &i.ShutdownScript, &i.ShutdownScriptTimeoutSeconds, - &i.StartupLogsLength, - &i.StartupLogsOverflowed, + &i.LogsLength, + &i.LogsOverflowed, &i.Subsystem, &i.StartupScriptBehavior, &i.StartedAt, @@ -6105,34 +6105,39 @@ func (q *sqlQuerier) GetWorkspaceAgentLifecycleStateByID(ctx context.Context, id return i, err } -const getWorkspaceAgentMetadata = `-- name: GetWorkspaceAgentMetadata :many +const getWorkspaceAgentLogsAfter = `-- name: GetWorkspaceAgentLogsAfter :many SELECT - workspace_agent_id, display_name, key, script, value, error, timeout, interval, collected_at + agent_id, created_at, output, id, level, source FROM - workspace_agent_metadata + workspace_agent_logs WHERE - workspace_agent_id = $1 + agent_id = $1 + AND ( + id > $2 + ) ORDER BY id ASC ` -func (q *sqlQuerier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAgentID uuid.UUID) ([]WorkspaceAgentMetadatum, error) { - rows, err := q.db.QueryContext(ctx, getWorkspaceAgentMetadata, workspaceAgentID) +type GetWorkspaceAgentLogsAfterParams struct { + AgentID uuid.UUID `db:"agent_id" json:"agent_id"` + CreatedAfter int64 `db:"created_after" json:"created_after"` +} + +func (q *sqlQuerier) GetWorkspaceAgentLogsAfter(ctx context.Context, arg GetWorkspaceAgentLogsAfterParams) ([]WorkspaceAgentLog, error) { + rows, err := q.db.QueryContext(ctx, getWorkspaceAgentLogsAfter, arg.AgentID, arg.CreatedAfter) if err != nil { return nil, err } defer rows.Close() - var items []WorkspaceAgentMetadatum + var items []WorkspaceAgentLog for rows.Next() { - var i WorkspaceAgentMetadatum + var i WorkspaceAgentLog if err := rows.Scan( - &i.WorkspaceAgentID, - &i.DisplayName, - &i.Key, - &i.Script, - &i.Value, - &i.Error, - &i.Timeout, - &i.Interval, - &i.CollectedAt, + &i.AgentID, + &i.CreatedAt, + &i.Output, + &i.ID, + &i.Level, + &i.Source, ); err != nil { return nil, err } @@ -6147,38 +6152,34 @@ func (q *sqlQuerier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAge return items, nil } -const getWorkspaceAgentStartupLogsAfter = `-- name: GetWorkspaceAgentStartupLogsAfter :many +const getWorkspaceAgentMetadata = `-- name: GetWorkspaceAgentMetadata :many SELECT - agent_id, created_at, output, id, level + workspace_agent_id, display_name, key, script, value, error, timeout, interval, collected_at FROM - workspace_agent_startup_logs + workspace_agent_metadata WHERE - agent_id = $1 - AND ( - id > $2 - ) ORDER BY id ASC + workspace_agent_id = $1 ` -type GetWorkspaceAgentStartupLogsAfterParams struct { - AgentID uuid.UUID `db:"agent_id" json:"agent_id"` - CreatedAfter int64 `db:"created_after" json:"created_after"` -} - -func (q *sqlQuerier) GetWorkspaceAgentStartupLogsAfter(ctx context.Context, arg GetWorkspaceAgentStartupLogsAfterParams) ([]WorkspaceAgentStartupLog, error) { - rows, err := q.db.QueryContext(ctx, getWorkspaceAgentStartupLogsAfter, arg.AgentID, arg.CreatedAfter) +func (q *sqlQuerier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAgentID uuid.UUID) ([]WorkspaceAgentMetadatum, error) { + rows, err := q.db.QueryContext(ctx, getWorkspaceAgentMetadata, workspaceAgentID) if err != nil { return nil, err } defer rows.Close() - var items []WorkspaceAgentStartupLog + var items []WorkspaceAgentMetadatum for rows.Next() { - var i WorkspaceAgentStartupLog + var i WorkspaceAgentMetadatum if err := rows.Scan( - &i.AgentID, - &i.CreatedAt, - &i.Output, - &i.ID, - &i.Level, + &i.WorkspaceAgentID, + &i.DisplayName, + &i.Key, + &i.Script, + &i.Value, + &i.Error, + &i.Timeout, + &i.Interval, + &i.CollectedAt, ); err != nil { return nil, err } @@ -6195,7 +6196,7 @@ func (q *sqlQuerier) GetWorkspaceAgentStartupLogsAfter(ctx context.Context, arg const getWorkspaceAgentsByResourceIDs = `-- name: GetWorkspaceAgentsByResourceIDs :many SELECT - id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed, subsystem, startup_script_behavior, started_at, ready_at + id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, logs_length, logs_overflowed, subsystem, startup_script_behavior, started_at, ready_at FROM workspace_agents WHERE @@ -6239,8 +6240,8 @@ func (q *sqlQuerier) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids [] &i.ExpandedDirectory, &i.ShutdownScript, &i.ShutdownScriptTimeoutSeconds, - &i.StartupLogsLength, - &i.StartupLogsOverflowed, + &i.LogsLength, + &i.LogsOverflowed, &i.Subsystem, &i.StartupScriptBehavior, &i.StartedAt, @@ -6260,7 +6261,7 @@ func (q *sqlQuerier) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids [] } const getWorkspaceAgentsCreatedAfter = `-- name: GetWorkspaceAgentsCreatedAfter :many -SELECT id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed, subsystem, startup_script_behavior, started_at, ready_at FROM workspace_agents WHERE created_at > $1 +SELECT id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, logs_length, logs_overflowed, subsystem, startup_script_behavior, started_at, ready_at FROM workspace_agents WHERE created_at > $1 ` func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceAgent, error) { @@ -6300,8 +6301,8 @@ func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, created &i.ExpandedDirectory, &i.ShutdownScript, &i.ShutdownScriptTimeoutSeconds, - &i.StartupLogsLength, - &i.StartupLogsOverflowed, + &i.LogsLength, + &i.LogsOverflowed, &i.Subsystem, &i.StartupScriptBehavior, &i.StartedAt, @@ -6322,7 +6323,7 @@ func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, created const getWorkspaceAgentsInLatestBuildByWorkspaceID = `-- name: GetWorkspaceAgentsInLatestBuildByWorkspaceID :many SELECT - workspace_agents.id, workspace_agents.created_at, workspace_agents.updated_at, workspace_agents.name, workspace_agents.first_connected_at, workspace_agents.last_connected_at, workspace_agents.disconnected_at, workspace_agents.resource_id, workspace_agents.auth_token, workspace_agents.auth_instance_id, workspace_agents.architecture, workspace_agents.environment_variables, workspace_agents.operating_system, workspace_agents.startup_script, workspace_agents.instance_metadata, workspace_agents.resource_metadata, workspace_agents.directory, workspace_agents.version, workspace_agents.last_connected_replica_id, workspace_agents.connection_timeout_seconds, workspace_agents.troubleshooting_url, workspace_agents.motd_file, workspace_agents.lifecycle_state, workspace_agents.startup_script_timeout_seconds, workspace_agents.expanded_directory, workspace_agents.shutdown_script, workspace_agents.shutdown_script_timeout_seconds, workspace_agents.startup_logs_length, workspace_agents.startup_logs_overflowed, workspace_agents.subsystem, workspace_agents.startup_script_behavior, workspace_agents.started_at, workspace_agents.ready_at + workspace_agents.id, workspace_agents.created_at, workspace_agents.updated_at, workspace_agents.name, workspace_agents.first_connected_at, workspace_agents.last_connected_at, workspace_agents.disconnected_at, workspace_agents.resource_id, workspace_agents.auth_token, workspace_agents.auth_instance_id, workspace_agents.architecture, workspace_agents.environment_variables, workspace_agents.operating_system, workspace_agents.startup_script, workspace_agents.instance_metadata, workspace_agents.resource_metadata, workspace_agents.directory, workspace_agents.version, workspace_agents.last_connected_replica_id, workspace_agents.connection_timeout_seconds, workspace_agents.troubleshooting_url, workspace_agents.motd_file, workspace_agents.lifecycle_state, workspace_agents.startup_script_timeout_seconds, workspace_agents.expanded_directory, workspace_agents.shutdown_script, workspace_agents.shutdown_script_timeout_seconds, workspace_agents.logs_length, workspace_agents.logs_overflowed, workspace_agents.subsystem, workspace_agents.startup_script_behavior, workspace_agents.started_at, workspace_agents.ready_at FROM workspace_agents JOIN @@ -6378,8 +6379,8 @@ func (q *sqlQuerier) GetWorkspaceAgentsInLatestBuildByWorkspaceID(ctx context.Co &i.ExpandedDirectory, &i.ShutdownScript, &i.ShutdownScriptTimeoutSeconds, - &i.StartupLogsLength, - &i.StartupLogsOverflowed, + &i.LogsLength, + &i.LogsOverflowed, &i.Subsystem, &i.StartupScriptBehavior, &i.StartedAt, @@ -6424,7 +6425,7 @@ INSERT INTO shutdown_script_timeout_seconds ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) RETURNING id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed, subsystem, startup_script_behavior, started_at, ready_at + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) RETURNING id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, logs_length, logs_overflowed, subsystem, startup_script_behavior, started_at, ready_at ` type InsertWorkspaceAgentParams struct { @@ -6504,8 +6505,8 @@ func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspa &i.ExpandedDirectory, &i.ShutdownScript, &i.ShutdownScriptTimeoutSeconds, - &i.StartupLogsLength, - &i.StartupLogsOverflowed, + &i.LogsLength, + &i.LogsOverflowed, &i.Subsystem, &i.StartupScriptBehavior, &i.StartedAt, @@ -6514,85 +6515,54 @@ func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspa return i, err } -const insertWorkspaceAgentMetadata = `-- name: InsertWorkspaceAgentMetadata :exec -INSERT INTO - workspace_agent_metadata ( - workspace_agent_id, - display_name, - key, - script, - timeout, - interval - ) -VALUES - ($1, $2, $3, $4, $5, $6) -` - -type InsertWorkspaceAgentMetadataParams struct { - WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` - DisplayName string `db:"display_name" json:"display_name"` - Key string `db:"key" json:"key"` - Script string `db:"script" json:"script"` - Timeout int64 `db:"timeout" json:"timeout"` - Interval int64 `db:"interval" json:"interval"` -} - -func (q *sqlQuerier) InsertWorkspaceAgentMetadata(ctx context.Context, arg InsertWorkspaceAgentMetadataParams) error { - _, err := q.db.ExecContext(ctx, insertWorkspaceAgentMetadata, - arg.WorkspaceAgentID, - arg.DisplayName, - arg.Key, - arg.Script, - arg.Timeout, - arg.Interval, - ) - return err -} - -const insertWorkspaceAgentStartupLogs = `-- name: InsertWorkspaceAgentStartupLogs :many +const insertWorkspaceAgentLogs = `-- name: InsertWorkspaceAgentLogs :many WITH new_length AS ( UPDATE workspace_agents SET - startup_logs_length = startup_logs_length + $5 WHERE workspace_agents.id = $1 + logs_length = logs_length + $6 WHERE workspace_agents.id = $1 ) INSERT INTO - workspace_agent_startup_logs (agent_id, created_at, output, level) + workspace_agent_logs (agent_id, created_at, output, level, source) SELECT $1 :: uuid AS agent_id, unnest($2 :: timestamptz [ ]) AS created_at, unnest($3 :: VARCHAR(1024) [ ]) AS output, - unnest($4 :: log_level [ ]) AS level - RETURNING workspace_agent_startup_logs.agent_id, workspace_agent_startup_logs.created_at, workspace_agent_startup_logs.output, workspace_agent_startup_logs.id, workspace_agent_startup_logs.level + unnest($4 :: log_level [ ]) AS level, + unnest($5 :: workspace_agent_log_source [ ]) AS source + RETURNING workspace_agent_logs.agent_id, workspace_agent_logs.created_at, workspace_agent_logs.output, workspace_agent_logs.id, workspace_agent_logs.level, workspace_agent_logs.source ` -type InsertWorkspaceAgentStartupLogsParams struct { - AgentID uuid.UUID `db:"agent_id" json:"agent_id"` - CreatedAt []time.Time `db:"created_at" json:"created_at"` - Output []string `db:"output" json:"output"` - Level []LogLevel `db:"level" json:"level"` - OutputLength int32 `db:"output_length" json:"output_length"` +type InsertWorkspaceAgentLogsParams struct { + AgentID uuid.UUID `db:"agent_id" json:"agent_id"` + CreatedAt []time.Time `db:"created_at" json:"created_at"` + Output []string `db:"output" json:"output"` + Level []LogLevel `db:"level" json:"level"` + Source []WorkspaceAgentLogSource `db:"source" json:"source"` + OutputLength int32 `db:"output_length" json:"output_length"` } -func (q *sqlQuerier) InsertWorkspaceAgentStartupLogs(ctx context.Context, arg InsertWorkspaceAgentStartupLogsParams) ([]WorkspaceAgentStartupLog, error) { - rows, err := q.db.QueryContext(ctx, insertWorkspaceAgentStartupLogs, +func (q *sqlQuerier) InsertWorkspaceAgentLogs(ctx context.Context, arg InsertWorkspaceAgentLogsParams) ([]WorkspaceAgentLog, error) { + rows, err := q.db.QueryContext(ctx, insertWorkspaceAgentLogs, arg.AgentID, pq.Array(arg.CreatedAt), pq.Array(arg.Output), pq.Array(arg.Level), + pq.Array(arg.Source), arg.OutputLength, ) if err != nil { return nil, err } defer rows.Close() - var items []WorkspaceAgentStartupLog + var items []WorkspaceAgentLog for rows.Next() { - var i WorkspaceAgentStartupLog + var i WorkspaceAgentLog if err := rows.Scan( &i.AgentID, &i.CreatedAt, &i.Output, &i.ID, &i.Level, + &i.Source, ); err != nil { return nil, err } @@ -6607,6 +6577,41 @@ func (q *sqlQuerier) InsertWorkspaceAgentStartupLogs(ctx context.Context, arg In return items, nil } +const insertWorkspaceAgentMetadata = `-- name: InsertWorkspaceAgentMetadata :exec +INSERT INTO + workspace_agent_metadata ( + workspace_agent_id, + display_name, + key, + script, + timeout, + interval + ) +VALUES + ($1, $2, $3, $4, $5, $6) +` + +type InsertWorkspaceAgentMetadataParams struct { + WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` + DisplayName string `db:"display_name" json:"display_name"` + Key string `db:"key" json:"key"` + Script string `db:"script" json:"script"` + Timeout int64 `db:"timeout" json:"timeout"` + Interval int64 `db:"interval" json:"interval"` +} + +func (q *sqlQuerier) InsertWorkspaceAgentMetadata(ctx context.Context, arg InsertWorkspaceAgentMetadataParams) error { + _, err := q.db.ExecContext(ctx, insertWorkspaceAgentMetadata, + arg.WorkspaceAgentID, + arg.DisplayName, + arg.Key, + arg.Script, + arg.Timeout, + arg.Interval, + ) + return err +} + const updateWorkspaceAgentConnectionByID = `-- name: UpdateWorkspaceAgentConnectionByID :exec UPDATE workspace_agents @@ -6669,6 +6674,25 @@ func (q *sqlQuerier) UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, return err } +const updateWorkspaceAgentLogOverflowByID = `-- name: UpdateWorkspaceAgentLogOverflowByID :exec +UPDATE + workspace_agents +SET + logs_overflowed = $2 +WHERE + id = $1 +` + +type UpdateWorkspaceAgentLogOverflowByIDParams struct { + ID uuid.UUID `db:"id" json:"id"` + LogsOverflowed bool `db:"logs_overflowed" json:"logs_overflowed"` +} + +func (q *sqlQuerier) UpdateWorkspaceAgentLogOverflowByID(ctx context.Context, arg UpdateWorkspaceAgentLogOverflowByIDParams) error { + _, err := q.db.ExecContext(ctx, updateWorkspaceAgentLogOverflowByID, arg.ID, arg.LogsOverflowed) + return err +} + const updateWorkspaceAgentMetadata = `-- name: UpdateWorkspaceAgentMetadata :exec UPDATE workspace_agent_metadata @@ -6728,25 +6752,6 @@ func (q *sqlQuerier) UpdateWorkspaceAgentStartupByID(ctx context.Context, arg Up return err } -const updateWorkspaceAgentStartupLogOverflowByID = `-- name: UpdateWorkspaceAgentStartupLogOverflowByID :exec -UPDATE - workspace_agents -SET - startup_logs_overflowed = $2 -WHERE - id = $1 -` - -type UpdateWorkspaceAgentStartupLogOverflowByIDParams struct { - ID uuid.UUID `db:"id" json:"id"` - StartupLogsOverflowed bool `db:"startup_logs_overflowed" json:"startup_logs_overflowed"` -} - -func (q *sqlQuerier) UpdateWorkspaceAgentStartupLogOverflowByID(ctx context.Context, arg UpdateWorkspaceAgentStartupLogOverflowByIDParams) error { - _, err := q.db.ExecContext(ctx, updateWorkspaceAgentStartupLogOverflowByID, arg.ID, arg.StartupLogsOverflowed) - return err -} - const deleteOldWorkspaceAgentStats = `-- name: DeleteOldWorkspaceAgentStats :exec DELETE FROM workspace_agent_stats WHERE created_at < NOW() - INTERVAL '30 days' ` diff --git a/coderd/database/queries/workspaceagents.sql b/coderd/database/queries/workspaceagents.sql index 03168685aa37b..4025ac7e59a1b 100644 --- a/coderd/database/queries/workspaceagents.sql +++ b/coderd/database/queries/workspaceagents.sql @@ -140,43 +140,44 @@ FROM WHERE workspace_agent_id = $1; --- name: UpdateWorkspaceAgentStartupLogOverflowByID :exec +-- name: UpdateWorkspaceAgentLogOverflowByID :exec UPDATE workspace_agents SET - startup_logs_overflowed = $2 + logs_overflowed = $2 WHERE id = $1; --- name: GetWorkspaceAgentStartupLogsAfter :many +-- name: GetWorkspaceAgentLogsAfter :many SELECT * FROM - workspace_agent_startup_logs + workspace_agent_logs WHERE agent_id = $1 AND ( id > @created_after ) ORDER BY id ASC; --- name: InsertWorkspaceAgentStartupLogs :many +-- name: InsertWorkspaceAgentLogs :many WITH new_length AS ( UPDATE workspace_agents SET - startup_logs_length = startup_logs_length + @output_length WHERE workspace_agents.id = @agent_id + logs_length = logs_length + @output_length WHERE workspace_agents.id = @agent_id ) INSERT INTO - workspace_agent_startup_logs (agent_id, created_at, output, level) + workspace_agent_logs (agent_id, created_at, output, level, source) SELECT @agent_id :: uuid AS agent_id, unnest(@created_at :: timestamptz [ ]) AS created_at, unnest(@output :: VARCHAR(1024) [ ]) AS output, - unnest(@level :: log_level [ ]) AS level - RETURNING workspace_agent_startup_logs.*; + unnest(@level :: log_level [ ]) AS level, + unnest(@source :: workspace_agent_log_source [ ]) AS source + RETURNING workspace_agent_logs.*; -- If an agent hasn't connected in the last 7 days, we purge it's logs. -- Logs can take up a lot of space, so it's important we clean up frequently. --- name: DeleteOldWorkspaceAgentStartupLogs :exec -DELETE FROM workspace_agent_startup_logs WHERE agent_id IN +-- name: DeleteOldWorkspaceAgentLogs :exec +DELETE FROM workspace_agent_logs WHERE agent_id IN (SELECT id FROM workspace_agents WHERE last_connected_at IS NOT NULL AND last_connected_at < NOW() - INTERVAL '7 day'); diff --git a/coderd/deprecated.go b/coderd/deprecated.go index 0911724de884e..59e23b8d48136 100644 --- a/coderd/deprecated.go +++ b/coderd/deprecated.go @@ -27,3 +27,32 @@ func templateVersionParametersDeprecated(rw http.ResponseWriter, r *http.Request func templateVersionSchemaDeprecated(rw http.ResponseWriter, r *http.Request) { httpapi.Write(r.Context(), rw, http.StatusOK, []struct{}{}) } + +// @Summary Removed: Patch workspace agent logs +// @ID removed-patch-workspace-agent-logs +// @Security CoderSessionToken +// @Accept json +// @Produce json +// @Tags Agents +// @Param request body agentsdk.PatchLogs true "logs" +// @Success 200 {object} codersdk.Response +// @Router /workspaceagents/me/startup-logs [patch] +func (api *API) patchWorkspaceAgentLogsDeprecated(rw http.ResponseWriter, r *http.Request) { + api.patchWorkspaceAgentLogs(rw, r) +} + +// @Summary Removed: Get logs by workspace agent +// @ID removed-get-logs-by-workspace-agent +// @Security CoderSessionToken +// @Produce json +// @Tags Agents +// @Param workspaceagent path string true "Workspace agent ID" format(uuid) +// @Param before query int false "Before log id" +// @Param after query int false "After log id" +// @Param follow query bool false "Follow log stream" +// @Param no_compression query bool false "Disable compression for WebSocket connection" +// @Success 200 {array} codersdk.WorkspaceAgentLog +// @Router /workspaceagents/{workspaceagent}/startup-logs [get] +func (api *API) workspaceAgentLogsDeprecated(rw http.ResponseWriter, r *http.Request) { + api.workspaceAgentLogs(rw, r) +} diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index c0a4f064cb392..4596b2f27f661 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -234,20 +234,20 @@ func (api *API) postWorkspaceAgentStartup(rw http.ResponseWriter, r *http.Reques httpapi.Write(ctx, rw, http.StatusOK, nil) } -// @Summary Patch workspace agent startup logs -// @ID patch-workspace-agent-startup-logs +// @Summary Patch workspace agent logs +// @ID patch-workspace-agent-logs // @Security CoderSessionToken // @Accept json // @Produce json // @Tags Agents -// @Param request body agentsdk.PatchStartupLogs true "Startup logs" +// @Param request body agentsdk.PatchLogs true "logs" // @Success 200 {object} codersdk.Response -// @Router /workspaceagents/me/startup-logs [patch] -func (api *API) patchWorkspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Request) { +// @Router /workspaceagents/me/logs [patch] +func (api *API) patchWorkspaceAgentLogs(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() workspaceAgent := httpmw.WorkspaceAgent(r) - var req agentsdk.PatchStartupLogs + var req agentsdk.PatchLogs if !httpapi.Read(ctx, rw, r, &req) { return } @@ -260,6 +260,7 @@ func (api *API) patchWorkspaceAgentStartupLogs(rw http.ResponseWriter, r *http.R createdAt := make([]time.Time, 0) output := make([]string, 0) level := make([]database.LogLevel, 0) + source := make([]database.WorkspaceAgentLogSource, 0) outputLength := 0 for _, logEntry := range req.Logs { createdAt = append(createdAt, logEntry.CreatedAt) @@ -278,39 +279,54 @@ func (api *API) patchWorkspaceAgentStartupLogs(rw http.ResponseWriter, r *http.R return } level = append(level, parsedLevel) + + if logEntry.Source == "" { + // Default to "startup_script" to support older agents that didn't have the source field. + logEntry.Source = codersdk.WorkspaceAgentLogSourceStartupScript + } + parsedSource := database.WorkspaceAgentLogSource(logEntry.Source) + if !parsedSource.Valid() { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "Invalid log source provided.", + Detail: fmt.Sprintf("invalid log source: %q", logEntry.Source), + }) + return + } + source = append(source, parsedSource) } - logs, err := api.Database.InsertWorkspaceAgentStartupLogs(ctx, database.InsertWorkspaceAgentStartupLogsParams{ + logs, err := api.Database.InsertWorkspaceAgentLogs(ctx, database.InsertWorkspaceAgentLogsParams{ AgentID: workspaceAgent.ID, CreatedAt: createdAt, Output: output, Level: level, + Source: source, OutputLength: int32(outputLength), }) if err != nil { - if !database.IsStartupLogsLimitError(err) { + if !database.IsWorkspaceAgentLogsLimitError(err) { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Failed to upload startup logs", + Message: "Failed to upload logs", Detail: err.Error(), }) return } - if workspaceAgent.StartupLogsOverflowed { + if workspaceAgent.LogsOverflowed { httpapi.Write(ctx, rw, http.StatusRequestEntityTooLarge, codersdk.Response{ - Message: "Startup logs limit exceeded", + Message: "Logs limit exceeded", Detail: err.Error(), }) return } - err := api.Database.UpdateWorkspaceAgentStartupLogOverflowByID(ctx, database.UpdateWorkspaceAgentStartupLogOverflowByIDParams{ - ID: workspaceAgent.ID, - StartupLogsOverflowed: true, + err := api.Database.UpdateWorkspaceAgentLogOverflowByID(ctx, database.UpdateWorkspaceAgentLogOverflowByIDParams{ + ID: workspaceAgent.ID, + LogsOverflowed: true, }) if err != nil { // We don't want to return here, because the agent will retry // on failure and this isn't a huge deal. The overflow state // is just a hint to the user that the logs are incomplete. - api.Logger.Warn(ctx, "failed to update workspace agent startup log overflow", slog.Error(err)) + api.Logger.Warn(ctx, "failed to update workspace agent log overflow", slog.Error(err)) } resource, err := api.Database.GetWorkspaceResourceByID(ctx, workspaceAgent.ResourceID) @@ -334,7 +350,7 @@ func (api *API) patchWorkspaceAgentStartupLogs(rw http.ResponseWriter, r *http.R api.publishWorkspaceUpdate(ctx, build.WorkspaceID) httpapi.Write(ctx, rw, http.StatusRequestEntityTooLarge, codersdk.Response{ - Message: "Startup logs limit exceeded", + Message: "Logs limit exceeded", }) return } @@ -343,11 +359,11 @@ func (api *API) patchWorkspaceAgentStartupLogs(rw http.ResponseWriter, r *http.R // Publish by the lowest log ID inserted so the // log stream will fetch everything from that point. - api.publishWorkspaceAgentStartupLogsUpdate(ctx, workspaceAgent.ID, agentsdk.StartupLogsNotifyMessage{ + api.publishWorkspaceAgentLogsUpdate(ctx, workspaceAgent.ID, agentsdk.LogsNotifyMessage{ CreatedAfter: lowestLogID - 1, }) - if workspaceAgent.StartupLogsLength == 0 { + if workspaceAgent.LogsLength == 0 { // If these are the first logs being appended, we publish a UI update // to notify the UI that logs are now available. resource, err := api.Database.GetWorkspaceResourceByID(ctx, workspaceAgent.ResourceID) @@ -374,11 +390,10 @@ func (api *API) patchWorkspaceAgentStartupLogs(rw http.ResponseWriter, r *http.R httpapi.Write(ctx, rw, http.StatusOK, nil) } -// workspaceAgentStartupLogs returns the logs sent from a workspace agent -// during startup. +// workspaceAgentLogs returns the logs associated with a workspace agent // -// @Summary Get startup logs by workspace agent -// @ID get-startup-logs-by-workspace-agent +// @Summary Get logs by workspace agent +// @ID get-logs-by-workspace-agent // @Security CoderSessionToken // @Produce json // @Tags Agents @@ -387,9 +402,9 @@ func (api *API) patchWorkspaceAgentStartupLogs(rw http.ResponseWriter, r *http.R // @Param after query int false "After log id" // @Param follow query bool false "Follow log stream" // @Param no_compression query bool false "Disable compression for WebSocket connection" -// @Success 200 {array} codersdk.WorkspaceAgentStartupLog -// @Router /workspaceagents/{workspaceagent}/startup-logs [get] -func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Request) { +// @Success 200 {array} codersdk.WorkspaceAgentLog +// @Router /workspaceagents/{workspaceagent}/logs [get] +func (api *API) workspaceAgentLogs(rw http.ResponseWriter, r *http.Request) { // This mostly copies how provisioner job logs are streamed! var ( ctx = r.Context() @@ -416,7 +431,7 @@ func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Reques } } - logs, err := api.Database.GetWorkspaceAgentStartupLogsAfter(ctx, database.GetWorkspaceAgentStartupLogsAfterParams{ + logs, err := api.Database.GetWorkspaceAgentLogsAfter(ctx, database.GetWorkspaceAgentLogsAfterParams{ AgentID: workspaceAgent.ID, CreatedAfter: after, }) @@ -431,11 +446,11 @@ func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Reques return } if logs == nil { - logs = []database.WorkspaceAgentStartupLog{} + logs = []database.WorkspaceAgentLog{} } if !follow { - httpapi.Write(ctx, rw, http.StatusOK, convertWorkspaceAgentStartupLogs(logs)) + httpapi.Write(ctx, rw, http.StatusOK, convertWorkspaceAgentLogs(logs)) return } @@ -472,7 +487,7 @@ func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Reques // The Go stdlib JSON encoder appends a newline character after message write. encoder := json.NewEncoder(wsNetConn) - err = encoder.Encode(convertWorkspaceAgentStartupLogs(logs)) + err = encoder.Encode(convertWorkspaceAgentLogs(logs)) if err != nil { return } @@ -488,7 +503,7 @@ func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Reques notifyCh <- struct{}{} // Subscribe early to prevent missing log events. - closeSubscribe, err := api.Pubsub.Subscribe(agentsdk.StartupLogsNotifyChannel(workspaceAgent.ID), func(_ context.Context, _ []byte) { + closeSubscribe, err := api.Pubsub.Subscribe(agentsdk.LogsNotifyChannel(workspaceAgent.ID), func(_ context.Context, _ []byte) { // The message is not important, we're tracking lastSentLogID manually. select { case notifyCh <- struct{}{}: @@ -497,7 +512,7 @@ func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Reques }) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Failed to subscribe to startup logs.", + Message: "Failed to subscribe to logs.", Detail: err.Error(), }) return @@ -505,7 +520,7 @@ func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Reques defer closeSubscribe() // Buffer size controls the log prefetch capacity. - bufferedLogs := make(chan []database.WorkspaceAgentStartupLog, 8) + bufferedLogs := make(chan []database.WorkspaceAgentLog, 8) // Check at least once per minute in case we didn't receive a pubsub message. recheckInterval := time.Minute t := time.NewTicker(recheckInterval) @@ -523,7 +538,7 @@ func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Reques t.Reset(recheckInterval) } - logs, err := api.Database.GetWorkspaceAgentStartupLogsAfter(ctx, database.GetWorkspaceAgentStartupLogsAfterParams{ + logs, err := api.Database.GetWorkspaceAgentLogsAfter(ctx, database.GetWorkspaceAgentLogsAfterParams{ AgentID: workspaceAgent.ID, CreatedAfter: lastSentLogID, }) @@ -531,7 +546,7 @@ func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Reques if xerrors.Is(err, context.Canceled) { return } - logger.Warn(ctx, "failed to get workspace agent startup logs after", slog.Error(err)) + logger.Warn(ctx, "failed to get workspace agent logs after", slog.Error(err)) continue } if len(logs) == 0 { @@ -569,7 +584,7 @@ func (api *API) workspaceAgentStartupLogs(rw http.ResponseWriter, r *http.Reques } return } - err = encoder.Encode(convertWorkspaceAgentStartupLogs(logs)) + err = encoder.Encode(convertWorkspaceAgentLogs(logs)) if err != nil { return } @@ -1244,8 +1259,8 @@ func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.Coordin StartupScript: dbAgent.StartupScript.String, StartupScriptBehavior: codersdk.WorkspaceAgentStartupScriptBehavior(dbAgent.StartupScriptBehavior), StartupScriptTimeoutSeconds: dbAgent.StartupScriptTimeoutSeconds, - StartupLogsLength: dbAgent.StartupLogsLength, - StartupLogsOverflowed: dbAgent.StartupLogsOverflowed, + LogsLength: dbAgent.LogsLength, + LogsOverflowed: dbAgent.LogsOverflowed, Version: dbAgent.Version, EnvironmentVariables: envs, Directory: dbAgent.Directory, @@ -2076,16 +2091,16 @@ func websocketNetConn(ctx context.Context, conn *websocket.Conn, msgType websock } } -func convertWorkspaceAgentStartupLogs(logs []database.WorkspaceAgentStartupLog) []codersdk.WorkspaceAgentStartupLog { - sdk := make([]codersdk.WorkspaceAgentStartupLog, 0, len(logs)) +func convertWorkspaceAgentLogs(logs []database.WorkspaceAgentLog) []codersdk.WorkspaceAgentLog { + sdk := make([]codersdk.WorkspaceAgentLog, 0, len(logs)) for _, logEntry := range logs { - sdk = append(sdk, convertWorkspaceAgentStartupLog(logEntry)) + sdk = append(sdk, convertWorkspaceAgentLog(logEntry)) } return sdk } -func convertWorkspaceAgentStartupLog(logEntry database.WorkspaceAgentStartupLog) codersdk.WorkspaceAgentStartupLog { - return codersdk.WorkspaceAgentStartupLog{ +func convertWorkspaceAgentLog(logEntry database.WorkspaceAgentLog) codersdk.WorkspaceAgentLog { + return codersdk.WorkspaceAgentLog{ ID: logEntry.ID, CreatedAt: logEntry.CreatedAt, Output: logEntry.Output, diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 87603361780e1..c7d13b3393541 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -213,8 +213,8 @@ func TestWorkspaceAgentStartupLogs(t *testing.T) { agentClient := agentsdk.New(client.URL) agentClient.SetSessionToken(authToken) - err := agentClient.PatchStartupLogs(ctx, agentsdk.PatchStartupLogs{ - Logs: []agentsdk.StartupLog{ + err := agentClient.PatchLogs(ctx, agentsdk.PatchLogs{ + Logs: []agentsdk.Log{ { CreatedAt: database.Now(), Output: "testing", @@ -227,12 +227,12 @@ func TestWorkspaceAgentStartupLogs(t *testing.T) { }) require.NoError(t, err) - logs, closer, err := client.WorkspaceAgentStartupLogsAfter(ctx, build.Resources[0].Agents[0].ID, 0, true) + logs, closer, err := client.WorkspaceAgentLogsAfter(ctx, build.Resources[0].Agents[0].ID, 0, true) require.NoError(t, err) defer func() { _ = closer.Close() }() - var logChunk []codersdk.WorkspaceAgentStartupLog + var logChunk []codersdk.WorkspaceAgentLog select { case <-ctx.Done(): case logChunk = <-logs: @@ -280,8 +280,8 @@ func TestWorkspaceAgentStartupLogs(t *testing.T) { agentClient := agentsdk.New(client.URL) agentClient.SetSessionToken(authToken) - err = agentClient.PatchStartupLogs(ctx, agentsdk.PatchStartupLogs{ - Logs: []agentsdk.StartupLog{{ + err = agentClient.PatchLogs(ctx, agentsdk.PatchLogs{ + Logs: []agentsdk.Log{{ CreatedAt: database.Now(), Output: strings.Repeat("a", (1<<20)+1), }}, @@ -299,7 +299,7 @@ func TestWorkspaceAgentStartupLogs(t *testing.T) { t.FailNow() case update = <-updates: } - if update.LatestBuild.Resources[0].Agents[0].StartupLogsOverflowed { + if update.LatestBuild.Resources[0].Agents[0].LogsOverflowed { break } } diff --git a/coderd/workspaces.go b/coderd/workspaces.go index b8cc33ac8cf9e..8552b6aa593aa 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -1251,13 +1251,13 @@ func (api *API) publishWorkspaceUpdate(ctx context.Context, workspaceID uuid.UUI } } -func (api *API) publishWorkspaceAgentStartupLogsUpdate(ctx context.Context, workspaceAgentID uuid.UUID, m agentsdk.StartupLogsNotifyMessage) { +func (api *API) publishWorkspaceAgentLogsUpdate(ctx context.Context, workspaceAgentID uuid.UUID, m agentsdk.LogsNotifyMessage) { b, err := json.Marshal(m) if err != nil { - api.Logger.Warn(ctx, "failed to marshal startup logs notify message", slog.F("workspace_agent_id", workspaceAgentID), slog.Error(err)) + api.Logger.Warn(ctx, "failed to marshal logs notify message", slog.F("workspace_agent_id", workspaceAgentID), slog.Error(err)) } - err = api.Pubsub.Publish(agentsdk.StartupLogsNotifyChannel(workspaceAgentID), b) + err = api.Pubsub.Publish(agentsdk.LogsNotifyChannel(workspaceAgentID), b) if err != nil { - api.Logger.Warn(ctx, "failed to publish workspace agent startup logs update", slog.F("workspace_agent_id", workspaceAgentID), slog.Error(err)) + api.Logger.Warn(ctx, "failed to publish workspace agent logs update", slog.F("workspace_agent_id", workspaceAgentID), slog.Error(err)) } } diff --git a/coderd/wsconncache/wsconncache_test.go b/coderd/wsconncache/wsconncache_test.go index 00526fa0d27fb..d0769cf057316 100644 --- a/coderd/wsconncache/wsconncache_test.go +++ b/coderd/wsconncache/wsconncache_test.go @@ -274,7 +274,7 @@ func (*client) PostStartup(_ context.Context, _ agentsdk.PostStartupRequest) err return nil } -func (*client) PatchStartupLogs(_ context.Context, _ agentsdk.PatchStartupLogs) error { +func (*client) PatchLogs(_ context.Context, _ agentsdk.PatchLogs) error { return nil } diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index 9d3ad05aa1d79..f4b114438c010 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -621,20 +621,21 @@ func (c *Client) PostStartup(ctx context.Context, req PostStartupRequest) error return nil } -type StartupLog struct { - CreatedAt time.Time `json:"created_at"` - Output string `json:"output"` - Level codersdk.LogLevel `json:"level"` +type Log struct { + CreatedAt time.Time `json:"created_at"` + Output string `json:"output"` + Level codersdk.LogLevel `json:"level"` + Source codersdk.WorkspaceAgentLogSource `json:"source"` } -type PatchStartupLogs struct { - Logs []StartupLog `json:"logs"` +type PatchLogs struct { + Logs []Log `json:"logs"` } -// PatchStartupLogs writes log messages to the agent startup script. +// PatchLogs writes log messages to the agent startup script. // Log messages are limited to 1MB in total. -func (c *Client) PatchStartupLogs(ctx context.Context, req PatchStartupLogs) error { - res, err := c.SDK.Request(ctx, http.MethodPatch, "/api/v2/workspaceagents/me/startup-logs", req) +func (c *Client) PatchLogs(ctx context.Context, req PatchLogs) error { + res, err := c.SDK.Request(ctx, http.MethodPatch, "/api/v2/workspaceagents/me/logs", req) if err != nil { return err } @@ -737,13 +738,13 @@ func websocketNetConn(ctx context.Context, conn *websocket.Conn, msgType websock } } -// StartupLogsNotifyChannel returns the channel name responsible for notifying -// of new startup logs. -func StartupLogsNotifyChannel(agentID uuid.UUID) string { - return fmt.Sprintf("startup-logs:%s", agentID) +// LogsNotifyChannel returns the channel name responsible for notifying +// of new logs. +func LogsNotifyChannel(agentID uuid.UUID) string { + return fmt.Sprintf("agent-logs:%s", agentID) } -type StartupLogsNotifyMessage struct { +type LogsNotifyMessage struct { CreatedAfter int64 `json:"created_after"` } diff --git a/codersdk/agentsdk/logs.go b/codersdk/agentsdk/logs.go index 0185085b0d4b8..2eb7d4856f27e 100644 --- a/codersdk/agentsdk/logs.go +++ b/codersdk/agentsdk/logs.go @@ -16,10 +16,11 @@ import ( ) type startupLogsWriter struct { - buf bytes.Buffer // Buffer to track partial lines. - ctx context.Context - send func(ctx context.Context, log ...StartupLog) error - level codersdk.LogLevel + buf bytes.Buffer // Buffer to track partial lines. + ctx context.Context + send func(ctx context.Context, log ...Log) error + level codersdk.LogLevel + source codersdk.WorkspaceAgentLogSource } func (w *startupLogsWriter) Write(p []byte) (int, error) { @@ -39,10 +40,11 @@ func (w *startupLogsWriter) Write(p []byte) (int, error) { partial = w.buf.Bytes() w.buf.Reset() } - err := w.send(w.ctx, StartupLog{ + err := w.send(w.ctx, Log{ CreatedAt: time.Now().UTC(), // UTC, like database.Now(). Level: w.level, Output: string(partial) + string(p[:nl-cr]), + Source: w.source, }) if err != nil { return n - len(p), err @@ -61,10 +63,11 @@ func (w *startupLogsWriter) Write(p []byte) (int, error) { func (w *startupLogsWriter) Close() error { if w.buf.Len() > 0 { defer w.buf.Reset() - return w.send(w.ctx, StartupLog{ + return w.send(w.ctx, Log{ CreatedAt: time.Now().UTC(), // UTC, like database.Now(). Level: w.level, Output: w.buf.String(), + Source: w.source, }) } return nil @@ -78,20 +81,24 @@ func (w *startupLogsWriter) Close() error { // // Neither Write nor Close is safe for concurrent use and must be used // by a single goroutine. -func StartupLogsWriter(ctx context.Context, sender func(ctx context.Context, log ...StartupLog) error, level codersdk.LogLevel) io.WriteCloser { +func StartupLogsWriter(ctx context.Context, sender func(ctx context.Context, log ...Log) error, source codersdk.WorkspaceAgentLogSource, level codersdk.LogLevel) io.WriteCloser { + if source == "" { + source = codersdk.WorkspaceAgentLogSourceExternal + } return &startupLogsWriter{ - ctx: ctx, - send: sender, - level: level, + ctx: ctx, + send: sender, + level: level, + source: source, } } -// SendStartupLogs will send agent startup logs to the server. Calls to +// LogsSender will send agent startup logs to the server. Calls to // sendLog are non-blocking and will return an error if flushAndClose // has been called. Calling sendLog concurrently is not supported. If // the context passed to flushAndClose is canceled, any remaining logs // will be discarded. -func StartupLogsSender(patchStartupLogs func(ctx context.Context, req PatchStartupLogs) error, logger slog.Logger) (sendLog func(ctx context.Context, log ...StartupLog) error, flushAndClose func(context.Context) error) { +func LogsSender(patchLogs func(ctx context.Context, req PatchLogs) error, logger slog.Logger) (sendLog func(ctx context.Context, log ...Log) error, flushAndClose func(context.Context) error) { // The main context is used to close the sender goroutine and cancel // any outbound requests to the API. The shutdown context is used to // signal the sender goroutine to flush logs and then exit. @@ -100,7 +107,7 @@ func StartupLogsSender(patchStartupLogs func(ctx context.Context, req PatchStart // Synchronous sender, there can only be one outbound send at a time. sendDone := make(chan struct{}) - send := make(chan []StartupLog, 1) + send := make(chan []Log, 1) go func() { // Set flushTimeout and backlogLimit so that logs are uploaded // once every 250ms or when 100 logs have been added to the @@ -110,7 +117,7 @@ func StartupLogsSender(patchStartupLogs func(ctx context.Context, req PatchStart flush := time.NewTicker(flushTimeout) - var backlog []StartupLog + var backlog []Log defer func() { flush.Stop() if len(backlog) > 0 { @@ -150,7 +157,7 @@ func StartupLogsSender(patchStartupLogs func(ctx context.Context, req PatchStart // meaning these requests won't be interrupted by // shutdown. for r := retry.New(time.Second, 5*time.Second); r.Wait(ctx); { - err := patchStartupLogs(ctx, PatchStartupLogs{ + err := patchLogs(ctx, PatchLogs{ Logs: backlog, }) if err == nil { @@ -185,8 +192,8 @@ func StartupLogsSender(patchStartupLogs func(ctx context.Context, req PatchStart } }() - var queue []StartupLog - sendLog = func(callCtx context.Context, log ...StartupLog) error { + var queue []Log + sendLog = func(callCtx context.Context, log ...Log) error { select { case <-shutdownCtx.Done(): return xerrors.Errorf("closed: %w", shutdownCtx.Err()) diff --git a/codersdk/agentsdk/logs_test.go b/codersdk/agentsdk/logs_test.go index f14e20a8dfa1e..1cca86242e065 100644 --- a/codersdk/agentsdk/logs_test.go +++ b/codersdk/agentsdk/logs_test.go @@ -28,8 +28,9 @@ func TestStartupLogsWriter_Write(t *testing.T) { name string ctx context.Context level codersdk.LogLevel + source codersdk.WorkspaceAgentLogSource writes []string - want []agentsdk.StartupLog + want []agentsdk.Log wantErr bool closeFirst bool }{ @@ -38,10 +39,12 @@ func TestStartupLogsWriter_Write(t *testing.T) { ctx: context.Background(), level: codersdk.LogLevelInfo, writes: []string{"hello world\n"}, - want: []agentsdk.StartupLog{ + source: codersdk.WorkspaceAgentLogSourceShutdownScript, + want: []agentsdk.Log{ { Level: codersdk.LogLevelInfo, Output: "hello world", + Source: codersdk.WorkspaceAgentLogSourceShutdownScript, }, }, }, @@ -50,14 +53,16 @@ func TestStartupLogsWriter_Write(t *testing.T) { ctx: context.Background(), level: codersdk.LogLevelInfo, writes: []string{"hello world\n", "goodbye world\n"}, - want: []agentsdk.StartupLog{ + want: []agentsdk.Log{ { Level: codersdk.LogLevelInfo, Output: "hello world", + Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "goodbye world", + Source: codersdk.WorkspaceAgentLogSourceExternal, }, }, }, @@ -66,30 +71,36 @@ func TestStartupLogsWriter_Write(t *testing.T) { ctx: context.Background(), level: codersdk.LogLevelInfo, writes: []string{"\n\n", "hello world\n\n\n", "goodbye world\n"}, - want: []agentsdk.StartupLog{ + want: []agentsdk.Log{ { Level: codersdk.LogLevelInfo, Output: "", + Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "", + Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "hello world", + Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "", + Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "", + Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "goodbye world", + Source: codersdk.WorkspaceAgentLogSourceExternal, }, }, }, @@ -98,10 +109,11 @@ func TestStartupLogsWriter_Write(t *testing.T) { ctx: context.Background(), level: codersdk.LogLevelInfo, writes: []string{"hello world\n", "goodbye world"}, - want: []agentsdk.StartupLog{ + want: []agentsdk.Log{ { Level: codersdk.LogLevelInfo, Output: "hello world", + Source: codersdk.WorkspaceAgentLogSourceExternal, }, }, }, @@ -111,14 +123,16 @@ func TestStartupLogsWriter_Write(t *testing.T) { level: codersdk.LogLevelInfo, writes: []string{"hello world\n", "goodbye world"}, closeFirst: true, - want: []agentsdk.StartupLog{ + want: []agentsdk.Log{ { Level: codersdk.LogLevelInfo, Output: "hello world", + Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "goodbye world", + Source: codersdk.WorkspaceAgentLogSourceExternal, }, }, }, @@ -127,14 +141,16 @@ func TestStartupLogsWriter_Write(t *testing.T) { ctx: context.Background(), level: codersdk.LogLevelInfo, writes: []string{"hello world\n", "goodbye", " world\n"}, - want: []agentsdk.StartupLog{ + want: []agentsdk.Log{ { Level: codersdk.LogLevelInfo, Output: "hello world", + Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "goodbye world", + Source: codersdk.WorkspaceAgentLogSourceExternal, }, }, }, @@ -143,18 +159,21 @@ func TestStartupLogsWriter_Write(t *testing.T) { ctx: context.Background(), level: codersdk.LogLevelInfo, writes: []string{"hello world\r\n", "\r\r\n", "goodbye world\n"}, - want: []agentsdk.StartupLog{ + want: []agentsdk.Log{ { Level: codersdk.LogLevelInfo, Output: "hello world", + Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "\r", + Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "goodbye world", + Source: codersdk.WorkspaceAgentLogSourceExternal, }, }, }, @@ -172,8 +191,8 @@ func TestStartupLogsWriter_Write(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - var got []agentsdk.StartupLog - send := func(ctx context.Context, log ...agentsdk.StartupLog) error { + var got []agentsdk.Log + send := func(ctx context.Context, log ...agentsdk.Log) error { select { case <-ctx.Done(): return ctx.Err() @@ -182,7 +201,7 @@ func TestStartupLogsWriter_Write(t *testing.T) { got = append(got, log...) return nil } - w := agentsdk.StartupLogsWriter(tt.ctx, send, tt.level) + w := agentsdk.StartupLogsWriter(tt.ctx, send, tt.source, tt.level) for _, s := range tt.writes { _, err := w.Write([]byte(s)) if err != nil { @@ -233,7 +252,7 @@ func TestStartupLogsSender(t *testing.T) { name string sendCount int discard []int - patchResp func(req agentsdk.PatchStartupLogs) error + patchResp func(req agentsdk.PatchLogs) error }{ { name: "single log", @@ -247,7 +266,7 @@ func TestStartupLogsSender(t *testing.T) { name: "too large", sendCount: 1, discard: []int{1}, - patchResp: func(req agentsdk.PatchStartupLogs) error { + patchResp: func(req agentsdk.PatchLogs) error { return statusError(http.StatusRequestEntityTooLarge) }, }, @@ -260,8 +279,8 @@ func TestStartupLogsSender(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium) defer cancel() - got := []agentsdk.StartupLog{} - patchStartupLogs := func(_ context.Context, req agentsdk.PatchStartupLogs) error { + got := []agentsdk.Log{} + patchLogs := func(_ context.Context, req agentsdk.PatchLogs) error { if tt.patchResp != nil { err := tt.patchResp(req) if err != nil { @@ -272,15 +291,15 @@ func TestStartupLogsSender(t *testing.T) { return nil } - sendLog, flushAndClose := agentsdk.StartupLogsSender(patchStartupLogs, slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + sendLog, flushAndClose := agentsdk.LogsSender(patchLogs, slogtest.Make(t, nil).Leveled(slog.LevelDebug)) defer func() { err := flushAndClose(ctx) require.NoError(t, err) }() - var want []agentsdk.StartupLog + var want []agentsdk.Log for i := 0; i < tt.sendCount; i++ { - want = append(want, agentsdk.StartupLog{ + want = append(want, agentsdk.Log{ CreatedAt: time.Now(), Level: codersdk.LogLevelInfo, Output: fmt.Sprintf("hello world %d", i), @@ -306,18 +325,18 @@ func TestStartupLogsSender(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) defer cancel() - patchStartupLogs := func(_ context.Context, _ agentsdk.PatchStartupLogs) error { + patchLogs := func(_ context.Context, _ agentsdk.PatchLogs) error { assert.Fail(t, "should not be called") return nil } - sendLog, flushAndClose := agentsdk.StartupLogsSender(patchStartupLogs, slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + sendLog, flushAndClose := agentsdk.LogsSender(patchLogs, slogtest.Make(t, nil).Leveled(slog.LevelDebug)) defer func() { _ = flushAndClose(ctx) }() cancel() - err := sendLog(ctx, agentsdk.StartupLog{ + err := sendLog(ctx, agentsdk.Log{ CreatedAt: time.Now(), Level: codersdk.LogLevelInfo, Output: "hello world", @@ -336,18 +355,18 @@ func TestStartupLogsSender(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) defer cancel() - var want, got []agentsdk.StartupLog - patchStartupLogs := func(_ context.Context, req agentsdk.PatchStartupLogs) error { + var want, got []agentsdk.Log + patchLogs := func(_ context.Context, req agentsdk.PatchLogs) error { got = append(got, req.Logs...) return nil } - sendLog, flushAndClose := agentsdk.StartupLogsSender(patchStartupLogs, slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + sendLog, flushAndClose := agentsdk.LogsSender(patchLogs, slogtest.Make(t, nil).Leveled(slog.LevelDebug)) defer func() { _ = flushAndClose(ctx) }() - err := sendLog(ctx, agentsdk.StartupLog{ + err := sendLog(ctx, agentsdk.Log{ CreatedAt: time.Now(), Level: codersdk.LogLevelInfo, Output: "hello world", diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index b250f3a98e1a9..e9aad8421e36a 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -153,8 +153,8 @@ type WorkspaceAgent struct { StartupScript string `json:"startup_script,omitempty"` StartupScriptBehavior WorkspaceAgentStartupScriptBehavior `json:"startup_script_behavior"` StartupScriptTimeoutSeconds int32 `json:"startup_script_timeout_seconds"` // StartupScriptTimeoutSeconds is the number of seconds to wait for the startup script to complete. If the script does not complete within this time, the agent lifecycle will be marked as start_timeout. - StartupLogsLength int32 `json:"startup_logs_length"` - StartupLogsOverflowed bool `json:"startup_logs_overflowed"` + LogsLength int32 `json:"logs_length"` + LogsOverflowed bool `json:"logs_overflowed"` Directory string `json:"directory,omitempty"` ExpandedDirectory string `json:"expanded_directory,omitempty"` Version string `json:"version"` @@ -628,7 +628,7 @@ func (c *Client) WorkspaceAgentListeningPorts(ctx context.Context, agentID uuid. } //nolint:revive // Follow is a control flag on the server as well. -func (c *Client) WorkspaceAgentStartupLogsAfter(ctx context.Context, agentID uuid.UUID, after int64, follow bool) (<-chan []WorkspaceAgentStartupLog, io.Closer, error) { +func (c *Client) WorkspaceAgentLogsAfter(ctx context.Context, agentID uuid.UUID, after int64, follow bool) (<-chan []WorkspaceAgentLog, io.Closer, error) { var queryParams []string if after != 0 { queryParams = append(queryParams, fmt.Sprintf("after=%d", after)) @@ -640,7 +640,7 @@ func (c *Client) WorkspaceAgentStartupLogsAfter(ctx context.Context, agentID uui if len(queryParams) > 0 { query = "?" + strings.Join(queryParams, "&") } - reqURL, err := c.URL.Parse(fmt.Sprintf("/api/v2/workspaceagents/%s/startup-logs%s", agentID, query)) + reqURL, err := c.URL.Parse(fmt.Sprintf("/api/v2/workspaceagents/%s/logs%s", agentID, query)) if err != nil { return nil, nil, err } @@ -656,13 +656,13 @@ func (c *Client) WorkspaceAgentStartupLogsAfter(ctx context.Context, agentID uui return nil, nil, ReadBodyAsError(resp) } - var logs []WorkspaceAgentStartupLog + var logs []WorkspaceAgentLog err = json.NewDecoder(resp.Body).Decode(&logs) if err != nil { return nil, nil, xerrors.Errorf("decode startup logs: %w", err) } - ch := make(chan []WorkspaceAgentStartupLog, 1) + ch := make(chan []WorkspaceAgentLog, 1) ch <- logs close(ch) return ch, closeFunc(func() error { return nil }), nil @@ -690,7 +690,7 @@ func (c *Client) WorkspaceAgentStartupLogsAfter(ctx context.Context, agentID uui } return nil, nil, ReadBodyAsError(res) } - logChunks := make(chan []WorkspaceAgentStartupLog) + logChunks := make(chan []WorkspaceAgentLog) closed := make(chan struct{}) ctx, wsNetConn := websocketNetConn(ctx, conn, websocket.MessageText) decoder := json.NewDecoder(wsNetConn) @@ -699,7 +699,7 @@ func (c *Client) WorkspaceAgentStartupLogsAfter(ctx context.Context, agentID uui defer close(logChunks) defer conn.Close(websocket.StatusGoingAway, "") for { - var logs []WorkspaceAgentStartupLog + var logs []WorkspaceAgentLog err = decoder.Decode(&logs) if err != nil { return @@ -744,7 +744,7 @@ const ( GitProviderBitBucket GitProvider = "bitbucket" ) -type WorkspaceAgentStartupLog struct { +type WorkspaceAgentLog struct { ID int64 `json:"id"` CreatedAt time.Time `json:"created_at" format:"date-time"` Output string `json:"output"` @@ -756,3 +756,14 @@ type AgentSubsystem string const ( AgentSubsystemEnvbox AgentSubsystem = "envbox" ) + +type WorkspaceAgentLogSource string + +const ( + WorkspaceAgentLogSourceStartupScript WorkspaceAgentLogSource = "startup_script" + WorkspaceAgentLogSourceShutdownScript WorkspaceAgentLogSource = "shutdown_script" + WorkspaceAgentLogSourceKubernetes WorkspaceAgentLogSource = "kubernetes" + WorkspaceAgentLogSourceEnvbox WorkspaceAgentLogSource = "envbox" + WorkspaceAgentLogSourceEnvbuilder WorkspaceAgentLogSource = "envbuilder" + WorkspaceAgentLogSourceExternal WorkspaceAgentLogSource = "external" +) diff --git a/docs/admin/automation.md b/docs/admin/automation.md index b7bf28dbdca5c..c564903d55361 100644 --- a/docs/admin/automation.md +++ b/docs/admin/automation.md @@ -44,10 +44,10 @@ We strive to keep the following use cases up to date, but please note that chang Workspace agents have a special token that can send logs, metrics, and workspace activity. -- [Custom workspace logs](../api/agents.md#patch-workspace-agent-startup-logs): Expose messages prior to the Coder init script running (e.g. pulling image, VM starting, restoring snapshot). [coder-logstream-kube](https://github.com/coder/coder-logstream-kube) uses this to show Kubernetes events, such as image pulls or ResourceQuota restrictions. +- [Custom workspace logs](../api/agents.md#patch-workspace-agent-logs): Expose messages prior to the Coder init script running (e.g. pulling image, VM starting, restoring snapshot). [coder-logstream-kube](https://github.com/coder/coder-logstream-kube) uses this to show Kubernetes events, such as image pulls or ResourceQuota restrictions. ```sh - curl -X PATCH https://coder.example.com/api/v2/workspaceagents/me/startup-logs \ + curl -X PATCH https://coder.example.com/api/v2/workspaceagents/me/logs \ -H "Coder-Session-Token: $CODER_AGENT_TOKEN" \ -d "{ \"logs\": [ diff --git a/docs/api/agents.md b/docs/api/agents.md index 919fa06923c82..2316fdaafde51 100644 --- a/docs/api/agents.md +++ b/docs/api/agents.md @@ -293,6 +293,66 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/gitsshkey \ To perform this operation, you must be authenticated. [Learn more](authentication.md). +## Patch workspace agent logs + +### Code samples + +```shell +# Example request using curl +curl -X PATCH http://coder-server:8080/api/v2/workspaceagents/me/logs \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`PATCH /workspaceagents/me/logs` + +> Body parameter + +```json +{ + "logs": [ + { + "created_at": "string", + "level": "trace", + "output": "string", + "source": "startup_script" + } + ] +} +``` + +### Parameters + +| Name | In | Type | Required | Description | +| ------ | ---- | -------------------------------------------------- | -------- | ----------- | +| `body` | body | [agentsdk.PatchLogs](schemas.md#agentsdkpatchlogs) | true | logs | + +### Example responses + +> 200 Response + +```json +{ + "detail": "string", + "message": "string", + "validations": [ + { + "detail": "string", + "field": "string" + } + ] +} +``` + +### Responses + +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------ | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Response](schemas.md#codersdkresponse) | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). + ## Get authorized workspace agent manifest ### Code samples @@ -488,7 +548,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaceagents/me/report-stats \ To perform this operation, you must be authenticated. [Learn more](authentication.md). -## Patch workspace agent startup logs +## Removed: Patch workspace agent logs ### Code samples @@ -510,7 +570,8 @@ curl -X PATCH http://coder-server:8080/api/v2/workspaceagents/me/startup-logs \ { "created_at": "string", "level": "trace", - "output": "string" + "output": "string", + "source": "startup_script" } ] } @@ -518,9 +579,9 @@ curl -X PATCH http://coder-server:8080/api/v2/workspaceagents/me/startup-logs \ ### Parameters -| Name | In | Type | Required | Description | -| ------ | ---- | ---------------------------------------------------------------- | -------- | ------------ | -| `body` | body | [agentsdk.PatchStartupLogs](schemas.md#agentsdkpatchstartuplogs) | true | Startup logs | +| Name | In | Type | Required | Description | +| ------ | ---- | -------------------------------------------------- | -------- | ----------- | +| `body` | body | [agentsdk.PatchLogs](schemas.md#agentsdkpatchlogs) | true | logs | ### Example responses @@ -621,6 +682,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent} \ }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -628,8 +691,6 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent} \ "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, @@ -806,6 +867,74 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/lis To perform this operation, you must be authenticated. [Learn more](authentication.md). +## Get logs by workspace agent + +### Code samples + +```shell +# Example request using curl +curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/logs \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`GET /workspaceagents/{workspaceagent}/logs` + +### Parameters + +| Name | In | Type | Required | Description | +| ---------------- | ----- | ------------ | -------- | -------------------------------------------- | +| `workspaceagent` | path | string(uuid) | true | Workspace agent ID | +| `before` | query | integer | false | Before log id | +| `after` | query | integer | false | After log id | +| `follow` | query | boolean | false | Follow log stream | +| `no_compression` | query | boolean | false | Disable compression for WebSocket connection | + +### Example responses + +> 200 Response + +```json +[ + { + "created_at": "2019-08-24T14:15:22Z", + "id": 0, + "level": "trace", + "output": "string" + } +] +``` + +### Responses + +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | --------------------------------------------------------------------------- | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of [codersdk.WorkspaceAgentLog](schemas.md#codersdkworkspaceagentlog) | + +

Response Schema

+ +Status Code **200** + +| Name | Type | Required | Restrictions | Description | +| -------------- | ------------------------------------------------ | -------- | ------------ | ----------- | +| `[array item]` | array | false | | | +| `» created_at` | string(date-time) | false | | | +| `» id` | integer | false | | | +| `» level` | [codersdk.LogLevel](schemas.md#codersdkloglevel) | false | | | +| `» output` | string | false | | | + +#### Enumerated Values + +| Property | Value | +| -------- | ------- | +| `level` | `trace` | +| `level` | `debug` | +| `level` | `info` | +| `level` | `warn` | +| `level` | `error` | + +To perform this operation, you must be authenticated. [Learn more](authentication.md). + ## Open PTY to workspace agent ### Code samples @@ -832,7 +961,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/pty To perform this operation, you must be authenticated. [Learn more](authentication.md). -## Get startup logs by workspace agent +## Removed: Get logs by workspace agent ### Code samples @@ -872,11 +1001,11 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/sta ### Responses -| Status | Meaning | Description | Schema | -| ------ | ------------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------- | -| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of [codersdk.WorkspaceAgentStartupLog](schemas.md#codersdkworkspaceagentstartuplog) | +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | --------------------------------------------------------------------------- | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of [codersdk.WorkspaceAgentLog](schemas.md#codersdkworkspaceagentlog) | -

Response Schema

+

Response Schema

Status Code **200** diff --git a/docs/api/builds.md b/docs/api/builds.md index 665b8778b6865..daf7958de387f 100644 --- a/docs/api/builds.md +++ b/docs/api/builds.md @@ -107,6 +107,8 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -114,8 +116,6 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, @@ -269,6 +269,8 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \ }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -276,8 +278,6 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \ "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, @@ -570,6 +570,8 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/res }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -577,8 +579,6 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/res "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, @@ -659,6 +659,8 @@ Status Code **200** | `»»»» preferred` | boolean | false | | | | `»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | | | `»» login_before_ready` | boolean | false | | Deprecated: Use StartupScriptBehavior instead. | +| `»» logs_length` | integer | false | | | +| `»» logs_overflowed` | boolean | false | | | | `»» name` | string | false | | | | `»» operating_system` | string | false | | | | `»» ready_at` | string(date-time) | false | | | @@ -666,8 +668,6 @@ Status Code **200** | `»» shutdown_script` | string | false | | | | `»» shutdown_script_timeout_seconds` | integer | false | | | | `»» started_at` | string(date-time) | false | | | -| `»» startup_logs_length` | integer | false | | | -| `»» startup_logs_overflowed` | boolean | false | | | | `»» startup_script` | string | false | | | | `»» startup_script_behavior` | [codersdk.WorkspaceAgentStartupScriptBehavior](schemas.md#codersdkworkspaceagentstartupscriptbehavior) | false | | | | `»» startup_script_timeout_seconds` | integer | false | | »startup script timeout seconds is the number of seconds to wait for the startup script to complete. If the script does not complete within this time, the agent lifecycle will be marked as start_timeout. | @@ -828,6 +828,8 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -835,8 +837,6 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, @@ -995,6 +995,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -1002,8 +1004,6 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, @@ -1120,6 +1120,8 @@ Status Code **200** | `»»»»» preferred` | boolean | false | | | | `»»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | | | `»»» login_before_ready` | boolean | false | | Deprecated: Use StartupScriptBehavior instead. | +| `»»» logs_length` | integer | false | | | +| `»»» logs_overflowed` | boolean | false | | | | `»»» name` | string | false | | | | `»»» operating_system` | string | false | | | | `»»» ready_at` | string(date-time) | false | | | @@ -1127,8 +1129,6 @@ Status Code **200** | `»»» shutdown_script` | string | false | | | | `»»» shutdown_script_timeout_seconds` | integer | false | | | | `»»» started_at` | string(date-time) | false | | | -| `»»» startup_logs_length` | integer | false | | | -| `»»» startup_logs_overflowed` | boolean | false | | | | `»»» startup_script` | string | false | | | | `»»» startup_script_behavior` | [codersdk.WorkspaceAgentStartupScriptBehavior](schemas.md#codersdkworkspaceagentstartupscriptbehavior) | false | | | | `»»» startup_script_timeout_seconds` | integer | false | | »»startup script timeout seconds is the number of seconds to wait for the startup script to complete. If the script does not complete within this time, the agent lifecycle will be marked as start_timeout. | @@ -1343,6 +1343,8 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -1350,8 +1352,6 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 9bf6b0eea09b0..5b47c7c10ba88 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -157,6 +157,26 @@ | ---------------- | ------ | -------- | ------------ | ----------- | | `json_web_token` | string | true | | | +## agentsdk.Log + +```json +{ + "created_at": "string", + "level": "trace", + "output": "string", + "source": "startup_script" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------ | -------------------------------------------------------------------- | -------- | ------------ | ----------- | +| `created_at` | string | false | | | +| `level` | [codersdk.LogLevel](#codersdkloglevel) | false | | | +| `output` | string | false | | | +| `source` | [codersdk.WorkspaceAgentLogSource](#codersdkworkspaceagentlogsource) | false | | | + ## agentsdk.Manifest ```json @@ -277,7 +297,7 @@ | `startup_script_timeout` | integer | false | | | | `vscode_port_proxy_uri` | string | false | | | -## agentsdk.PatchStartupLogs +## agentsdk.PatchLogs ```json { @@ -285,7 +305,8 @@ { "created_at": "string", "level": "trace", - "output": "string" + "output": "string", + "source": "startup_script" } ] } @@ -293,9 +314,9 @@ ### Properties -| Name | Type | Required | Restrictions | Description | -| ------ | --------------------------------------------------- | -------- | ------------ | ----------- | -| `logs` | array of [agentsdk.StartupLog](#agentsdkstartuplog) | false | | | +| Name | Type | Required | Restrictions | Description | +| ------ | ------------------------------------- | -------- | ------------ | ----------- | +| `logs` | array of [agentsdk.Log](#agentsdklog) | false | | | ## agentsdk.PostAppHealthsRequest @@ -369,24 +390,6 @@ | `subsystem` | [codersdk.AgentSubsystem](#codersdkagentsubsystem) | false | | | | `version` | string | false | | | -## agentsdk.StartupLog - -```json -{ - "created_at": "string", - "level": "trace", - "output": "string" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------ | -------------------------------------- | -------- | ------------ | ----------- | -| `created_at` | string | false | | | -| `level` | [codersdk.LogLevel](#codersdkloglevel) | false | | | -| `output` | string | false | | | - ## agentsdk.Stats ```json @@ -5240,6 +5243,8 @@ If the schedule is empty, the user will be updated to use the default schedule.| }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -5247,8 +5252,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, @@ -5381,6 +5384,8 @@ If the schedule is empty, the user will be updated to use the default schedule.| }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -5388,8 +5393,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, @@ -5423,6 +5426,8 @@ If the schedule is empty, the user will be updated to use the default schedule.| | » `[any property]` | [codersdk.DERPRegion](#codersdkderpregion) | false | | | | `lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](#codersdkworkspaceagentlifecycle) | false | | | | `login_before_ready` | boolean | false | | Deprecated: Use StartupScriptBehavior instead. | +| `logs_length` | integer | false | | | +| `logs_overflowed` | boolean | false | | | | `name` | string | false | | | | `operating_system` | string | false | | | | `ready_at` | string | false | | | @@ -5430,8 +5435,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `shutdown_script` | string | false | | | | `shutdown_script_timeout_seconds` | integer | false | | | | `started_at` | string | false | | | -| `startup_logs_length` | integer | false | | | -| `startup_logs_overflowed` | boolean | false | | | | `startup_script` | string | false | | | | `startup_script_behavior` | [codersdk.WorkspaceAgentStartupScriptBehavior](#codersdkworkspaceagentstartupscriptbehavior) | false | | | | `startup_script_timeout_seconds` | integer | false | | Startup script timeout seconds is the number of seconds to wait for the startup script to complete. If the script does not complete within this time, the agent lifecycle will be marked as start_timeout. | @@ -5583,6 +5586,45 @@ If the schedule is empty, the user will be updated to use the default schedule.| | ------- | ------------------------------------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `ports` | array of [codersdk.WorkspaceAgentListeningPort](#codersdkworkspaceagentlisteningport) | false | | If there are no ports in the list, nothing should be displayed in the UI. There must not be a "no ports available" message or anything similar, as there will always be no ports displayed on platforms where our port detection logic is unsupported. | +## codersdk.WorkspaceAgentLog + +```json +{ + "created_at": "2019-08-24T14:15:22Z", + "id": 0, + "level": "trace", + "output": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------ | -------------------------------------- | -------- | ------------ | ----------- | +| `created_at` | string | false | | | +| `id` | integer | false | | | +| `level` | [codersdk.LogLevel](#codersdkloglevel) | false | | | +| `output` | string | false | | | + +## codersdk.WorkspaceAgentLogSource + +```json +"startup_script" +``` + +### Properties + +#### Enumerated Values + +| Value | +| ----------------- | +| `startup_script` | +| `shutdown_script` | +| `kubernetes` | +| `envbox` | +| `envbuilder` | +| `external` | + ## codersdk.WorkspaceAgentMetadataDescription ```json @@ -5605,26 +5647,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `script` | string | false | | | | `timeout` | integer | false | | | -## codersdk.WorkspaceAgentStartupLog - -```json -{ - "created_at": "2019-08-24T14:15:22Z", - "id": 0, - "level": "trace", - "output": "string" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------ | -------------------------------------- | -------- | ------------ | ----------- | -| `created_at` | string | false | | | -| `id` | integer | false | | | -| `level` | [codersdk.LogLevel](#codersdkloglevel) | false | | | -| `output` | string | false | | | - ## codersdk.WorkspaceAgentStartupScriptBehavior ```json @@ -5820,6 +5842,8 @@ If the schedule is empty, the user will be updated to use the default schedule.| }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -5827,8 +5851,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, @@ -6129,6 +6151,8 @@ If the schedule is empty, the user will be updated to use the default schedule.| }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -6136,8 +6160,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, @@ -6341,6 +6363,8 @@ If the schedule is empty, the user will be updated to use the default schedule.| }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -6348,8 +6372,6 @@ If the schedule is empty, the user will be updated to use the default schedule.| "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, diff --git a/docs/api/templates.md b/docs/api/templates.md index b77d2dcce62bf..f8a1612161f81 100644 --- a/docs/api/templates.md +++ b/docs/api/templates.md @@ -1620,6 +1620,8 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/d }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -1627,8 +1629,6 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/d "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, @@ -1709,6 +1709,8 @@ Status Code **200** | `»»»» preferred` | boolean | false | | | | `»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | | | `»» login_before_ready` | boolean | false | | Deprecated: Use StartupScriptBehavior instead. | +| `»» logs_length` | integer | false | | | +| `»» logs_overflowed` | boolean | false | | | | `»» name` | string | false | | | | `»» operating_system` | string | false | | | | `»» ready_at` | string(date-time) | false | | | @@ -1716,8 +1718,6 @@ Status Code **200** | `»» shutdown_script` | string | false | | | | `»» shutdown_script_timeout_seconds` | integer | false | | | | `»» started_at` | string(date-time) | false | | | -| `»» startup_logs_length` | integer | false | | | -| `»» startup_logs_overflowed` | boolean | false | | | | `»» startup_script` | string | false | | | | `»» startup_script_behavior` | [codersdk.WorkspaceAgentStartupScriptBehavior](schemas.md#codersdkworkspaceagentstartupscriptbehavior) | false | | | | `»» startup_script_timeout_seconds` | integer | false | | »startup script timeout seconds is the number of seconds to wait for the startup script to complete. If the script does not complete within this time, the agent lifecycle will be marked as start_timeout. | @@ -2012,6 +2012,8 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/r }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -2019,8 +2021,6 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/r "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, @@ -2101,6 +2101,8 @@ Status Code **200** | `»»»» preferred` | boolean | false | | | | `»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | | | `»» login_before_ready` | boolean | false | | Deprecated: Use StartupScriptBehavior instead. | +| `»» logs_length` | integer | false | | | +| `»» logs_overflowed` | boolean | false | | | | `»» name` | string | false | | | | `»» operating_system` | string | false | | | | `»» ready_at` | string(date-time) | false | | | @@ -2108,8 +2110,6 @@ Status Code **200** | `»» shutdown_script` | string | false | | | | `»» shutdown_script_timeout_seconds` | integer | false | | | | `»» started_at` | string(date-time) | false | | | -| `»» startup_logs_length` | integer | false | | | -| `»» startup_logs_overflowed` | boolean | false | | | | `»» startup_script` | string | false | | | | `»» startup_script_behavior` | [codersdk.WorkspaceAgentStartupScriptBehavior](schemas.md#codersdkworkspaceagentstartupscriptbehavior) | false | | | | `»» startup_script_timeout_seconds` | integer | false | | »startup script timeout seconds is the number of seconds to wait for the startup script to complete. If the script does not complete within this time, the agent lifecycle will be marked as start_timeout. | diff --git a/docs/api/workspaces.md b/docs/api/workspaces.md index 42f317616e55a..390a82448886c 100644 --- a/docs/api/workspaces.md +++ b/docs/api/workspaces.md @@ -135,6 +135,8 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -142,8 +144,6 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, @@ -323,6 +323,8 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -330,8 +332,6 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, @@ -510,6 +510,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \ }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -517,8 +519,6 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \ "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, @@ -699,6 +699,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \ }, "lifecycle_state": "created", "login_before_ready": true, + "logs_length": 0, + "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", @@ -706,8 +708,6 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \ "shutdown_script": "string", "shutdown_script_timeout_seconds": 0, "started_at": "2019-08-24T14:15:22Z", - "startup_logs_length": 0, - "startup_logs_overflowed": true, "startup_script": "string", "startup_script_behavior": "blocking", "startup_script_timeout_seconds": 0, diff --git a/site/src/api/api.ts b/site/src/api/api.ts index e39fb04c1ff84..a1ac0a16366e6 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -775,11 +775,11 @@ export const getWorkspaceBuildLogs = async ( return response.data } -export const getWorkspaceAgentStartupLogs = async ( +export const getWorkspaceAgentLogs = async ( agentID: string, -): Promise => { - const response = await axios.get( - `/api/v2/workspaceagents/${agentID}/startup-logs`, +): Promise => { + const response = await axios.get( + `/api/v2/workspaceagents/${agentID}/logs`, ) return response.data } @@ -1284,21 +1284,21 @@ export const watchBuildLogsByTemplateVersionId = ( return socket } -type WatchStartupLogsOptions = { +type WatchWorkspaceAgentLogsOptions = { after: number - onMessage: (logs: TypesGen.WorkspaceAgentStartupLog[]) => void + onMessage: (logs: TypesGen.WorkspaceAgentLog[]) => void onDone: () => void onError: (error: Error) => void } -export const watchStartupLogs = ( +export const watchWorkspaceAgentLogs = ( agentId: string, - { after, onMessage, onDone, onError }: WatchStartupLogsOptions, + { after, onMessage, onDone, onError }: WatchWorkspaceAgentLogsOptions, ) => { // WebSocket compression in Safari (confirmed in 16.5) is broken when // the server sends large messages. The following error is seen: // - // WebSocket connection to 'wss://.../startup-logs?follow&after=0' failed: The operation couldn’t be completed. Protocol error + // WebSocket connection to 'wss://.../logs?follow&after=0' failed: The operation couldn’t be completed. Protocol error // const noCompression = userAgentParser(navigator.userAgent).browser.name === "Safari" @@ -1307,11 +1307,11 @@ export const watchStartupLogs = ( const proto = location.protocol === "https:" ? "wss:" : "ws:" const socket = new WebSocket( - `${proto}//${location.host}/api/v2/workspaceagents/${agentId}/startup-logs?follow&after=${after}${noCompression}`, + `${proto}//${location.host}/api/v2/workspaceagents/${agentId}/logs?follow&after=${after}${noCompression}`, ) socket.binaryType = "blob" socket.addEventListener("message", (event) => { - const logs = JSON.parse(event.data) as TypesGen.WorkspaceAgentStartupLog[] + const logs = JSON.parse(event.data) as TypesGen.WorkspaceAgentLog[] onMessage(logs) }) socket.addEventListener("error", () => { diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index fcb71675e11e6..b26921e38dc56 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1302,8 +1302,8 @@ export interface WorkspaceAgent { readonly startup_script?: string readonly startup_script_behavior: WorkspaceAgentStartupScriptBehavior readonly startup_script_timeout_seconds: number - readonly startup_logs_length: number - readonly startup_logs_overflowed: boolean + readonly logs_length: number + readonly logs_overflowed: boolean readonly directory?: string readonly expanded_directory?: string readonly version: string @@ -1336,6 +1336,14 @@ export interface WorkspaceAgentListeningPortsResponse { readonly ports: WorkspaceAgentListeningPort[] } +// From codersdk/workspaceagents.go +export interface WorkspaceAgentLog { + readonly id: number + readonly created_at: string + readonly output: string + readonly level: LogLevel +} + // From codersdk/workspaceagents.go export interface WorkspaceAgentMetadata { readonly result: WorkspaceAgentMetadataResult @@ -1359,14 +1367,6 @@ export interface WorkspaceAgentMetadataResult { readonly error: string } -// From codersdk/workspaceagents.go -export interface WorkspaceAgentStartupLog { - readonly id: number - readonly created_at: string - readonly output: string - readonly level: LogLevel -} - // From codersdk/workspaceapps.go export interface WorkspaceApp { readonly id: string @@ -1820,6 +1820,23 @@ export const WorkspaceAgentLifecycles: WorkspaceAgentLifecycle[] = [ "starting", ] +// From codersdk/workspaceagents.go +export type WorkspaceAgentLogSource = + | "envbox" + | "envbuilder" + | "external" + | "kubernetes" + | "shutdown_script" + | "startup_script" +export const WorkspaceAgentLogSources: WorkspaceAgentLogSource[] = [ + "envbox", + "envbuilder", + "external", + "kubernetes", + "shutdown_script", + "startup_script", +] + // From codersdk/workspaceagents.go export type WorkspaceAgentStartupScriptBehavior = "blocking" | "non-blocking" export const WorkspaceAgentStartupScriptBehaviors: WorkspaceAgentStartupScriptBehavior[] = diff --git a/site/src/components/Resources/AgentRow.stories.tsx b/site/src/components/Resources/AgentRow.stories.tsx index 522fe37cb0b39..5e4adf5e1b4f0 100644 --- a/site/src/components/Resources/AgentRow.stories.tsx +++ b/site/src/components/Resources/AgentRow.stories.tsx @@ -215,7 +215,7 @@ Started.args = { ...Example.args, agent: { ...MockWorkspaceAgentReady, - startup_logs_length: 1, + logs_length: 1, }, } diff --git a/site/src/components/Resources/AgentRow.tsx b/site/src/components/Resources/AgentRow.tsx index a6a7e9fb57d86..f26c8b8fbdd5c 100644 --- a/site/src/components/Resources/AgentRow.tsx +++ b/site/src/components/Resources/AgentRow.tsx @@ -54,7 +54,7 @@ export interface AgentRowProps { hideVSCodeDesktopButton?: boolean serverVersion: string onUpdateAgent: () => void - storybookStartupLogs?: LineWithID[] + storybookLogs?: LineWithID[] storybookAgentMetadata?: WorkspaceAgentMetadata[] } @@ -66,7 +66,7 @@ export const AgentRow: FC = ({ hideVSCodeDesktopButton, serverVersion, onUpdateAgent, - storybookStartupLogs, + storybookLogs, storybookAgentMetadata, sshPrefix, }) => { @@ -75,10 +75,10 @@ export const AgentRow: FC = ({ context: { agentID: agent.id }, services: process.env.STORYBOOK ? { - getStartupLogs: async () => { - return storybookStartupLogs || [] + getLogs: async () => { + return storybookLogs || [] }, - streamStartupLogs: () => async () => { + streamLogs: () => async () => { // noop }, } @@ -93,39 +93,38 @@ export const AgentRow: FC = ({ ((agent.status === "connected" && hasAppsToDisplay) || agent.status === "connecting") const hasStartupFeatures = - Boolean(agent.startup_logs_length) || - Boolean(logsMachine.context.startupLogs?.length) + Boolean(agent.logs_length) || Boolean(logsMachine.context.logs?.length) const { proxy } = useProxy() - const [showStartupLogs, setShowStartupLogs] = useState( + const [showLogs, setShowLogs] = useState( ["starting", "start_timeout"].includes(agent.lifecycle_state) && hasStartupFeatures, ) useEffect(() => { - setShowStartupLogs(agent.lifecycle_state !== "ready" && hasStartupFeatures) + setShowLogs(agent.lifecycle_state !== "ready" && hasStartupFeatures) }, [agent.lifecycle_state, hasStartupFeatures]) // External applications can provide startup logs for an agent during it's spawn. // These could be Kubernetes logs, or other logs that are useful to the user. // For this reason, we want to fetch these logs when the agent is starting. useEffect(() => { if (agent.lifecycle_state === "starting") { - sendLogsEvent("FETCH_STARTUP_LOGS") + sendLogsEvent("FETCH_LOGS") } }, [sendLogsEvent, agent.lifecycle_state]) useEffect(() => { // We only want to fetch logs when they are actually shown, // otherwise we can make a lot of requests that aren't necessary. - if (showStartupLogs && logsMachine.can("FETCH_STARTUP_LOGS")) { - sendLogsEvent("FETCH_STARTUP_LOGS") + if (showLogs && logsMachine.can("FETCH_LOGS")) { + sendLogsEvent("FETCH_LOGS") } - }, [logsMachine, sendLogsEvent, showStartupLogs]) + }, [logsMachine, sendLogsEvent, showLogs]) const logListRef = useRef(null) const logListDivRef = useRef(null) const startupLogs = useMemo(() => { - const allLogs = logsMachine.context.startupLogs || [] + const allLogs = logsMachine.context.logs || [] const logs = [...allLogs] - if (agent.startup_logs_overflowed) { + if (agent.logs_overflowed) { logs.push({ id: -1, level: "error", @@ -134,7 +133,7 @@ export const AgentRow: FC = ({ }) } return logs - }, [logsMachine.context.startupLogs, agent.startup_logs_overflowed]) + }, [logsMachine.context.logs, agent.logs_overflowed]) const [bottomOfLogs, setBottomOfLogs] = useState(true) // This is a layout effect to remove flicker when we're scrolling to the bottom. useLayoutEffect(() => { @@ -142,7 +141,7 @@ export const AgentRow: FC = ({ if (bottomOfLogs && logListRef.current) { logListRef.current.scrollToItem(startupLogs.length - 1, "end") } - }, [showStartupLogs, startupLogs, logListRef, bottomOfLogs]) + }, [showLogs, startupLogs, logListRef, bottomOfLogs]) // This is a bit of a hack on the react-window API to get the scroll position. // If we're scrolled to the bottom, we want to keep the list scrolled to the bottom. @@ -284,7 +283,7 @@ export const AgentRow: FC = ({ {hasStartupFeatures && (
- + {({ width }) => ( = ({
- {showStartupLogs ? ( + {showLogs ? (