Files
coder/codersdk/connectionlog.go
T
dylanhuff-at-coder 76ae64391a refactor(codersdk): use ReadBodyAsJSON in typed endpoints (#27857)
This PR migrates 224 typed JSON response sites across 46 files to
`codersdk.ReadBodyAsJSON`, so invalid 2xx bodies return structured
errors while preserving URL credential redaction.

It intentionally excludes agent-direct HTTP, Azure IMDS, `UseNumber`,
and chat paths; stacked on coder/coder#27804, with chat and lint
follow-ups in coder/coder#27858 and coder/coder#27859. Refs
coder/coder#27044.

Reviewed and updated by Coder Agents on behalf of @dylanhuff-at-coder.
2026-08-05 13:22:23 -07:00

133 lines
4.5 KiB
Go

package codersdk
import (
"context"
"net/http"
"net/netip"
"strings"
"time"
"github.com/google/uuid"
)
type ConnectionLog struct {
ID uuid.UUID `json:"id" format:"uuid"`
ConnectTime time.Time `json:"connect_time" format:"date-time"`
Organization MinimalOrganization `json:"organization"`
WorkspaceOwnerID uuid.UUID `json:"workspace_owner_id" format:"uuid"`
WorkspaceOwnerUsername string `json:"workspace_owner_username"`
WorkspaceID uuid.UUID `json:"workspace_id" format:"uuid"`
WorkspaceName string `json:"workspace_name"`
AgentName string `json:"agent_name"`
IP *netip.Addr `json:"ip,omitempty"`
Type ConnectionType `json:"type"`
// WebInfo is only set when `type` is one of:
// - `ConnectionTypePortForwarding`
// - `ConnectionTypeWorkspaceApp`
// - `ConnectionTypeTunnel`
WebInfo *ConnectionLogWebInfo `json:"web_info,omitempty"`
// SSHInfo is only set when `type` is one of:
// - `ConnectionTypeSSH`
// - `ConnectionTypeReconnectingPTY`
// - `ConnectionTypeVSCode`
// - `ConnectionTypeJetBrains`
SSHInfo *ConnectionLogSSHInfo `json:"ssh_info,omitempty"`
}
// ConnectionType is the type of connection that the agent is receiving.
type ConnectionType string
const (
ConnectionTypeSSH ConnectionType = "ssh"
ConnectionTypeVSCode ConnectionType = "vscode"
ConnectionTypeJetBrains ConnectionType = "jetbrains"
ConnectionTypeReconnectingPTY ConnectionType = "reconnecting_pty"
ConnectionTypeWorkspaceApp ConnectionType = "workspace_app"
ConnectionTypePortForwarding ConnectionType = "port_forwarding"
// ConnectionTypeTunnel is recorded by coderd when a client
// establishes a tailnet tunnel to a workspace agent, and carries
// the authenticated user's identity. Tunnels via the user-scoped
// tailnet API (e.g. Coder Desktop) are not currently recorded.
ConnectionTypeTunnel ConnectionType = "tunnel"
)
// ConnectionLogStatus is the status of a connection log entry.
// It's the argument to the `status` filter when fetching connection logs.
type ConnectionLogStatus string
const (
ConnectionLogStatusOngoing ConnectionLogStatus = "ongoing"
ConnectionLogStatusCompleted ConnectionLogStatus = "completed"
)
func (s ConnectionLogStatus) Valid() bool {
switch s {
case ConnectionLogStatusOngoing, ConnectionLogStatusCompleted:
return true
default:
return false
}
}
type ConnectionLogWebInfo struct {
UserAgent string `json:"user_agent"`
// User is omitted if the connection event was from an unauthenticated user.
User *User `json:"user"`
SlugOrPort string `json:"slug_or_port"`
// StatusCode is the HTTP status code of the request.
StatusCode int32 `json:"status_code"`
}
type ConnectionLogSSHInfo struct {
ConnectionID uuid.UUID `json:"connection_id" format:"uuid"`
// DisconnectTime is omitted if a disconnect event with the same connection ID
// has not yet been seen.
DisconnectTime *time.Time `json:"disconnect_time,omitempty" format:"date-time"`
// DisconnectReason is omitted if a disconnect event with the same connection ID
// has not yet been seen.
DisconnectReason string `json:"disconnect_reason,omitempty"`
// ExitCode is the exit code of the SSH session. It is omitted if a
// disconnect event with the same connection ID has not yet been seen.
ExitCode *int32 `json:"exit_code,omitempty"`
}
type ConnectionLogsRequest struct {
SearchQuery string `json:"q,omitempty"`
Pagination
}
type ConnectionLogResponse struct {
ConnectionLogs []ConnectionLog `json:"connection_logs"`
Count int64 `json:"count"`
CountCap int64 `json:"count_cap"`
}
func (c *Client) ConnectionLogs(ctx context.Context, req ConnectionLogsRequest) (ConnectionLogResponse, error) {
res, err := c.Request(ctx, http.MethodGet, "/api/v2/connectionlog", nil, req.Pagination.asRequestOption(), func(r *http.Request) {
q := r.URL.Query()
var params []string
if req.SearchQuery != "" {
params = append(params, req.SearchQuery)
}
q.Set("q", strings.Join(params, " "))
r.URL.RawQuery = q.Encode()
})
if err != nil {
return ConnectionLogResponse{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return ConnectionLogResponse{}, ReadBodyAsError(res)
}
var logRes ConnectionLogResponse
err = ReadBodyAsJSON(res, &logRes)
if err != nil {
return ConnectionLogResponse{}, err
}
return logRes, nil
}