Skip to content

Commit 69ba27e

Browse files
authored
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
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy