Skip to content

Commit 69ba27e

Browse files
authored
feat: allow specifying devcontainer on agent in terraform (coder#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 coder#16423
1 parent 287e319 commit 69ba27e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+2614
-1252
lines changed

agent/proto/agent.pb.go

Lines changed: 814 additions & 716 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: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@ message Manifest {
9595
repeated WorkspaceAgentScript scripts = 10;
9696
repeated WorkspaceApp apps = 11;
9797
repeated WorkspaceAgentMetadata.Description metadata = 12;
98+
repeated WorkspaceAgentDevcontainer devcontainers = 17;
99+
}
100+
101+
message WorkspaceAgentDevcontainer {
102+
bytes id = 1;
103+
string workspace_folder = 2;
104+
string config_path = 3;
98105
}
99106

100107
message GetManifestRequest {}

cli/testdata/coder_provisioner_list_--output_json.golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"last_seen_at": "====[timestamp]=====",
88
"name": "test",
99
"version": "v0.0.0-devel",
10-
"api_version": "1.3",
10+
"api_version": "1.4",
1111
"provisioners": [
1212
"echo"
1313
],

coderd/agentapi/manifest.go

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package agentapi
33
import (
44
"context"
55
"database/sql"
6+
"errors"
67
"net/url"
78
"strings"
89
"time"
@@ -42,11 +43,12 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest
4243
return nil, err
4344
}
4445
var (
45-
dbApps []database.WorkspaceApp
46-
scripts []database.WorkspaceAgentScript
47-
metadata []database.WorkspaceAgentMetadatum
48-
workspace database.Workspace
49-
owner database.User
46+
dbApps []database.WorkspaceApp
47+
scripts []database.WorkspaceAgentScript
48+
metadata []database.WorkspaceAgentMetadatum
49+
workspace database.Workspace
50+
owner database.User
51+
devcontainers []database.WorkspaceAgentDevcontainer
5052
)
5153

5254
var eg errgroup.Group
@@ -80,6 +82,13 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest
8082
}
8183
return err
8284
})
85+
eg.Go(func() (err error) {
86+
devcontainers, err = a.Database.GetWorkspaceAgentDevcontainersByAgentID(ctx, workspaceAgent.ID)
87+
if err != nil && !errors.Is(err, sql.ErrNoRows) {
88+
return err
89+
}
90+
return nil
91+
})
8392
err = eg.Wait()
8493
if err != nil {
8594
return nil, xerrors.Errorf("fetching workspace agent data: %w", err)
@@ -125,10 +134,11 @@ func (a *ManifestAPI) GetManifest(ctx context.Context, _ *agentproto.GetManifest
125134
DisableDirectConnections: a.DisableDirectConnections,
126135
DerpForceWebsockets: a.DerpForceWebSockets,
127136

128-
DerpMap: tailnet.DERPMapToProto(a.DerpMapFn()),
129-
Scripts: dbAgentScriptsToProto(scripts),
130-
Apps: apps,
131-
Metadata: dbAgentMetadataToProtoDescription(metadata),
137+
DerpMap: tailnet.DERPMapToProto(a.DerpMapFn()),
138+
Scripts: dbAgentScriptsToProto(scripts),
139+
Apps: apps,
140+
Metadata: dbAgentMetadataToProtoDescription(metadata),
141+
Devcontainers: dbAgentDevcontainersToProto(devcontainers),
132142
}, nil
133143
}
134144

@@ -228,3 +238,15 @@ func dbAppToProto(dbApp database.WorkspaceApp, agent database.WorkspaceAgent, ow
228238
Hidden: dbApp.Hidden,
229239
}, nil
230240
}
241+
242+
func dbAgentDevcontainersToProto(devcontainers []database.WorkspaceAgentDevcontainer) []*agentproto.WorkspaceAgentDevcontainer {
243+
ret := make([]*agentproto.WorkspaceAgentDevcontainer, len(devcontainers))
244+
for i, dc := range devcontainers {
245+
ret[i] = &agentproto.WorkspaceAgentDevcontainer{
246+
Id: dc.ID[:],
247+
WorkspaceFolder: dc.WorkspaceFolder,
248+
ConfigPath: dc.ConfigPath,
249+
}
250+
}
251+
return ret
252+
}

coderd/agentapi/manifest_test.go

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,19 @@ func TestGetManifest(t *testing.T) {
156156
CollectedAt: someTime.Add(time.Hour),
157157
},
158158
}
159+
devcontainers = []database.WorkspaceAgentDevcontainer{
160+
{
161+
ID: uuid.New(),
162+
WorkspaceAgentID: agent.ID,
163+
WorkspaceFolder: "/cool/folder",
164+
},
165+
{
166+
ID: uuid.New(),
167+
WorkspaceAgentID: agent.ID,
168+
WorkspaceFolder: "/another/cool/folder",
169+
ConfigPath: "/another/cool/folder/.devcontainer/devcontainer.json",
170+
},
171+
}
159172
derpMapFn = func() *tailcfg.DERPMap {
160173
return &tailcfg.DERPMap{
161174
Regions: map[int]*tailcfg.DERPRegion{
@@ -267,6 +280,17 @@ func TestGetManifest(t *testing.T) {
267280
Timeout: durationpb.New(time.Duration(metadata[1].Timeout)),
268281
},
269282
}
283+
protoDevcontainers = []*agentproto.WorkspaceAgentDevcontainer{
284+
{
285+
Id: devcontainers[0].ID[:],
286+
WorkspaceFolder: devcontainers[0].WorkspaceFolder,
287+
},
288+
{
289+
Id: devcontainers[1].ID[:],
290+
WorkspaceFolder: devcontainers[1].WorkspaceFolder,
291+
ConfigPath: devcontainers[1].ConfigPath,
292+
},
293+
}
270294
)
271295

272296
t.Run("OK", func(t *testing.T) {
@@ -299,6 +323,7 @@ func TestGetManifest(t *testing.T) {
299323
WorkspaceAgentID: agent.ID,
300324
Keys: nil, // all
301325
}).Return(metadata, nil)
326+
mDB.EXPECT().GetWorkspaceAgentDevcontainersByAgentID(gomock.Any(), agent.ID).Return(devcontainers, nil)
302327
mDB.EXPECT().GetWorkspaceByID(gomock.Any(), workspace.ID).Return(workspace, nil)
303328
mDB.EXPECT().GetUserByID(gomock.Any(), workspace.OwnerID).Return(owner, nil)
304329

@@ -321,10 +346,11 @@ func TestGetManifest(t *testing.T) {
321346
// tailnet.DERPMapToProto() is extensively tested elsewhere, so it's
322347
// not necessary to manually recreate a big DERP map here like we
323348
// did for apps and metadata.
324-
DerpMap: tailnet.DERPMapToProto(derpMapFn()),
325-
Scripts: protoScripts,
326-
Apps: protoApps,
327-
Metadata: protoMetadata,
349+
DerpMap: tailnet.DERPMapToProto(derpMapFn()),
350+
Scripts: protoScripts,
351+
Apps: protoApps,
352+
Metadata: protoMetadata,
353+
Devcontainers: protoDevcontainers,
328354
}
329355

330356
// Log got and expected with spew.
@@ -364,6 +390,7 @@ func TestGetManifest(t *testing.T) {
364390
WorkspaceAgentID: agent.ID,
365391
Keys: nil, // all
366392
}).Return(metadata, nil)
393+
mDB.EXPECT().GetWorkspaceAgentDevcontainersByAgentID(gomock.Any(), agent.ID).Return(devcontainers, nil)
367394
mDB.EXPECT().GetWorkspaceByID(gomock.Any(), workspace.ID).Return(workspace, nil)
368395
mDB.EXPECT().GetUserByID(gomock.Any(), workspace.OwnerID).Return(owner, nil)
369396

@@ -386,10 +413,11 @@ func TestGetManifest(t *testing.T) {
386413
// tailnet.DERPMapToProto() is extensively tested elsewhere, so it's
387414
// not necessary to manually recreate a big DERP map here like we
388415
// did for apps and metadata.
389-
DerpMap: tailnet.DERPMapToProto(derpMapFn()),
390-
Scripts: protoScripts,
391-
Apps: protoApps,
392-
Metadata: protoMetadata,
416+
DerpMap: tailnet.DERPMapToProto(derpMapFn()),
417+
Scripts: protoScripts,
418+
Apps: protoApps,
419+
Metadata: protoMetadata,
420+
Devcontainers: protoDevcontainers,
393421
}
394422

395423
// Log got and expected with spew.

coderd/apidoc/docs.go

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

coderd/apidoc/swagger.json

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

coderd/database/dbauthz/dbauthz.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ var (
186186
rbac.ResourceNotificationMessage.Type: {policy.ActionCreate, policy.ActionRead},
187187
// Provisionerd creates workspaces resources monitor
188188
rbac.ResourceWorkspaceAgentResourceMonitor.Type: {policy.ActionCreate},
189+
rbac.ResourceWorkspaceAgentDevcontainers.Type: {policy.ActionCreate},
189190
}),
190191
Org: map[string][]rbac.Permission{},
191192
User: []rbac.Permission{},
@@ -2660,6 +2661,14 @@ func (q *querier) GetWorkspaceAgentByInstanceID(ctx context.Context, authInstanc
26602661
return agent, nil
26612662
}
26622663

2664+
func (q *querier) GetWorkspaceAgentDevcontainersByAgentID(ctx context.Context, workspaceAgentID uuid.UUID) ([]database.WorkspaceAgentDevcontainer, error) {
2665+
_, err := q.GetWorkspaceAgentByID(ctx, workspaceAgentID)
2666+
if err != nil {
2667+
return nil, err
2668+
}
2669+
return q.db.GetWorkspaceAgentDevcontainersByAgentID(ctx, workspaceAgentID)
2670+
}
2671+
26632672
func (q *querier) GetWorkspaceAgentLifecycleStateByID(ctx context.Context, id uuid.UUID) (database.GetWorkspaceAgentLifecycleStateByIDRow, error) {
26642673
_, err := q.GetWorkspaceAgentByID(ctx, id)
26652674
if err != nil {
@@ -3390,6 +3399,13 @@ func (q *querier) InsertWorkspaceAgent(ctx context.Context, arg database.InsertW
33903399
return q.db.InsertWorkspaceAgent(ctx, arg)
33913400
}
33923401

3402+
func (q *querier) InsertWorkspaceAgentDevcontainers(ctx context.Context, arg database.InsertWorkspaceAgentDevcontainersParams) ([]database.WorkspaceAgentDevcontainer, error) {
3403+
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceWorkspaceAgentDevcontainers); err != nil {
3404+
return nil, err
3405+
}
3406+
return q.db.InsertWorkspaceAgentDevcontainers(ctx, arg)
3407+
}
3408+
33933409
func (q *querier) InsertWorkspaceAgentLogSources(ctx context.Context, arg database.InsertWorkspaceAgentLogSourcesParams) ([]database.WorkspaceAgentLogSource, error) {
33943410
// TODO: This is used by the agent, should we have an rbac check here?
33953411
return q.db.InsertWorkspaceAgentLogSources(ctx, arg)

coderd/database/dbauthz/dbauthz_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3074,6 +3074,36 @@ func (s *MethodTestSuite) TestWorkspace() {
30743074
})
30753075
check.Args(w.ID).Asserts(w, policy.ActionUpdate).Returns()
30763076
}))
3077+
s.Run("GetWorkspaceAgentDevcontainersByAgentID", s.Subtest(func(db database.Store, check *expects) {
3078+
u := dbgen.User(s.T(), db, database.User{})
3079+
o := dbgen.Organization(s.T(), db, database.Organization{})
3080+
tpl := dbgen.Template(s.T(), db, database.Template{
3081+
OrganizationID: o.ID,
3082+
CreatedBy: u.ID,
3083+
})
3084+
tv := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
3085+
TemplateID: uuid.NullUUID{UUID: tpl.ID, Valid: true},
3086+
OrganizationID: o.ID,
3087+
CreatedBy: u.ID,
3088+
})
3089+
w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{
3090+
TemplateID: tpl.ID,
3091+
OrganizationID: o.ID,
3092+
OwnerID: u.ID,
3093+
})
3094+
j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{
3095+
Type: database.ProvisionerJobTypeWorkspaceBuild,
3096+
})
3097+
b := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{
3098+
JobID: j.ID,
3099+
WorkspaceID: w.ID,
3100+
TemplateVersionID: tv.ID,
3101+
})
3102+
res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: b.JobID})
3103+
agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID})
3104+
d := dbgen.WorkspaceAgentDevcontainer(s.T(), db, database.WorkspaceAgentDevcontainer{WorkspaceAgentID: agt.ID})
3105+
check.Args(agt.ID).Asserts(w, policy.ActionRead).Returns([]database.WorkspaceAgentDevcontainer{d})
3106+
}))
30773107
}
30783108

30793109
func (s *MethodTestSuite) TestWorkspacePortSharing() {
@@ -5021,3 +5051,45 @@ func (s *MethodTestSuite) TestResourcesMonitor() {
50215051
check.Args(agt.ID).Asserts(w, policy.ActionRead).Returns(monitors)
50225052
}))
50235053
}
5054+
5055+
func (s *MethodTestSuite) TestResourcesProvisionerdserver() {
5056+
createAgent := func(t *testing.T, db database.Store) (database.WorkspaceAgent, database.WorkspaceTable) {
5057+
t.Helper()
5058+
5059+
u := dbgen.User(t, db, database.User{})
5060+
o := dbgen.Organization(t, db, database.Organization{})
5061+
tpl := dbgen.Template(t, db, database.Template{
5062+
OrganizationID: o.ID,
5063+
CreatedBy: u.ID,
5064+
})
5065+
tv := dbgen.TemplateVersion(t, db, database.TemplateVersion{
5066+
TemplateID: uuid.NullUUID{UUID: tpl.ID, Valid: true},
5067+
OrganizationID: o.ID,
5068+
CreatedBy: u.ID,
5069+
})
5070+
w := dbgen.Workspace(t, db, database.WorkspaceTable{
5071+
TemplateID: tpl.ID,
5072+
OrganizationID: o.ID,
5073+
OwnerID: u.ID,
5074+
})
5075+
j := dbgen.ProvisionerJob(t, db, nil, database.ProvisionerJob{
5076+
Type: database.ProvisionerJobTypeWorkspaceBuild,
5077+
})
5078+
b := dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
5079+
JobID: j.ID,
5080+
WorkspaceID: w.ID,
5081+
TemplateVersionID: tv.ID,
5082+
})
5083+
res := dbgen.WorkspaceResource(t, db, database.WorkspaceResource{JobID: b.JobID})
5084+
agt := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{ResourceID: res.ID})
5085+
5086+
return agt, w
5087+
}
5088+
5089+
s.Run("InsertWorkspaceAgentDevcontainers", s.Subtest(func(db database.Store, check *expects) {
5090+
agt, _ := createAgent(s.T(), db)
5091+
check.Args(database.InsertWorkspaceAgentDevcontainersParams{
5092+
WorkspaceAgentID: agt.ID,
5093+
}).Asserts(rbac.ResourceWorkspaceAgentDevcontainers, policy.ActionCreate)
5094+
}))
5095+
}

coderd/database/dbgen/dbgen.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,18 @@ func WorkspaceAgentScriptTiming(t testing.TB, db database.Store, orig database.W
255255
panic("failed to insert workspace agent script timing")
256256
}
257257

258+
func WorkspaceAgentDevcontainer(t testing.TB, db database.Store, orig database.WorkspaceAgentDevcontainer) database.WorkspaceAgentDevcontainer {
259+
devcontainers, err := db.InsertWorkspaceAgentDevcontainers(genCtx, database.InsertWorkspaceAgentDevcontainersParams{
260+
WorkspaceAgentID: takeFirst(orig.WorkspaceAgentID, uuid.New()),
261+
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
262+
ID: []uuid.UUID{takeFirst(orig.ID, uuid.New())},
263+
WorkspaceFolder: []string{takeFirst(orig.WorkspaceFolder, "/workspace")},
264+
ConfigPath: []string{takeFirst(orig.ConfigPath, "")},
265+
})
266+
require.NoError(t, err, "insert workspace agent devcontainer")
267+
return devcontainers[0]
268+
}
269+
258270
func Workspace(t testing.TB, db database.Store, orig database.WorkspaceTable) database.WorkspaceTable {
259271
t.Helper()
260272

0 commit comments

Comments
 (0)