Skip to content

Commit 0547e45

Browse files
feat(agent): add ParentId to agent manifest
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.
1 parent 2cd3f99 commit 0547e45

File tree

4 files changed

+121
-27
lines changed

4 files changed

+121
-27
lines changed

agent/proto/agent.pb.go

Lines changed: 39 additions & 27 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

agent/proto/agent.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ message Manifest {
9090
string motd_path = 6;
9191
bool disable_direct_connections = 7;
9292
bool derp_force_websockets = 8;
93+
optional bytes parent_id = 18;
9394

9495
coder.tailnet.v2.DERPMap derp_map = 9;
9596
repeated WorkspaceAgentScript scripts = 10;

coderd/agentapi/manifest.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest
120120
return nil, xerrors.Errorf("converting workspace apps: %w", err)
121121
}
122122

123+
var parentID []byte
124+
if workspaceAgent.ParentID.Valid {
125+
parentID = workspaceAgent.ParentID.UUID[:]
126+
}
127+
123128
return &agentproto.Manifest{
124129
AgentId: workspaceAgent.ID[:],
125130
AgentName: workspaceAgent.Name,
@@ -133,6 +138,7 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest
133138
MotdPath: workspaceAgent.MOTDFile,
134139
DisableDirectConnections: a.DisableDirectConnections,
135140
DerpForceWebsockets: a.DerpForceWebSockets,
141+
ParentId: parentID,
136142

137143
DerpMap: tailnet.DERPMapToProto(a.DerpMapFn()),
138144
Scripts: dbAgentScriptsToProto(scripts),

coderd/agentapi/manifest_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ func TestGetManifest(t *testing.T) {
6060
Directory: "/cool/dir",
6161
MOTDFile: "/cool/motd",
6262
}
63+
childAgent = database.WorkspaceAgent{
64+
ID: uuid.New(),
65+
Name: "cool-child-agent",
66+
ParentID: uuid.NullUUID{Valid: true, UUID: agent.ID},
67+
Directory: "/workspace/dir",
68+
MOTDFile: "/workspace/motd",
69+
}
6370
apps = []database.WorkspaceApp{
6471
{
6572
ID: uuid.New(),
@@ -364,6 +371,74 @@ func TestGetManifest(t *testing.T) {
364371
require.Equal(t, expected, got)
365372
})
366373

374+
t.Run("OK/Child", func(t *testing.T) {
375+
t.Parallel()
376+
377+
mDB := dbmock.NewMockStore(gomock.NewController(t))
378+
379+
api := &agentapi.ManifestAPI{
380+
AccessURL: &url.URL{Scheme: "https", Host: "example.com"},
381+
AppHostname: "*--apps.example.com",
382+
ExternalAuthConfigs: []*externalauth.Config{
383+
{Type: string(codersdk.EnhancedExternalAuthProviderGitHub)},
384+
{Type: "some-provider"},
385+
{Type: string(codersdk.EnhancedExternalAuthProviderGitLab)},
386+
},
387+
DisableDirectConnections: true,
388+
DerpForceWebSockets: true,
389+
390+
AgentFn: func(ctx context.Context) (database.WorkspaceAgent, error) {
391+
return childAgent, nil
392+
},
393+
WorkspaceID: workspace.ID,
394+
Database: mDB,
395+
DerpMapFn: derpMapFn,
396+
}
397+
398+
mDB.EXPECT().GetWorkspaceAppsByAgentID(gomock.Any(), childAgent.ID).Return([]database.WorkspaceApp{}, nil)
399+
mDB.EXPECT().GetWorkspaceAgentScriptsByAgentIDs(gomock.Any(), []uuid.UUID{childAgent.ID}).Return([]database.WorkspaceAgentScript{}, nil)
400+
mDB.EXPECT().GetWorkspaceAgentMetadata(gomock.Any(), database.GetWorkspaceAgentMetadataParams{
401+
WorkspaceAgentID: childAgent.ID,
402+
Keys: nil, // all
403+
}).Return([]database.WorkspaceAgentMetadatum{}, nil)
404+
mDB.EXPECT().GetWorkspaceAgentDevcontainersByAgentID(gomock.Any(), childAgent.ID).Return([]database.WorkspaceAgentDevcontainer{}, nil)
405+
mDB.EXPECT().GetWorkspaceByID(gomock.Any(), workspace.ID).Return(workspace, nil)
406+
mDB.EXPECT().GetUserByID(gomock.Any(), workspace.OwnerID).Return(owner, nil)
407+
408+
got, err := api.GetManifest(context.Background(), &agentproto.GetManifestRequest{})
409+
require.NoError(t, err)
410+
411+
expected := &agentproto.Manifest{
412+
AgentId: childAgent.ID[:],
413+
AgentName: childAgent.Name,
414+
ParentId: agent.ID[:],
415+
OwnerUsername: owner.Username,
416+
WorkspaceId: workspace.ID[:],
417+
WorkspaceName: workspace.Name,
418+
GitAuthConfigs: 2, // two "enhanced" external auth configs
419+
EnvironmentVariables: nil,
420+
Directory: childAgent.Directory,
421+
VsCodePortProxyUri: fmt.Sprintf("https://{{port}}--%s--%s--%s--apps.example.com", childAgent.Name, workspace.Name, owner.Username),
422+
MotdPath: childAgent.MOTDFile,
423+
DisableDirectConnections: true,
424+
DerpForceWebsockets: true,
425+
// tailnet.DERPMapToProto() is extensively tested elsewhere, so it's
426+
// not necessary to manually recreate a big DERP map here like we
427+
// did for apps and metadata.
428+
DerpMap: tailnet.DERPMapToProto(derpMapFn()),
429+
Scripts: []*agentproto.WorkspaceAgentScript{},
430+
Apps: []*agentproto.WorkspaceApp{},
431+
Metadata: []*agentproto.WorkspaceAgentMetadata_Description{},
432+
Devcontainers: []*agentproto.WorkspaceAgentDevcontainer{},
433+
}
434+
435+
// Log got and expected with spew.
436+
// t.Log("got:\n" + spew.Sdump(got))
437+
// t.Log("expected:\n" + spew.Sdump(expected))
438+
439+
require.Equal(t, expected, got)
440+
})
441+
367442
t.Run("NoAppHostname", func(t *testing.T) {
368443
t.Parallel()
369444

0 commit comments

Comments
 (0)