mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
1ba7139f21
1 of 9 [next >>](https://github.com/coder/coder/pull/24811) RFC: [Bridge ↔ Boundaries Correlation RFC](https://www.notion.so/Bridge-Boundaries-Correlation-313d579be59281f3b4efdbfd6896775a) Adds three new proto fields for boundary session correlation. **`ReportBoundaryLogsRequest`** - `session_id` (string, field 2) — UUID generated by boundary at startup, shared across all batches from a single run. - `confined_process` (string, field 3) — name of the confined process (e.g. `claude-code`, `codex`, `copilot`). **`BoundaryLog`** - `sequence_number` (uint64, field 4) — monotonically increasing counter per session, primary ordering key when boundary is in use. `BoundaryLog.time` already existed at field 2; no change needed there. API version bumped to v2.9. No behaviour change in coderd or the agent. This is a pure schema bump that the boundary repo will consume in its own stack. > Generated by Coder Agents
166 lines
5.9 KiB
Go
166 lines
5.9 KiB
Go
package taskstatus
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"github.com/google/uuid"
|
|
"golang.org/x/xerrors"
|
|
|
|
"cdr.dev/slog/v3"
|
|
agentproto "github.com/coder/coder/v2/agent/proto"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/coder/v2/codersdk/agentsdk"
|
|
"github.com/coder/quartz"
|
|
)
|
|
|
|
// client abstracts the details of using codersdk.Client for workspace operations.
|
|
// This interface allows for easier testing by enabling mock implementations and
|
|
// provides a cleaner separation of concerns.
|
|
//
|
|
// The interface is designed to be initialized in two phases:
|
|
// 1. Create the client with newClient(coderClient)
|
|
// 2. Configure logging when the io.Writer is available in Run()
|
|
type client interface {
|
|
// CreateUserWorkspace creates a workspace for a user.
|
|
CreateUserWorkspace(ctx context.Context, userID string, req codersdk.CreateWorkspaceRequest) (codersdk.Workspace, error)
|
|
|
|
// WorkspaceByOwnerAndName retrieves a workspace by owner and name.
|
|
WorkspaceByOwnerAndName(ctx context.Context, owner string, name string, params codersdk.WorkspaceOptions) (codersdk.Workspace, error)
|
|
|
|
// WorkspaceExternalAgentCredentials retrieves credentials for an external agent.
|
|
WorkspaceExternalAgentCredentials(ctx context.Context, workspaceID uuid.UUID, agentName string) (codersdk.ExternalAgentCredentials, error)
|
|
|
|
// watchWorkspace watches for updates to a workspace.
|
|
watchWorkspace(ctx context.Context, workspaceID uuid.UUID) (<-chan codersdk.Workspace, error)
|
|
|
|
// deleteWorkspace deletes the workspace by creating a build with delete transition.
|
|
deleteWorkspace(ctx context.Context, workspaceID uuid.UUID) error
|
|
|
|
// initialize sets up the client with the provided logger, which is only available after Run() is called.
|
|
initialize(logger slog.Logger)
|
|
}
|
|
|
|
// appStatusUpdater abstracts the details of updating app status via the
|
|
// Agent dRPC API. This interface is separate from client because it
|
|
// requires an agent token which is only available after creating an
|
|
// external workspace.
|
|
type appStatusUpdater interface {
|
|
// updateAppStatus sends a status update for a workspace app.
|
|
updateAppStatus(ctx context.Context, req *agentproto.UpdateAppStatusRequest) error
|
|
|
|
// initialize establishes the dRPC connection using the provided
|
|
// agent token. Must be called before updateAppStatus.
|
|
initialize(ctx context.Context, logger slog.Logger, agentToken string) error
|
|
|
|
// close cleanly shuts down the underlying dRPC connection.
|
|
close() error
|
|
}
|
|
|
|
// sdkClient is the concrete implementation of the client interface using
|
|
// codersdk.Client.
|
|
type sdkClient struct {
|
|
coderClient *codersdk.Client
|
|
clock quartz.Clock
|
|
logger slog.Logger
|
|
}
|
|
|
|
// newClient creates a new client implementation using the provided codersdk.Client.
|
|
func newClient(coderClient *codersdk.Client) client {
|
|
return &sdkClient{
|
|
coderClient: coderClient,
|
|
clock: quartz.NewReal(),
|
|
}
|
|
}
|
|
|
|
func (c *sdkClient) CreateUserWorkspace(ctx context.Context, userID string, req codersdk.CreateWorkspaceRequest) (codersdk.Workspace, error) {
|
|
return c.coderClient.CreateUserWorkspace(ctx, userID, req)
|
|
}
|
|
|
|
func (c *sdkClient) WorkspaceByOwnerAndName(ctx context.Context, owner string, name string, params codersdk.WorkspaceOptions) (codersdk.Workspace, error) {
|
|
return c.coderClient.WorkspaceByOwnerAndName(ctx, owner, name, params)
|
|
}
|
|
|
|
func (c *sdkClient) WorkspaceExternalAgentCredentials(ctx context.Context, workspaceID uuid.UUID, agentName string) (codersdk.ExternalAgentCredentials, error) {
|
|
return c.coderClient.WorkspaceExternalAgentCredentials(ctx, workspaceID, agentName)
|
|
}
|
|
|
|
func (c *sdkClient) watchWorkspace(ctx context.Context, workspaceID uuid.UUID) (<-chan codersdk.Workspace, error) {
|
|
return c.coderClient.WatchWorkspace(ctx, workspaceID)
|
|
}
|
|
|
|
func (c *sdkClient) deleteWorkspace(ctx context.Context, workspaceID uuid.UUID) error {
|
|
// Create a build with delete transition to delete the workspace
|
|
_, err := c.coderClient.CreateWorkspaceBuild(ctx, workspaceID, codersdk.CreateWorkspaceBuildRequest{
|
|
Transition: codersdk.WorkspaceTransitionDelete,
|
|
Reason: codersdk.CreateWorkspaceBuildReasonCLI,
|
|
})
|
|
if err != nil {
|
|
return xerrors.Errorf("create delete build: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *sdkClient) initialize(logger slog.Logger) {
|
|
// Configure the coder client logging
|
|
c.logger = logger
|
|
c.coderClient.SetLogger(logger)
|
|
c.coderClient.SetLogBodies(true)
|
|
}
|
|
|
|
// sdkAppStatusUpdater is the concrete implementation of the
|
|
// appStatusUpdater interface. It dials the Agent dRPC endpoint once
|
|
// during initialize and reuses the connection for all subsequent
|
|
// UpdateAppStatus calls.
|
|
type sdkAppStatusUpdater struct {
|
|
drpcClient agentproto.DRPCAgentClient28
|
|
url *url.URL
|
|
httpClient *http.Client
|
|
}
|
|
|
|
// newAppStatusUpdater creates a new appStatusUpdater implementation.
|
|
func newAppStatusUpdater(client *codersdk.Client) appStatusUpdater {
|
|
return &sdkAppStatusUpdater{
|
|
url: client.URL,
|
|
httpClient: client.HTTPClient,
|
|
}
|
|
}
|
|
|
|
func (u *sdkAppStatusUpdater) updateAppStatus(ctx context.Context, req *agentproto.UpdateAppStatusRequest) error {
|
|
if u.drpcClient == nil {
|
|
return xerrors.New("dRPC client not initialized - call initialize first")
|
|
}
|
|
_, err := u.drpcClient.UpdateAppStatus(ctx, req)
|
|
return err
|
|
}
|
|
|
|
func (u *sdkAppStatusUpdater) close() error {
|
|
if u.drpcClient == nil {
|
|
return nil
|
|
}
|
|
return u.drpcClient.DRPCConn().Close()
|
|
}
|
|
|
|
func (u *sdkAppStatusUpdater) initialize(ctx context.Context, logger slog.Logger, agentToken string) error {
|
|
agentClient := agentsdk.New(
|
|
u.url,
|
|
agentsdk.WithFixedToken(agentToken),
|
|
codersdk.WithHTTPClient(u.httpClient),
|
|
codersdk.WithLogger(logger),
|
|
codersdk.WithLogBodies(),
|
|
)
|
|
drpcClient, _, err := agentClient.ConnectRPC29WithRole(ctx, "")
|
|
if err != nil {
|
|
return xerrors.Errorf("connect to agent dRPC endpoint: %w", err)
|
|
}
|
|
u.drpcClient = drpcClient
|
|
return nil
|
|
}
|
|
|
|
// Ensure sdkClient implements the client interface.
|
|
var _ client = (*sdkClient)(nil)
|
|
|
|
// Ensure sdkAppStatusUpdater implements the appStatusUpdater interface.
|
|
var _ appStatusUpdater = (*sdkAppStatusUpdater)(nil)
|