feat(agent): add ParentId to agent manifest (#17888)

Closes https://github.com/coder/internal/issues/648

This change introduces a new `ParentId` field to the agent's manifest.
This will allow an agent to know if it is a child or not, as well as
knowing who the owner is.

This is part of the Dev Container Agents work
This commit is contained in:
Danielle Maywood
2025-05-19 16:09:56 +01:00
committed by GitHub
parent f044cc3550
commit 61f22a59ba
11 changed files with 152 additions and 35 deletions
+6
View File
@@ -120,6 +120,11 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest
return nil, xerrors.Errorf("converting workspace apps: %w", err)
}
var parentID []byte
if workspaceAgent.ParentID.Valid {
parentID = workspaceAgent.ParentID.UUID[:]
}
return &agentproto.Manifest{
AgentId: workspaceAgent.ID[:],
AgentName: workspaceAgent.Name,
@@ -133,6 +138,7 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest
MotdPath: workspaceAgent.MOTDFile,
DisableDirectConnections: a.DisableDirectConnections,
DerpForceWebsockets: a.DerpForceWebSockets,
ParentId: parentID,
DerpMap: tailnet.DERPMapToProto(a.DerpMapFn()),
Scripts: dbAgentScriptsToProto(scripts),
+72
View File
@@ -60,6 +60,13 @@ func TestGetManifest(t *testing.T) {
Directory: "/cool/dir",
MOTDFile: "/cool/motd",
}
childAgent = database.WorkspaceAgent{
ID: uuid.New(),
Name: "cool-child-agent",
ParentID: uuid.NullUUID{Valid: true, UUID: agent.ID},
Directory: "/workspace/dir",
MOTDFile: "/workspace/motd",
}
apps = []database.WorkspaceApp{
{
ID: uuid.New(),
@@ -337,6 +344,7 @@ func TestGetManifest(t *testing.T) {
expected := &agentproto.Manifest{
AgentId: agent.ID[:],
AgentName: agent.Name,
ParentId: nil,
OwnerUsername: owner.Username,
WorkspaceId: workspace.ID[:],
WorkspaceName: workspace.Name,
@@ -364,6 +372,70 @@ func TestGetManifest(t *testing.T) {
require.Equal(t, expected, got)
})
t.Run("OK/Child", func(t *testing.T) {
t.Parallel()
mDB := dbmock.NewMockStore(gomock.NewController(t))
api := &agentapi.ManifestAPI{
AccessURL: &url.URL{Scheme: "https", Host: "example.com"},
AppHostname: "*--apps.example.com",
ExternalAuthConfigs: []*externalauth.Config{
{Type: string(codersdk.EnhancedExternalAuthProviderGitHub)},
{Type: "some-provider"},
{Type: string(codersdk.EnhancedExternalAuthProviderGitLab)},
},
DisableDirectConnections: true,
DerpForceWebSockets: true,
AgentFn: func(ctx context.Context) (database.WorkspaceAgent, error) {
return childAgent, nil
},
WorkspaceID: workspace.ID,
Database: mDB,
DerpMapFn: derpMapFn,
}
mDB.EXPECT().GetWorkspaceAppsByAgentID(gomock.Any(), childAgent.ID).Return([]database.WorkspaceApp{}, nil)
mDB.EXPECT().GetWorkspaceAgentScriptsByAgentIDs(gomock.Any(), []uuid.UUID{childAgent.ID}).Return([]database.WorkspaceAgentScript{}, nil)
mDB.EXPECT().GetWorkspaceAgentMetadata(gomock.Any(), database.GetWorkspaceAgentMetadataParams{
WorkspaceAgentID: childAgent.ID,
Keys: nil, // all
}).Return([]database.WorkspaceAgentMetadatum{}, nil)
mDB.EXPECT().GetWorkspaceAgentDevcontainersByAgentID(gomock.Any(), childAgent.ID).Return([]database.WorkspaceAgentDevcontainer{}, nil)
mDB.EXPECT().GetWorkspaceByID(gomock.Any(), workspace.ID).Return(workspace, nil)
mDB.EXPECT().GetUserByID(gomock.Any(), workspace.OwnerID).Return(owner, nil)
got, err := api.GetManifest(context.Background(), &agentproto.GetManifestRequest{})
require.NoError(t, err)
expected := &agentproto.Manifest{
AgentId: childAgent.ID[:],
AgentName: childAgent.Name,
ParentId: agent.ID[:],
OwnerUsername: owner.Username,
WorkspaceId: workspace.ID[:],
WorkspaceName: workspace.Name,
GitAuthConfigs: 2, // two "enhanced" external auth configs
EnvironmentVariables: nil,
Directory: childAgent.Directory,
VsCodePortProxyUri: fmt.Sprintf("https://{{port}}--%s--%s--%s--apps.example.com", childAgent.Name, workspace.Name, owner.Username),
MotdPath: childAgent.MOTDFile,
DisableDirectConnections: true,
DerpForceWebsockets: true,
// tailnet.DERPMapToProto() is extensively tested elsewhere, so it's
// not necessary to manually recreate a big DERP map here like we
// did for apps and metadata.
DerpMap: tailnet.DERPMapToProto(derpMapFn()),
Scripts: []*agentproto.WorkspaceAgentScript{},
Apps: []*agentproto.WorkspaceApp{},
Metadata: []*agentproto.WorkspaceAgentMetadata_Description{},
Devcontainers: []*agentproto.WorkspaceAgentDevcontainer{},
}
require.Equal(t, expected, got)
})
t.Run("NoAppHostname", func(t *testing.T) {
t.Parallel()