refactor(coderd): pass the agent's build ID to the manifest API

The agent token middleware already resolves the agent's build, so the
manifest no longer re-reads the resource and build to list its secrets.
This commit is contained in:
Steven Masley
2026-10-15 19:21:06 +00:00
committed by Steven Masley
parent 79da70bd00
commit 3fb12d964c
4 changed files with 31 additions and 50 deletions
+4
View File
@@ -96,6 +96,9 @@ type Options struct {
WorkspaceID uuid.UUID
OrganizationID uuid.UUID
TemplateVersionID uuid.UUID
// WorkspaceBuildID is the build that created the agent, resolved by the
// agent token middleware.
WorkspaceBuildID uuid.UUID
AuthenticatedCtx context.Context
Log slog.Logger
@@ -156,6 +159,7 @@ func New(opts Options, workspace database.Workspace, agent database.WorkspaceAge
Database: opts.Database,
DerpMapFn: opts.DerpMapFn,
WorkspaceID: opts.WorkspaceID,
WorkspaceBuildID: opts.WorkspaceBuildID,
}
// Don't cache details for prebuilds, though the cached fields will eventually be updated
+6 -25
View File
@@ -32,6 +32,9 @@ type ManifestAPI struct {
DerpForceWebSockets bool
DisableUserSecretFilePath bool
WorkspaceID uuid.UUID
// WorkspaceBuildID is the build that created the agent. Its live secrets
// are delivered in the manifest.
WorkspaceBuildID uuid.UUID
AgentFn func(ctx context.Context) (database.WorkspaceAgent, error)
Database database.Store
@@ -98,9 +101,10 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest
if err != nil {
return nil, xerrors.Errorf("getting user secrets: %w", err)
}
workspaceSecrets, err := a.agentBuildSecrets(ctx, workspaceAgent)
// nolint:gocritic // Only the workspace secret manager reads values.
workspaceSecrets, err := a.Database.ListActiveWorkspaceSecrets(dbauthz.AsWorkspaceSecretManager(ctx), a.WorkspaceBuildID)
if err != nil {
return nil, err
return nil, xerrors.Errorf("getting workspace secrets: %w", err)
}
appSlug := appurl.ApplicationURL{
@@ -168,29 +172,6 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest
}, nil
}
// agentBuildSecrets returns the live workspace secrets linked to the build
// that created the agent. Only the latest build has live secrets, so an agent
// left over from an earlier build receives none.
func (a *ManifestAPI) agentBuildSecrets(ctx context.Context, agent database.WorkspaceAgent) ([]database.WorkspaceSecret, error) {
// nolint:gocritic // System context needed to resolve the agent's build;
// the agent is already authenticated.
sysCtx := dbauthz.AsSystemRestricted(ctx)
resource, err := a.Database.GetWorkspaceResourceByID(sysCtx, agent.ResourceID)
if err != nil {
return nil, xerrors.Errorf("getting workspace agent resource: %w", err)
}
build, err := a.Database.GetWorkspaceBuildByJobID(sysCtx, resource.JobID)
if err != nil {
return nil, xerrors.Errorf("getting workspace agent build: %w", err)
}
// nolint:gocritic // Only the workspace secret manager reads values.
secrets, err := a.Database.ListActiveWorkspaceSecrets(dbauthz.AsWorkspaceSecretManager(ctx), build.ID)
if err != nil {
return nil, xerrors.Errorf("getting workspace secrets: %w", err)
}
return secrets, nil
}
func vscodeProxyURI(app appurl.ApplicationURL, accessURL *url.URL, appHost string) string {
// Proxying by port only works for subdomains. If subdomain support is not
// available, return an empty string.
+20 -25
View File
@@ -56,7 +56,6 @@ func TestGetManifest(t *testing.T) {
agentBuild = database.WorkspaceBuild{
ID: uuid.New(),
WorkspaceID: workspace.ID,
JobID: uuid.New(),
}
agent = database.WorkspaceAgent{
ID: uuid.New(),
@@ -341,10 +340,11 @@ func TestGetManifest(t *testing.T) {
DisableDirectConnections: true,
DerpForceWebSockets: true,
AgentFn: func(ctx context.Context) (database.WorkspaceAgent, error) { return agent, nil },
WorkspaceID: workspace.ID,
Database: mDB,
DerpMapFn: derpMapFn,
AgentFn: func(ctx context.Context) (database.WorkspaceAgent, error) { return agent, nil },
WorkspaceID: workspace.ID,
WorkspaceBuildID: agentBuild.ID,
Database: mDB,
DerpMapFn: derpMapFn,
}
mDB.EXPECT().GetWorkspaceAppsByAgentID(gomock.Any(), agent.ID).Return(apps, nil)
@@ -356,8 +356,6 @@ func TestGetManifest(t *testing.T) {
mDB.EXPECT().GetWorkspaceAgentDevcontainersByAgentID(gomock.Any(), agent.ID).Return(devcontainers, nil)
mDB.EXPECT().GetWorkspaceByID(gomock.Any(), workspace.ID).Return(workspace, nil)
mDB.EXPECT().ListUserSecretsWithValues(gomock.Any(), workspace.OwnerID).Return(nil, nil)
mDB.EXPECT().GetWorkspaceResourceByID(gomock.Any(), gomock.Any()).Return(database.WorkspaceResource{JobID: agentBuild.JobID}, nil)
mDB.EXPECT().GetWorkspaceBuildByJobID(gomock.Any(), agentBuild.JobID).Return(agentBuild, nil)
mDB.EXPECT().ListActiveWorkspaceSecrets(gomock.Any(), agentBuild.ID).Return(nil, nil)
got, err := api.GetManifest(context.Background(), &agentproto.GetManifestRequest{})
@@ -411,10 +409,11 @@ func TestGetManifest(t *testing.T) {
DisableDirectConnections: true,
DerpForceWebSockets: true,
AgentFn: func(ctx context.Context) (database.WorkspaceAgent, error) { return childAgent, nil },
WorkspaceID: workspace.ID,
Database: mDB,
DerpMapFn: derpMapFn,
AgentFn: func(ctx context.Context) (database.WorkspaceAgent, error) { return childAgent, nil },
WorkspaceID: workspace.ID,
WorkspaceBuildID: agentBuild.ID,
Database: mDB,
DerpMapFn: derpMapFn,
}
mDB.EXPECT().GetWorkspaceAppsByAgentID(gomock.Any(), childAgent.ID).Return([]database.WorkspaceApp{}, nil)
@@ -426,8 +425,6 @@ func TestGetManifest(t *testing.T) {
mDB.EXPECT().GetWorkspaceAgentDevcontainersByAgentID(gomock.Any(), childAgent.ID).Return([]database.WorkspaceAgentDevcontainer{}, nil)
mDB.EXPECT().GetWorkspaceByID(gomock.Any(), workspace.ID).Return(workspace, nil)
mDB.EXPECT().ListUserSecretsWithValues(gomock.Any(), workspace.OwnerID).Return(nil, nil)
mDB.EXPECT().GetWorkspaceResourceByID(gomock.Any(), gomock.Any()).Return(database.WorkspaceResource{JobID: agentBuild.JobID}, nil)
mDB.EXPECT().GetWorkspaceBuildByJobID(gomock.Any(), agentBuild.JobID).Return(agentBuild, nil)
mDB.EXPECT().ListActiveWorkspaceSecrets(gomock.Any(), agentBuild.ID).Return(nil, nil)
got, err := api.GetManifest(context.Background(), &agentproto.GetManifestRequest{})
@@ -588,10 +585,11 @@ func TestGetManifest(t *testing.T) {
DisableDirectConnections: true,
DerpForceWebSockets: true,
AgentFn: func(ctx context.Context) (database.WorkspaceAgent, error) { return childAgent, nil },
WorkspaceID: workspace.ID,
Database: mDB,
DerpMapFn: derpMapFn,
AgentFn: func(ctx context.Context) (database.WorkspaceAgent, error) { return childAgent, nil },
WorkspaceID: workspace.ID,
WorkspaceBuildID: agentBuild.ID,
Database: mDB,
DerpMapFn: derpMapFn,
}
mDB.EXPECT().GetWorkspaceAppsByAgentID(gomock.Any(), childAgent.ID).Return([]database.WorkspaceApp{}, nil)
@@ -614,8 +612,6 @@ func TestGetManifest(t *testing.T) {
}, nil)
// Workspace secrets follow user secrets so a workspace secret that
// targets the same env var wins when the agent applies them in order.
mDB.EXPECT().GetWorkspaceResourceByID(gomock.Any(), gomock.Any()).Return(database.WorkspaceResource{JobID: agentBuild.JobID}, nil)
mDB.EXPECT().GetWorkspaceBuildByJobID(gomock.Any(), agentBuild.JobID).Return(agentBuild, nil)
mDB.EXPECT().ListActiveWorkspaceSecrets(gomock.Any(), agentBuild.ID).Return([]database.WorkspaceSecret{
{EnvName: "GITHUB_TOKEN", Value: sql.NullString{String: "ghp_workspace", Valid: true}},
}, nil)
@@ -718,10 +714,11 @@ func TestGetManifest(t *testing.T) {
DisableDirectConnections: true,
DerpForceWebSockets: true,
AgentFn: func(ctx context.Context) (database.WorkspaceAgent, error) { return agent, nil },
WorkspaceID: workspace.ID,
Database: mDB,
DerpMapFn: derpMapFn,
AgentFn: func(ctx context.Context) (database.WorkspaceAgent, error) { return agent, nil },
WorkspaceID: workspace.ID,
WorkspaceBuildID: agentBuild.ID,
Database: mDB,
DerpMapFn: derpMapFn,
}
mDB.EXPECT().GetWorkspaceAppsByAgentID(gomock.Any(), agent.ID).Return(apps, nil)
@@ -733,8 +730,6 @@ func TestGetManifest(t *testing.T) {
mDB.EXPECT().GetWorkspaceAgentDevcontainersByAgentID(gomock.Any(), agent.ID).Return(devcontainers, nil)
mDB.EXPECT().GetWorkspaceByID(gomock.Any(), workspace.ID).Return(workspace, nil)
mDB.EXPECT().ListUserSecretsWithValues(gomock.Any(), workspace.OwnerID).Return(nil, nil)
mDB.EXPECT().GetWorkspaceResourceByID(gomock.Any(), gomock.Any()).Return(database.WorkspaceResource{JobID: agentBuild.JobID}, nil)
mDB.EXPECT().GetWorkspaceBuildByJobID(gomock.Any(), agentBuild.JobID).Return(agentBuild, nil)
mDB.EXPECT().ListActiveWorkspaceSecrets(gomock.Any(), agentBuild.ID).Return(nil, nil)
got, err := api.GetManifest(context.Background(), &agentproto.GetManifestRequest{})
+1
View File
@@ -159,6 +159,7 @@ func (api *API) workspaceAgentRPC(rw http.ResponseWriter, r *http.Request) {
WorkspaceID: workspace.ID,
OrganizationID: workspace.OrganizationID,
TemplateVersionID: build.TemplateVersionID,
WorkspaceBuildID: build.ID,
AuthenticatedCtx: ctx,
Log: logger,