From 691495d76122f860e9b29d6f790f4767a12ead59 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Tue, 7 Feb 2023 15:35:09 -0600 Subject: [PATCH] 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! --- agent/agent.go | 42 +++++++++++-- agent/agent_test.go | 62 ++++++++++++++++++- coderd/apidoc/docs.go | 18 ++++-- coderd/apidoc/swagger.json | 18 ++++-- coderd/coderd.go | 2 +- coderd/coderdtest/authorize.go | 2 +- coderd/coderdtest/swaggerparser.go | 2 +- coderd/database/dbfake/databasefake.go | 3 +- coderd/database/dump.sql | 5 +- .../000096_agent_resolved_directory.down.sql | 6 ++ .../000096_agent_resolved_directory.up.sql | 9 +++ coderd/database/models.go | 2 + coderd/database/querier.go | 2 +- coderd/database/queries.sql.go | 34 ++++++---- coderd/database/queries/workspaceagents.sql | 5 +- coderd/workspaceagents.go | 20 +++--- coderd/wsconncache/wsconncache_test.go | 2 +- codersdk/agentsdk/agentsdk.go | 10 +-- codersdk/workspaceagents.go | 1 + docs/api/agents.md | 1 + docs/api/builds.md | 8 +++ docs/api/schemas.md | 16 +++-- docs/api/templates.md | 4 ++ docs/api/workspaces.md | 4 ++ site/src/api/typesGenerated.ts | 1 + 25 files changed, 220 insertions(+), 59 deletions(-) create mode 100644 coderd/database/migrations/000096_agent_resolved_directory.down.sql create mode 100644 coderd/database/migrations/000096_agent_resolved_directory.up.sql diff --git a/agent/agent.go b/agent/agent.go index bb2a769b01..d230577b3b 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -75,7 +75,7 @@ type Client interface { ReportStats(ctx context.Context, log slog.Logger, stats func() *agentsdk.Stats) (io.Closer, error) PostLifecycle(ctx context.Context, state agentsdk.PostLifecycleRequest) error PostAppHealth(ctx context.Context, req agentsdk.PostAppHealthsRequest) error - PostVersion(ctx context.Context, version string) error + PostStartup(ctx context.Context, req agentsdk.PostStartupRequest) error } func New(options Options) io.Closer { @@ -236,16 +236,29 @@ func (a *agent) run(ctx context.Context) error { } a.sessionToken.Store(&sessionToken) - err = a.client.PostVersion(ctx, buildinfo.Version()) - if err != nil { - return xerrors.Errorf("update workspace agent version: %w", err) - } - metadata, err := a.client.Metadata(ctx) if err != nil { return xerrors.Errorf("fetch metadata: %w", err) } a.logger.Info(ctx, "fetched metadata") + + // Expand the directory and send it back to coderd so external + // applications that rely on the directory can use it. + // + // An example is VS Code Remote, which must know the directory + // before initializing a connection. + metadata.Directory, err = expandDirectory(metadata.Directory) + if err != nil { + return xerrors.Errorf("expand directory: %w", err) + } + err = a.client.PostStartup(ctx, agentsdk.PostStartupRequest{ + Version: buildinfo.Version(), + ExpandedDirectory: metadata.Directory, + }) + if err != nil { + return xerrors.Errorf("update workspace agent version: %w", err) + } + oldMetadata := a.metadata.Swap(metadata) // The startup script should only execute on the first run! @@ -1318,3 +1331,20 @@ func userHomeDir() (string, error) { } return u.HomeDir, nil } + +// expandDirectory converts a directory path to an absolute path. +// It primarily resolves the home directory and any environment +// variables that may be set +func expandDirectory(dir string) (string, error) { + if dir == "" { + return "", nil + } + if dir[0] == '~' { + home, err := userHomeDir() + if err != nil { + return "", err + } + dir = filepath.Join(home, dir[1:]) + } + return os.ExpandEnv(dir), nil +} diff --git a/agent/agent_test.go b/agent/agent_test.go index 22290ba63b..d91039b38b 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -787,6 +787,56 @@ func TestAgent_Lifecycle(t *testing.T) { }) } +func TestAgent_Startup(t *testing.T) { + t.Parallel() + + t.Run("EmptyDirectory", func(t *testing.T) { + t.Parallel() + + _, client, _, _ := setupAgent(t, agentsdk.Metadata{ + StartupScript: "true", + StartupScriptTimeout: 30 * time.Second, + Directory: "", + }, 0) + assert.Eventually(t, func() bool { + return client.getStartup().Version != "" + }, testutil.WaitShort, testutil.IntervalFast) + require.Equal(t, "", client.getStartup().ExpandedDirectory) + }) + + t.Run("HomeDirectory", func(t *testing.T) { + t.Parallel() + + _, client, _, _ := setupAgent(t, agentsdk.Metadata{ + StartupScript: "true", + StartupScriptTimeout: 30 * time.Second, + Directory: "~", + }, 0) + assert.Eventually(t, func() bool { + return client.getStartup().Version != "" + }, testutil.WaitShort, testutil.IntervalFast) + homeDir, err := os.UserHomeDir() + require.NoError(t, err) + require.Equal(t, homeDir, client.getStartup().ExpandedDirectory) + }) + + t.Run("HomeEnvironmentVariable", func(t *testing.T) { + t.Parallel() + + _, client, _, _ := setupAgent(t, agentsdk.Metadata{ + StartupScript: "true", + StartupScriptTimeout: 30 * time.Second, + Directory: "$HOME", + }, 0) + assert.Eventually(t, func() bool { + return client.getStartup().Version != "" + }, testutil.WaitShort, testutil.IntervalFast) + homeDir, err := os.UserHomeDir() + require.NoError(t, err) + require.Equal(t, homeDir, client.getStartup().ExpandedDirectory) + }) +} + func TestAgent_ReconnectingPTY(t *testing.T) { t.Parallel() if runtime.GOOS == "windows" { @@ -1178,6 +1228,7 @@ type client struct { mu sync.Mutex // Protects following. lifecycleStates []codersdk.WorkspaceAgentLifecycle + startup agentsdk.PostStartupRequest } func (c *client) Metadata(_ context.Context) (agentsdk.Metadata, error) { @@ -1250,7 +1301,16 @@ func (*client) PostAppHealth(_ context.Context, _ agentsdk.PostAppHealthsRequest return nil } -func (*client) PostVersion(_ context.Context, _ string) error { +func (c *client) getStartup() agentsdk.PostStartupRequest { + c.mu.Lock() + defer c.mu.Unlock() + return c.startup +} + +func (c *client) PostStartup(_ context.Context, startup agentsdk.PostStartupRequest) error { + c.mu.Lock() + defer c.mu.Unlock() + c.startup = startup return nil } diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 46b1fa7946..c80c48eaff 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -4104,7 +4104,7 @@ const docTemplate = `{ } } }, - "/workspaceagents/me/version": { + "/workspaceagents/me/startup": { "post": { "security": [ { @@ -4120,16 +4120,16 @@ const docTemplate = `{ "tags": [ "Agents" ], - "summary": "Submit workspace agent version", - "operationId": "submit-workspace-agent-version", + "summary": "Submit workspace agent startup", + "operationId": "submit-workspace-agent-startup", "parameters": [ { - "description": "Version request", + "description": "Startup request", "name": "request", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/agentsdk.PostVersionRequest" + "$ref": "#/definitions/agentsdk.PostStartupRequest" } } ], @@ -5099,9 +5099,12 @@ const docTemplate = `{ } } }, - "agentsdk.PostVersionRequest": { + "agentsdk.PostStartupRequest": { "type": "object", "properties": { + "expanded_directory": { + "type": "string" + }, "version": { "type": "string" } @@ -7945,6 +7948,9 @@ const docTemplate = `{ "type": "string" } }, + "expanded_directory": { + "type": "string" + }, "first_connected_at": { "type": "string", "format": "date-time" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index e1762712b0..ce1f9616f0 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -3606,7 +3606,7 @@ } } }, - "/workspaceagents/me/version": { + "/workspaceagents/me/startup": { "post": { "security": [ { @@ -3616,16 +3616,16 @@ "consumes": ["application/json"], "produces": ["application/json"], "tags": ["Agents"], - "summary": "Submit workspace agent version", - "operationId": "submit-workspace-agent-version", + "summary": "Submit workspace agent startup", + "operationId": "submit-workspace-agent-startup", "parameters": [ { - "description": "Version request", + "description": "Startup request", "name": "request", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/agentsdk.PostVersionRequest" + "$ref": "#/definitions/agentsdk.PostStartupRequest" } } ], @@ -4502,9 +4502,12 @@ } } }, - "agentsdk.PostVersionRequest": { + "agentsdk.PostStartupRequest": { "type": "object", "properties": { + "expanded_directory": { + "type": "string" + }, "version": { "type": "string" } @@ -7150,6 +7153,9 @@ "type": "string" } }, + "expanded_directory": { + "type": "string" + }, "first_connected_at": { "type": "string", "format": "date-time" diff --git a/coderd/coderd.go b/coderd/coderd.go index 4eb4d19ac5..bfce5a5fb1 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -553,7 +553,7 @@ func New(options *Options) *API { r.Route("/me", func(r chi.Router) { r.Use(httpmw.ExtractWorkspaceAgent(options.Database)) r.Get("/metadata", api.workspaceAgentMetadata) - r.Post("/version", api.postWorkspaceAgentVersion) + r.Post("/startup", api.postWorkspaceAgentStartup) r.Post("/app-health", api.postWorkspaceAppHealth) r.Get("/gitauth", api.workspaceAgentsGitAuth) r.Get("/gitsshkey", api.agentGitSSHKey) diff --git a/coderd/coderdtest/authorize.go b/coderd/coderdtest/authorize.go index 42abafc68d..3fcbb63cb6 100644 --- a/coderd/coderdtest/authorize.go +++ b/coderd/coderdtest/authorize.go @@ -73,7 +73,7 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) { "GET:/api/v2/workspaceagents/me/gitsshkey": {NoAuthorize: true}, "GET:/api/v2/workspaceagents/me/metadata": {NoAuthorize: true}, "GET:/api/v2/workspaceagents/me/coordinate": {NoAuthorize: true}, - "POST:/api/v2/workspaceagents/me/version": {NoAuthorize: true}, + "POST:/api/v2/workspaceagents/me/startup": {NoAuthorize: true}, "POST:/api/v2/workspaceagents/me/app-health": {NoAuthorize: true}, "POST:/api/v2/workspaceagents/me/report-stats": {NoAuthorize: true}, "POST:/api/v2/workspaceagents/me/report-lifecycle": {NoAuthorize: true}, diff --git a/coderd/coderdtest/swaggerparser.go b/coderd/coderdtest/swaggerparser.go index 1f3ab4a323..8abf783149 100644 --- a/coderd/coderdtest/swaggerparser.go +++ b/coderd/coderdtest/swaggerparser.go @@ -344,7 +344,7 @@ func assertProduce(t *testing.T, comment SwaggerComment) { assert.Contains(t, allowedProduceTypes, comment.produce, "@Produce value is limited to specific types: %s", strings.Join(allowedProduceTypes, ",")) } else { if (comment.router == "/workspaceagents/me/app-health" && comment.method == "post") || - (comment.router == "/workspaceagents/me/version" && comment.method == "post") || + (comment.router == "/workspaceagents/me/startup" && comment.method == "post") || (comment.router == "/licenses/{id}" && comment.method == "delete") || (comment.router == "/debug/coordinator" && comment.method == "get") { return // Exception: HTTP 200 is returned without response entity diff --git a/coderd/database/dbfake/databasefake.go b/coderd/database/dbfake/databasefake.go index 07bf8a117c..01ad6942d6 100644 --- a/coderd/database/dbfake/databasefake.go +++ b/coderd/database/dbfake/databasefake.go @@ -3192,7 +3192,7 @@ func (q *fakeQuerier) UpdateWorkspaceAgentConnectionByID(_ context.Context, arg return sql.ErrNoRows } -func (q *fakeQuerier) UpdateWorkspaceAgentVersionByID(_ context.Context, arg database.UpdateWorkspaceAgentVersionByIDParams) error { +func (q *fakeQuerier) UpdateWorkspaceAgentStartupByID(_ context.Context, arg database.UpdateWorkspaceAgentStartupByIDParams) error { if err := validateDatabaseType(arg); err != nil { return err } @@ -3206,6 +3206,7 @@ func (q *fakeQuerier) UpdateWorkspaceAgentVersionByID(_ context.Context, arg dat } agent.Version = arg.Version + agent.ExpandedDirectory = arg.ExpandedDirectory q.workspaceAgents[index] = agent return nil } diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 11666f135e..e8bf641dd5 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -468,7 +468,8 @@ CREATE TABLE workspace_agents ( motd_file text DEFAULT ''::text NOT NULL, lifecycle_state workspace_agent_lifecycle_state DEFAULT 'created'::workspace_agent_lifecycle_state NOT NULL, login_before_ready boolean DEFAULT true NOT NULL, - startup_script_timeout_seconds integer DEFAULT 0 NOT NULL + startup_script_timeout_seconds integer DEFAULT 0 NOT NULL, + expanded_directory character varying(4096) DEFAULT ''::character varying NOT NULL ); COMMENT ON COLUMN workspace_agents.version IS 'Version tracks the version of the currently running workspace agent. Workspace agents register their version upon start.'; @@ -485,6 +486,8 @@ COMMENT ON COLUMN workspace_agents.login_before_ready IS 'If true, the agent wil COMMENT ON COLUMN workspace_agents.startup_script_timeout_seconds IS 'The number of seconds to wait for the startup script to complete. If the script does not complete within this time, the agent lifecycle will be marked as start_timeout.'; +COMMENT ON COLUMN workspace_agents.expanded_directory IS 'The resolved path of a user-specified directory. e.g. ~/coder -> /home/coder/coder'; + CREATE TABLE workspace_apps ( id uuid NOT NULL, created_at timestamp with time zone NOT NULL, diff --git a/coderd/database/migrations/000096_agent_resolved_directory.down.sql b/coderd/database/migrations/000096_agent_resolved_directory.down.sql new file mode 100644 index 0000000000..e54c206b26 --- /dev/null +++ b/coderd/database/migrations/000096_agent_resolved_directory.down.sql @@ -0,0 +1,6 @@ +BEGIN; + +ALTER TABLE ONLY workspace_agents + DROP COLUMN IF EXISTS expanded_directory; + +COMMIT; diff --git a/coderd/database/migrations/000096_agent_resolved_directory.up.sql b/coderd/database/migrations/000096_agent_resolved_directory.up.sql new file mode 100644 index 0000000000..94e65b051f --- /dev/null +++ b/coderd/database/migrations/000096_agent_resolved_directory.up.sql @@ -0,0 +1,9 @@ +BEGIN; + +ALTER TABLE ONLY workspace_agents + ADD COLUMN IF NOT EXISTS expanded_directory varchar(4096) DEFAULT '' NOT NULL; + +COMMENT ON COLUMN workspace_agents.expanded_directory +IS 'The resolved path of a user-specified directory. e.g. ~/coder -> /home/coder/coder'; + +COMMIT; diff --git a/coderd/database/models.go b/coderd/database/models.go index 7bd1601078..ce5d2204a7 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1531,6 +1531,8 @@ type WorkspaceAgent struct { LoginBeforeReady bool `db:"login_before_ready" json:"login_before_ready"` // The number of seconds to wait for the startup script to complete. If the script does not complete within this time, the agent lifecycle will be marked as start_timeout. StartupScriptTimeoutSeconds int32 `db:"startup_script_timeout_seconds" json:"startup_script_timeout_seconds"` + // The resolved path of a user-specified directory. e.g. ~/coder -> /home/coder/coder + ExpandedDirectory string `db:"expanded_directory" json:"expanded_directory"` } type WorkspaceApp struct { diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 38b5b34c6a..a588b28076 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -196,7 +196,7 @@ type sqlcQuerier interface { UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams) (Workspace, error) UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg UpdateWorkspaceAgentConnectionByIDParams) error UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, arg UpdateWorkspaceAgentLifecycleStateByIDParams) error - UpdateWorkspaceAgentVersionByID(ctx context.Context, arg UpdateWorkspaceAgentVersionByIDParams) error + UpdateWorkspaceAgentStartupByID(ctx context.Context, arg UpdateWorkspaceAgentStartupByIDParams) error UpdateWorkspaceAppHealthByID(ctx context.Context, arg UpdateWorkspaceAppHealthByIDParams) error UpdateWorkspaceAutostart(ctx context.Context, arg UpdateWorkspaceAutostartParams) error UpdateWorkspaceBuildByID(ctx context.Context, arg UpdateWorkspaceBuildByIDParams) (WorkspaceBuild, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index ded90b8ae8..9ed8530997 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4825,7 +4825,7 @@ func (q *sqlQuerier) UpdateUserStatus(ctx context.Context, arg UpdateUserStatusP const getWorkspaceAgentByAuthToken = `-- name: GetWorkspaceAgentByAuthToken :one SELECT - id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, login_before_ready, startup_script_timeout_seconds + id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, login_before_ready, startup_script_timeout_seconds, expanded_directory FROM workspace_agents WHERE @@ -4863,13 +4863,14 @@ func (q *sqlQuerier) GetWorkspaceAgentByAuthToken(ctx context.Context, authToken &i.LifecycleState, &i.LoginBeforeReady, &i.StartupScriptTimeoutSeconds, + &i.ExpandedDirectory, ) return i, err } const getWorkspaceAgentByID = `-- name: GetWorkspaceAgentByID :one SELECT - id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, login_before_ready, startup_script_timeout_seconds + id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, login_before_ready, startup_script_timeout_seconds, expanded_directory FROM workspace_agents WHERE @@ -4905,13 +4906,14 @@ func (q *sqlQuerier) GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (W &i.LifecycleState, &i.LoginBeforeReady, &i.StartupScriptTimeoutSeconds, + &i.ExpandedDirectory, ) return i, err } const getWorkspaceAgentByInstanceID = `-- name: GetWorkspaceAgentByInstanceID :one SELECT - id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, login_before_ready, startup_script_timeout_seconds + id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, login_before_ready, startup_script_timeout_seconds, expanded_directory FROM workspace_agents WHERE @@ -4949,13 +4951,14 @@ func (q *sqlQuerier) GetWorkspaceAgentByInstanceID(ctx context.Context, authInst &i.LifecycleState, &i.LoginBeforeReady, &i.StartupScriptTimeoutSeconds, + &i.ExpandedDirectory, ) return i, err } const getWorkspaceAgentsByResourceIDs = `-- name: GetWorkspaceAgentsByResourceIDs :many SELECT - id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, login_before_ready, startup_script_timeout_seconds + id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, login_before_ready, startup_script_timeout_seconds, expanded_directory FROM workspace_agents WHERE @@ -4997,6 +5000,7 @@ func (q *sqlQuerier) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids [] &i.LifecycleState, &i.LoginBeforeReady, &i.StartupScriptTimeoutSeconds, + &i.ExpandedDirectory, ); err != nil { return nil, err } @@ -5012,7 +5016,7 @@ func (q *sqlQuerier) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids [] } const getWorkspaceAgentsCreatedAfter = `-- name: GetWorkspaceAgentsCreatedAfter :many -SELECT id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, login_before_ready, startup_script_timeout_seconds FROM workspace_agents WHERE created_at > $1 +SELECT id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, login_before_ready, startup_script_timeout_seconds, expanded_directory FROM workspace_agents WHERE created_at > $1 ` func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceAgent, error) { @@ -5050,6 +5054,7 @@ func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, created &i.LifecycleState, &i.LoginBeforeReady, &i.StartupScriptTimeoutSeconds, + &i.ExpandedDirectory, ); err != nil { return nil, err } @@ -5088,7 +5093,7 @@ INSERT INTO startup_script_timeout_seconds ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) RETURNING id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, login_before_ready, startup_script_timeout_seconds + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) RETURNING id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, login_before_ready, startup_script_timeout_seconds, expanded_directory ` type InsertWorkspaceAgentParams struct { @@ -5162,6 +5167,7 @@ func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspa &i.LifecycleState, &i.LoginBeforeReady, &i.StartupScriptTimeoutSeconds, + &i.ExpandedDirectory, ) return i, err } @@ -5219,22 +5225,24 @@ func (q *sqlQuerier) UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, return err } -const updateWorkspaceAgentVersionByID = `-- name: UpdateWorkspaceAgentVersionByID :exec +const updateWorkspaceAgentStartupByID = `-- name: UpdateWorkspaceAgentStartupByID :exec UPDATE workspace_agents SET - version = $2 + version = $2, + expanded_directory = $3 WHERE id = $1 ` -type UpdateWorkspaceAgentVersionByIDParams struct { - ID uuid.UUID `db:"id" json:"id"` - Version string `db:"version" json:"version"` +type UpdateWorkspaceAgentStartupByIDParams struct { + ID uuid.UUID `db:"id" json:"id"` + Version string `db:"version" json:"version"` + ExpandedDirectory string `db:"expanded_directory" json:"expanded_directory"` } -func (q *sqlQuerier) UpdateWorkspaceAgentVersionByID(ctx context.Context, arg UpdateWorkspaceAgentVersionByIDParams) error { - _, err := q.db.ExecContext(ctx, updateWorkspaceAgentVersionByID, arg.ID, arg.Version) +func (q *sqlQuerier) UpdateWorkspaceAgentStartupByID(ctx context.Context, arg UpdateWorkspaceAgentStartupByIDParams) error { + _, err := q.db.ExecContext(ctx, updateWorkspaceAgentStartupByID, arg.ID, arg.Version, arg.ExpandedDirectory) return err } diff --git a/coderd/database/queries/workspaceagents.sql b/coderd/database/queries/workspaceagents.sql index 8ff6818230..544e58e432 100644 --- a/coderd/database/queries/workspaceagents.sql +++ b/coderd/database/queries/workspaceagents.sql @@ -75,11 +75,12 @@ SET WHERE id = $1; --- name: UpdateWorkspaceAgentVersionByID :exec +-- name: UpdateWorkspaceAgentStartupByID :exec UPDATE workspace_agents SET - version = $2 + version = $2, + expanded_directory = $3 WHERE id = $1; diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 87456b6d82..5c14f5ea21 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -155,17 +155,17 @@ func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) }) } -// @Summary Submit workspace agent version -// @ID submit-workspace-agent-version +// @Summary Submit workspace agent startup +// @ID submit-workspace-agent-startup // @Security CoderSessionToken // @Accept json // @Produce json // @Tags Agents -// @Param request body agentsdk.PostVersionRequest true "Version request" +// @Param request body agentsdk.PostStartupRequest true "Startup request" // @Success 200 -// @Router /workspaceagents/me/version [post] +// @Router /workspaceagents/me/startup [post] // @x-apidocgen {"skip": true} -func (api *API) postWorkspaceAgentVersion(rw http.ResponseWriter, r *http.Request) { +func (api *API) postWorkspaceAgentStartup(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() workspaceAgent := httpmw.WorkspaceAgent(r) apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout, api.DeploymentConfig.AgentFallbackTroubleshootingURL.Value) @@ -177,7 +177,7 @@ func (api *API) postWorkspaceAgentVersion(rw http.ResponseWriter, r *http.Reques return } - var req agentsdk.PostVersionRequest + var req agentsdk.PostStartupRequest if !httpapi.Read(ctx, rw, r, &req) { return } @@ -192,9 +192,10 @@ func (api *API) postWorkspaceAgentVersion(rw http.ResponseWriter, r *http.Reques return } - if err := api.Database.UpdateWorkspaceAgentVersionByID(ctx, database.UpdateWorkspaceAgentVersionByIDParams{ - ID: apiAgent.ID, - Version: req.Version, + if err := api.Database.UpdateWorkspaceAgentStartupByID(ctx, database.UpdateWorkspaceAgentStartupByIDParams{ + ID: apiAgent.ID, + Version: req.Version, + ExpandedDirectory: req.ExpandedDirectory, }); err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Error setting agent version", @@ -782,6 +783,7 @@ func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.Coordin Version: dbAgent.Version, EnvironmentVariables: envs, Directory: dbAgent.Directory, + ExpandedDirectory: dbAgent.ExpandedDirectory, Apps: apps, ConnectionTimeoutSeconds: dbAgent.ConnectionTimeoutSeconds, TroubleshootingURL: troubleshootingURL, diff --git a/coderd/wsconncache/wsconncache_test.go b/coderd/wsconncache/wsconncache_test.go index 45abb42033..397761add5 100644 --- a/coderd/wsconncache/wsconncache_test.go +++ b/coderd/wsconncache/wsconncache_test.go @@ -226,6 +226,6 @@ func (*client) PostAppHealth(_ context.Context, _ agentsdk.PostAppHealthsRequest return nil } -func (*client) PostVersion(_ context.Context, _ string) error { +func (*client) PostStartup(_ context.Context, _ agentsdk.PostStartupRequest) error { return nil } diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index d09c4a5382..9406b73f4b 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -469,13 +469,13 @@ func (c *Client) PostLifecycle(ctx context.Context, req PostLifecycleRequest) er return nil } -type PostVersionRequest struct { - Version string `json:"version"` +type PostStartupRequest struct { + Version string `json:"version"` + ExpandedDirectory string `json:"expanded_directory"` } -func (c *Client) PostVersion(ctx context.Context, version string) error { - versionReq := PostVersionRequest{Version: version} - res, err := c.SDK.Request(ctx, http.MethodPost, "/api/v2/workspaceagents/me/version", versionReq) +func (c *Client) PostStartup(ctx context.Context, req PostStartupRequest) error { + res, err := c.SDK.Request(ctx, http.MethodPost, "/api/v2/workspaceagents/me/startup", req) if err != nil { return err } diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 244fb95eee..b60a6906e7 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -68,6 +68,7 @@ type WorkspaceAgent struct { OperatingSystem string `json:"operating_system"` StartupScript string `json:"startup_script,omitempty"` Directory string `json:"directory,omitempty"` + ExpandedDirectory string `json:"expanded_directory,omitempty"` Version string `json:"version"` Apps []WorkspaceApp `json:"apps"` // DERPLatency is mapped by region name (e.g. "New York City", "Seattle"). diff --git a/docs/api/agents.md b/docs/api/agents.md index 31f88ad590..ffea8e4006 100644 --- a/docs/api/agents.md +++ b/docs/api/agents.md @@ -490,6 +490,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent} \ "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", diff --git a/docs/api/builds.md b/docs/api/builds.md index 8dc8c107fa..343bac7312 100644 --- a/docs/api/builds.md +++ b/docs/api/builds.md @@ -82,6 +82,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", @@ -227,6 +228,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \ "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", @@ -515,6 +517,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/res "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", @@ -598,6 +601,7 @@ Status Code **200** | `»» disconnected_at` | string(date-time) | false | | | | `»» environment_variables` | object | false | | | | `»»» [any property]` | string | false | | | +| `»» expanded_directory` | string | false | | | | `»» first_connected_at` | string(date-time) | false | | | | `»» id` | string(uuid) | false | | | | `»» instance_id` | string | false | | | @@ -737,6 +741,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", @@ -887,6 +892,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", @@ -1002,6 +1008,7 @@ Status Code **200** | `»»» disconnected_at` | string(date-time) | false | | | | `»»» environment_variables` | object | false | | | | `»»»» [any property]` | string | false | | | +| `»»» expanded_directory` | string | false | | | | `»»» first_connected_at` | string(date-time) | false | | | | `»»» id` | string(uuid) | false | | | | `»»» instance_id` | string | false | | | @@ -1201,6 +1208,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 58dc3a6a0d..72da2799a4 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -228,19 +228,21 @@ | ------- | -------------------------------------------------------------------- | -------- | ------------ | ----------- | | `state` | [codersdk.WorkspaceAgentLifecycle](#codersdkworkspaceagentlifecycle) | false | | | -## agentsdk.PostVersionRequest +## agentsdk.PostStartupRequest ```json { + "expanded_directory": "string", "version": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| --------- | ------ | -------- | ------------ | ----------- | -| `version` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| -------------------- | ------ | -------- | ------------ | ----------- | +| `expanded_directory` | string | false | | | +| `version` | string | false | | | ## agentsdk.Stats @@ -4942,6 +4944,7 @@ Parameter represents a set value for the scope. "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", @@ -5064,6 +5067,7 @@ Parameter represents a set value for the scope. "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", @@ -5104,6 +5108,7 @@ Parameter represents a set value for the scope. | `disconnected_at` | string | false | | | | `environment_variables` | object | false | | | | » `[any property]` | string | false | | | +| `expanded_directory` | string | false | | | | `first_connected_at` | string | false | | | | `id` | string | false | | | | `instance_id` | string | false | | | @@ -5397,6 +5402,7 @@ Parameter represents a set value for the scope. "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", @@ -5565,6 +5571,7 @@ Parameter represents a set value for the scope. "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", @@ -5755,6 +5762,7 @@ Parameter represents a set value for the scope. "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", diff --git a/docs/api/templates.md b/docs/api/templates.md index 2599339db9..c4820c9036 100644 --- a/docs/api/templates.md +++ b/docs/api/templates.md @@ -1566,6 +1566,7 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/d "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", @@ -1649,6 +1650,7 @@ Status Code **200** | `»» disconnected_at` | string(date-time) | false | | | | `»» environment_variables` | object | false | | | | `»»» [any property]` | string | false | | | +| `»» expanded_directory` | string | false | | | | `»» first_connected_at` | string(date-time) | false | | | | `»» id` | string(uuid) | false | | | | `»» instance_id` | string | false | | | @@ -1918,6 +1920,7 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/r "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", @@ -2001,6 +2004,7 @@ Status Code **200** | `»» disconnected_at` | string(date-time) | false | | | | `»» environment_variables` | object | false | | | | `»»» [any property]` | string | false | | | +| `»» expanded_directory` | string | false | | | | `»» first_connected_at` | string(date-time) | false | | | | `»» id` | string(uuid) | false | | | | `»» instance_id` | string | false | | | diff --git a/docs/api/workspaces.md b/docs/api/workspaces.md index b1c5efbd6e..8de9218b15 100644 --- a/docs/api/workspaces.md +++ b/docs/api/workspaces.md @@ -86,6 +86,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", @@ -250,6 +251,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", @@ -433,6 +435,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \ "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", @@ -598,6 +601,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \ "property1": "string", "property2": "string" }, + "expanded_directory": "string", "first_connected_at": "2019-08-24T14:15:22Z", "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "instance_id": "string", diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index de16786644..e2811b4331 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -923,6 +923,7 @@ export interface WorkspaceAgent { readonly operating_system: string readonly startup_script?: string readonly directory?: string + readonly expanded_directory?: string readonly version: string readonly apps: WorkspaceApp[] readonly latency?: Record