Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 691495d

Browse files
authored
feat: add expanded_directory to the agent for extension support (#6087)
This will enable opening the default `dir` of an agent in the VS Code extension!
1 parent f6effdb commit 691495d

25 files changed

+220
-59
lines changed

agent/agent.go

+36-6
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ type Client interface {
7575
ReportStats(ctx context.Context, log slog.Logger, stats func() *agentsdk.Stats) (io.Closer, error)
7676
PostLifecycle(ctx context.Context, state agentsdk.PostLifecycleRequest) error
7777
PostAppHealth(ctx context.Context, req agentsdk.PostAppHealthsRequest) error
78-
PostVersion(ctx context.Context, version string) error
78+
PostStartup(ctx context.Context, req agentsdk.PostStartupRequest) error
7979
}
8080

8181
func New(options Options) io.Closer {
@@ -236,16 +236,29 @@ func (a *agent) run(ctx context.Context) error {
236236
}
237237
a.sessionToken.Store(&sessionToken)
238238

239-
err = a.client.PostVersion(ctx, buildinfo.Version())
240-
if err != nil {
241-
return xerrors.Errorf("update workspace agent version: %w", err)
242-
}
243-
244239
metadata, err := a.client.Metadata(ctx)
245240
if err != nil {
246241
return xerrors.Errorf("fetch metadata: %w", err)
247242
}
248243
a.logger.Info(ctx, "fetched metadata")
244+
245+
// Expand the directory and send it back to coderd so external
246+
// applications that rely on the directory can use it.
247+
//
248+
// An example is VS Code Remote, which must know the directory
249+
// before initializing a connection.
250+
metadata.Directory, err = expandDirectory(metadata.Directory)
251+
if err != nil {
252+
return xerrors.Errorf("expand directory: %w", err)
253+
}
254+
err = a.client.PostStartup(ctx, agentsdk.PostStartupRequest{
255+
Version: buildinfo.Version(),
256+
ExpandedDirectory: metadata.Directory,
257+
})
258+
if err != nil {
259+
return xerrors.Errorf("update workspace agent version: %w", err)
260+
}
261+
249262
oldMetadata := a.metadata.Swap(metadata)
250263

251264
// The startup script should only execute on the first run!
@@ -1318,3 +1331,20 @@ func userHomeDir() (string, error) {
13181331
}
13191332
return u.HomeDir, nil
13201333
}
1334+
1335+
// expandDirectory converts a directory path to an absolute path.
1336+
// It primarily resolves the home directory and any environment
1337+
// variables that may be set
1338+
func expandDirectory(dir string) (string, error) {
1339+
if dir == "" {
1340+
return "", nil
1341+
}
1342+
if dir[0] == '~' {
1343+
home, err := userHomeDir()
1344+
if err != nil {
1345+
return "", err
1346+
}
1347+
dir = filepath.Join(home, dir[1:])
1348+
}
1349+
return os.ExpandEnv(dir), nil
1350+
}

agent/agent_test.go

+61-1
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,56 @@ func TestAgent_Lifecycle(t *testing.T) {
787787
})
788788
}
789789

790+
func TestAgent_Startup(t *testing.T) {
791+
t.Parallel()
792+
793+
t.Run("EmptyDirectory", func(t *testing.T) {
794+
t.Parallel()
795+
796+
_, client, _, _ := setupAgent(t, agentsdk.Metadata{
797+
StartupScript: "true",
798+
StartupScriptTimeout: 30 * time.Second,
799+
Directory: "",
800+
}, 0)
801+
assert.Eventually(t, func() bool {
802+
return client.getStartup().Version != ""
803+
}, testutil.WaitShort, testutil.IntervalFast)
804+
require.Equal(t, "", client.getStartup().ExpandedDirectory)
805+
})
806+
807+
t.Run("HomeDirectory", func(t *testing.T) {
808+
t.Parallel()
809+
810+
_, client, _, _ := setupAgent(t, agentsdk.Metadata{
811+
StartupScript: "true",
812+
StartupScriptTimeout: 30 * time.Second,
813+
Directory: "~",
814+
}, 0)
815+
assert.Eventually(t, func() bool {
816+
return client.getStartup().Version != ""
817+
}, testutil.WaitShort, testutil.IntervalFast)
818+
homeDir, err := os.UserHomeDir()
819+
require.NoError(t, err)
820+
require.Equal(t, homeDir, client.getStartup().ExpandedDirectory)
821+
})
822+
823+
t.Run("HomeEnvironmentVariable", func(t *testing.T) {
824+
t.Parallel()
825+
826+
_, client, _, _ := setupAgent(t, agentsdk.Metadata{
827+
StartupScript: "true",
828+
StartupScriptTimeout: 30 * time.Second,
829+
Directory: "$HOME",
830+
}, 0)
831+
assert.Eventually(t, func() bool {
832+
return client.getStartup().Version != ""
833+
}, testutil.WaitShort, testutil.IntervalFast)
834+
homeDir, err := os.UserHomeDir()
835+
require.NoError(t, err)
836+
require.Equal(t, homeDir, client.getStartup().ExpandedDirectory)
837+
})
838+
}
839+
790840
func TestAgent_ReconnectingPTY(t *testing.T) {
791841
t.Parallel()
792842
if runtime.GOOS == "windows" {
@@ -1178,6 +1228,7 @@ type client struct {
11781228

11791229
mu sync.Mutex // Protects following.
11801230
lifecycleStates []codersdk.WorkspaceAgentLifecycle
1231+
startup agentsdk.PostStartupRequest
11811232
}
11821233

11831234
func (c *client) Metadata(_ context.Context) (agentsdk.Metadata, error) {
@@ -1250,7 +1301,16 @@ func (*client) PostAppHealth(_ context.Context, _ agentsdk.PostAppHealthsRequest
12501301
return nil
12511302
}
12521303

1253-
func (*client) PostVersion(_ context.Context, _ string) error {
1304+
func (c *client) getStartup() agentsdk.PostStartupRequest {
1305+
c.mu.Lock()
1306+
defer c.mu.Unlock()
1307+
return c.startup
1308+
}
1309+
1310+
func (c *client) PostStartup(_ context.Context, startup agentsdk.PostStartupRequest) error {
1311+
c.mu.Lock()
1312+
defer c.mu.Unlock()
1313+
c.startup = startup
12541314
return nil
12551315
}
12561316

coderd/apidoc/docs.go

+12-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+12-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderd.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -553,7 +553,7 @@ func New(options *Options) *API {
553553
r.Route("/me", func(r chi.Router) {
554554
r.Use(httpmw.ExtractWorkspaceAgent(options.Database))
555555
r.Get("/metadata", api.workspaceAgentMetadata)
556-
r.Post("/version", api.postWorkspaceAgentVersion)
556+
r.Post("/startup", api.postWorkspaceAgentStartup)
557557
r.Post("/app-health", api.postWorkspaceAppHealth)
558558
r.Get("/gitauth", api.workspaceAgentsGitAuth)
559559
r.Get("/gitsshkey", api.agentGitSSHKey)

coderd/coderdtest/authorize.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) {
7373
"GET:/api/v2/workspaceagents/me/gitsshkey": {NoAuthorize: true},
7474
"GET:/api/v2/workspaceagents/me/metadata": {NoAuthorize: true},
7575
"GET:/api/v2/workspaceagents/me/coordinate": {NoAuthorize: true},
76-
"POST:/api/v2/workspaceagents/me/version": {NoAuthorize: true},
76+
"POST:/api/v2/workspaceagents/me/startup": {NoAuthorize: true},
7777
"POST:/api/v2/workspaceagents/me/app-health": {NoAuthorize: true},
7878
"POST:/api/v2/workspaceagents/me/report-stats": {NoAuthorize: true},
7979
"POST:/api/v2/workspaceagents/me/report-lifecycle": {NoAuthorize: true},

coderd/coderdtest/swaggerparser.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ func assertProduce(t *testing.T, comment SwaggerComment) {
344344
assert.Contains(t, allowedProduceTypes, comment.produce, "@Produce value is limited to specific types: %s", strings.Join(allowedProduceTypes, ","))
345345
} else {
346346
if (comment.router == "/workspaceagents/me/app-health" && comment.method == "post") ||
347-
(comment.router == "/workspaceagents/me/version" && comment.method == "post") ||
347+
(comment.router == "/workspaceagents/me/startup" && comment.method == "post") ||
348348
(comment.router == "/licenses/{id}" && comment.method == "delete") ||
349349
(comment.router == "/debug/coordinator" && comment.method == "get") {
350350
return // Exception: HTTP 200 is returned without response entity

coderd/database/dbfake/databasefake.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -3192,7 +3192,7 @@ func (q *fakeQuerier) UpdateWorkspaceAgentConnectionByID(_ context.Context, arg
31923192
return sql.ErrNoRows
31933193
}
31943194

3195-
func (q *fakeQuerier) UpdateWorkspaceAgentVersionByID(_ context.Context, arg database.UpdateWorkspaceAgentVersionByIDParams) error {
3195+
func (q *fakeQuerier) UpdateWorkspaceAgentStartupByID(_ context.Context, arg database.UpdateWorkspaceAgentStartupByIDParams) error {
31963196
if err := validateDatabaseType(arg); err != nil {
31973197
return err
31983198
}
@@ -3206,6 +3206,7 @@ func (q *fakeQuerier) UpdateWorkspaceAgentVersionByID(_ context.Context, arg dat
32063206
}
32073207

32083208
agent.Version = arg.Version
3209+
agent.ExpandedDirectory = arg.ExpandedDirectory
32093210
q.workspaceAgents[index] = agent
32103211
return nil
32113212
}

coderd/database/dump.sql

+4-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
BEGIN;
2+
3+
ALTER TABLE ONLY workspace_agents
4+
DROP COLUMN IF EXISTS expanded_directory;
5+
6+
COMMIT;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
BEGIN;
2+
3+
ALTER TABLE ONLY workspace_agents
4+
ADD COLUMN IF NOT EXISTS expanded_directory varchar(4096) DEFAULT '' NOT NULL;
5+
6+
COMMENT ON COLUMN workspace_agents.expanded_directory
7+
IS 'The resolved path of a user-specified directory. e.g. ~/coder -> /home/coder/coder';
8+
9+
COMMIT;

coderd/database/models.go

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/querier.go

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)