diff --git a/agent/agent.go b/agent/agent.go index fda2958ab2db1..4189f89569f30 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! @@ -1314,3 +1327,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 22290ba63b79f..d91039b38bef2 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 46b1fa7946593..c80c48eaff5b8 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 e1762712b01b4..ce1f9616f0c03 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 4eb4d19ac5188..bfce5a5fb1a88 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 42abafc68dc6c..3fcbb63cb632c 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 1f3ab4a323131..8abf78314930a 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 07bf8a117cb09..01ad6942d6b10 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 11666f135e975..e8bf641dd59e6 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 0000000000000..e54c206b26418 --- /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 0000000000000..94e65b051f5b9 --- /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 7bd1601078556..ce5d2204a7266 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 38b5b34c6a601..a588b28076233 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 ded90b8ae8189..9ed8530997320 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 8ff681823016e..544e58e432c4c 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 87456b6d82ad5..5c14f5ea217a5 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 45abb42033f8f..397761add5e4c 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 d09c4a5382534..9406b73f4b69b 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 244fb95eee1ab..b60a6906e7ff6 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 31f88ad590511..ffea8e40065f3 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 8dc8c107fad17..343bac7312b96 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 58dc3a6a0d70a..72da2799a471b 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 2599339db9245..c4820c9036c2e 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 b1c5efbd6e9c2..8de9218b15b14 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 de16786644201..e2811b43318a3 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