feat: allow specifying devcontainer on agent in terraform (#16997)

This change allows specifying devcontainers in terraform and plumbs it
through to the agent via agent manifest.

This will be used for autostarting devcontainers in a workspace.

Depends on coder/terraform-provider-coder#368
Updates #16423
This commit is contained in:
Mathias Fredriksson
2025-03-20 19:09:39 +02:00
committed by GitHub
parent 287e3198d8
commit 69ba27e347
49 changed files with 2615 additions and 1253 deletions
+31 -9
View File
@@ -3,6 +3,7 @@ package agentapi
import (
"context"
"database/sql"
"errors"
"net/url"
"strings"
"time"
@@ -42,11 +43,12 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest
return nil, err
}
var (
dbApps []database.WorkspaceApp
scripts []database.WorkspaceAgentScript
metadata []database.WorkspaceAgentMetadatum
workspace database.Workspace
owner database.User
dbApps []database.WorkspaceApp
scripts []database.WorkspaceAgentScript
metadata []database.WorkspaceAgentMetadatum
workspace database.Workspace
owner database.User
devcontainers []database.WorkspaceAgentDevcontainer
)
var eg errgroup.Group
@@ -80,6 +82,13 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest
}
return err
})
eg.Go(func() (err error) {
devcontainers, err = a.Database.GetWorkspaceAgentDevcontainersByAgentID(ctx, workspaceAgent.ID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return err
}
return nil
})
err = eg.Wait()
if err != nil {
return nil, xerrors.Errorf("fetching workspace agent data: %w", err)
@@ -125,10 +134,11 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest
DisableDirectConnections: a.DisableDirectConnections,
DerpForceWebsockets: a.DerpForceWebSockets,
DerpMap: tailnet.DERPMapToProto(a.DerpMapFn()),
Scripts: dbAgentScriptsToProto(scripts),
Apps: apps,
Metadata: dbAgentMetadataToProtoDescription(metadata),
DerpMap: tailnet.DERPMapToProto(a.DerpMapFn()),
Scripts: dbAgentScriptsToProto(scripts),
Apps: apps,
Metadata: dbAgentMetadataToProtoDescription(metadata),
Devcontainers: dbAgentDevcontainersToProto(devcontainers),
}, nil
}
@@ -228,3 +238,15 @@ func dbAppToProto(dbApp database.WorkspaceApp, agent database.WorkspaceAgent, ow
Hidden: dbApp.Hidden,
}, nil
}
func dbAgentDevcontainersToProto(devcontainers []database.WorkspaceAgentDevcontainer) []*agentproto.WorkspaceAgentDevcontainer {
ret := make([]*agentproto.WorkspaceAgentDevcontainer, len(devcontainers))
for i, dc := range devcontainers {
ret[i] = &agentproto.WorkspaceAgentDevcontainer{
Id: dc.ID[:],
WorkspaceFolder: dc.WorkspaceFolder,
ConfigPath: dc.ConfigPath,
}
}
return ret
}
+36 -8
View File
@@ -156,6 +156,19 @@ func TestGetManifest(t *testing.T) {
CollectedAt: someTime.Add(time.Hour),
},
}
devcontainers = []database.WorkspaceAgentDevcontainer{
{
ID: uuid.New(),
WorkspaceAgentID: agent.ID,
WorkspaceFolder: "/cool/folder",
},
{
ID: uuid.New(),
WorkspaceAgentID: agent.ID,
WorkspaceFolder: "/another/cool/folder",
ConfigPath: "/another/cool/folder/.devcontainer/devcontainer.json",
},
}
derpMapFn = func() *tailcfg.DERPMap {
return &tailcfg.DERPMap{
Regions: map[int]*tailcfg.DERPRegion{
@@ -267,6 +280,17 @@ func TestGetManifest(t *testing.T) {
Timeout: durationpb.New(time.Duration(metadata[1].Timeout)),
},
}
protoDevcontainers = []*agentproto.WorkspaceAgentDevcontainer{
{
Id: devcontainers[0].ID[:],
WorkspaceFolder: devcontainers[0].WorkspaceFolder,
},
{
Id: devcontainers[1].ID[:],
WorkspaceFolder: devcontainers[1].WorkspaceFolder,
ConfigPath: devcontainers[1].ConfigPath,
},
}
)
t.Run("OK", func(t *testing.T) {
@@ -299,6 +323,7 @@ func TestGetManifest(t *testing.T) {
WorkspaceAgentID: agent.ID,
Keys: nil, // all
}).Return(metadata, nil)
mDB.EXPECT().GetWorkspaceAgentDevcontainersByAgentID(gomock.Any(), agent.ID).Return(devcontainers, nil)
mDB.EXPECT().GetWorkspaceByID(gomock.Any(), workspace.ID).Return(workspace, nil)
mDB.EXPECT().GetUserByID(gomock.Any(), workspace.OwnerID).Return(owner, nil)
@@ -321,10 +346,11 @@ func TestGetManifest(t *testing.T) {
// 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: protoScripts,
Apps: protoApps,
Metadata: protoMetadata,
DerpMap: tailnet.DERPMapToProto(derpMapFn()),
Scripts: protoScripts,
Apps: protoApps,
Metadata: protoMetadata,
Devcontainers: protoDevcontainers,
}
// Log got and expected with spew.
@@ -364,6 +390,7 @@ func TestGetManifest(t *testing.T) {
WorkspaceAgentID: agent.ID,
Keys: nil, // all
}).Return(metadata, nil)
mDB.EXPECT().GetWorkspaceAgentDevcontainersByAgentID(gomock.Any(), agent.ID).Return(devcontainers, nil)
mDB.EXPECT().GetWorkspaceByID(gomock.Any(), workspace.ID).Return(workspace, nil)
mDB.EXPECT().GetUserByID(gomock.Any(), workspace.OwnerID).Return(owner, nil)
@@ -386,10 +413,11 @@ func TestGetManifest(t *testing.T) {
// 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: protoScripts,
Apps: protoApps,
Metadata: protoMetadata,
DerpMap: tailnet.DERPMapToProto(derpMapFn()),
Scripts: protoScripts,
Apps: protoApps,
Metadata: protoMetadata,
Devcontainers: protoDevcontainers,
}
// Log got and expected with spew.