feat: add telemetry support for workspace agent subsystem (#7579)

This commit is contained in:
Jon Ayers 2023-05-17 22:49:25 -05:00 committed by GitHub
parent 52bb84a26f
commit 00a2413c03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 366 additions and 27 deletions

View File

@ -62,6 +62,7 @@ type Options struct {
IgnorePorts map[int]string
SSHMaxTimeout time.Duration
TailnetListenPort uint16
Subsystem codersdk.AgentSubsystem
}
type Client interface {
@ -119,6 +120,7 @@ func New(options Options) Agent {
ignorePorts: options.IgnorePorts,
connStatsChan: make(chan *agentsdk.Stats, 1),
sshMaxTimeout: options.SSHMaxTimeout,
subsystem: options.Subsystem,
}
a.init(ctx)
return a
@ -136,6 +138,7 @@ type agent struct {
// listing all listening ports. This is helpful to hide ports that
// are used by the agent, that the user does not care about.
ignorePorts map[int]string
subsystem codersdk.AgentSubsystem
reconnectingPTYs sync.Map
reconnectingPTYTimeout time.Duration
@ -488,6 +491,7 @@ func (a *agent) run(ctx context.Context) error {
err = a.client.PostStartup(ctx, agentsdk.PostStartupRequest{
Version: buildinfo.Version(),
ExpandedDirectory: manifest.Directory,
Subsystem: a.subsystem,
})
if err != nil {
return xerrors.Errorf("update workspace agent version: %w", err)
@ -1455,3 +1459,8 @@ func expandDirectory(dir string) (string, error) {
}
return dir, nil
}
// EnvAgentSubsystem is the environment variable used to denote the
// specialized environment in which the agent is running
// (e.g. envbox, envbuilder).
const EnvAgentSubsystem = "CODER_AGENT_SUBSYSTEM"

View File

@ -26,6 +26,7 @@ import (
"github.com/coder/coder/agent/reaper"
"github.com/coder/coder/buildinfo"
"github.com/coder/coder/cli/clibase"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/codersdk/agentsdk"
)
@ -197,6 +198,7 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
return xerrors.Errorf("add executable to $PATH: %w", err)
}
subsystem := inv.Environ.Get(agent.EnvAgentSubsystem)
agnt := agent.New(agent.Options{
Client: client,
Logger: logger,
@ -218,6 +220,7 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
},
IgnorePorts: ignorePorts,
SSHMaxTimeout: sshMaxTimeout,
Subsystem: codersdk.AgentSubsystem(subsystem),
})
debugSrvClose := ServeHandler(ctx, logger, agnt.HTTPDebug(), debugAddress, "debug")

View File

@ -12,8 +12,10 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/coder/coder/agent"
"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/provisioner/echo"
"github.com/coder/coder/provisionersdk/proto"
"github.com/coder/coder/pty/ptytest"
@ -235,4 +237,43 @@ func TestWorkspaceAgent(t *testing.T) {
_, err = uuid.Parse(strings.TrimSpace(string(token)))
require.NoError(t, err)
})
t.Run("PostStartup", func(t *testing.T) {
t.Parallel()
authToken := uuid.NewString()
client := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
})
user := coderdtest.CreateFirstUser(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
logDir := t.TempDir()
inv, _ := clitest.New(t,
"agent",
"--auth", "token",
"--agent-token", authToken,
"--agent-url", client.URL.String(),
"--log-dir", logDir,
)
// Set the subsystem for the agent.
inv.Environ.Set(agent.EnvAgentSubsystem, string(codersdk.AgentSubsystemEnvbox))
pty := ptytest.New(t).Attach(inv)
clitest.Start(t, inv)
pty.ExpectMatch("starting agent")
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
require.Len(t, resources, 1)
require.Len(t, resources[0].Agents, 1)
require.Equal(t, codersdk.AgentSubsystemEnvbox, resources[0].Agents[0].Subsystem)
})
}

15
coderd/apidoc/docs.go generated
View File

@ -5966,6 +5966,9 @@ const docTemplate = `{
"expanded_directory": {
"type": "string"
},
"subsystem": {
"$ref": "#/definitions/codersdk.AgentSubsystem"
},
"version": {
"type": "string"
}
@ -6406,6 +6409,15 @@ const docTemplate = `{
}
}
},
"codersdk.AgentSubsystem": {
"type": "string",
"enum": [
"envbox"
],
"x-enum-varnames": [
"AgentSubsystemEnvbox"
]
},
"codersdk.AppHostResponse": {
"type": "object",
"properties": {
@ -9627,6 +9639,9 @@ const docTemplate = `{
"status": {
"$ref": "#/definitions/codersdk.WorkspaceAgentStatus"
},
"subsystem": {
"$ref": "#/definitions/codersdk.AgentSubsystem"
},
"troubleshooting_url": {
"type": "string"
},

View File

@ -5258,6 +5258,9 @@
"expanded_directory": {
"type": "string"
},
"subsystem": {
"$ref": "#/definitions/codersdk.AgentSubsystem"
},
"version": {
"type": "string"
}
@ -5676,6 +5679,11 @@
}
}
},
"codersdk.AgentSubsystem": {
"type": "string",
"enum": ["envbox"],
"x-enum-varnames": ["AgentSubsystemEnvbox"]
},
"codersdk.AppHostResponse": {
"type": "object",
"properties": {
@ -8665,6 +8673,9 @@
"status": {
"$ref": "#/definitions/codersdk.WorkspaceAgentStatus"
},
"subsystem": {
"$ref": "#/definitions/codersdk.AgentSubsystem"
},
"troubleshooting_url": {
"type": "string"
},

View File

@ -1029,7 +1029,8 @@ func (s *MethodTestSuite) TestWorkspace() {
res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: build.JobID})
agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID})
check.Args(database.UpdateWorkspaceAgentStartupByIDParams{
ID: agt.ID,
ID: agt.ID,
Subsystem: database.WorkspaceAgentSubsystemNone,
}).Asserts(ws, rbac.ActionUpdate).Returns()
}))
s.Run("GetWorkspaceAgentStartupLogsAfter", s.Subtest(func(db database.Store, check *expects) {

View File

@ -3702,6 +3702,7 @@ func (q *fakeQuerier) UpdateWorkspaceAgentStartupByID(_ context.Context, arg dat
agent.Version = arg.Version
agent.ExpandedDirectory = arg.ExpandedDirectory
agent.Subsystem = arg.Subsystem
q.workspaceAgents[index] = agent
return nil
}

View File

@ -116,6 +116,12 @@ CREATE TYPE workspace_agent_lifecycle_state AS ENUM (
'off'
);
CREATE TYPE workspace_agent_subsystem AS ENUM (
'envbuilder',
'envbox',
'none'
);
CREATE TYPE workspace_app_health AS ENUM (
'disabled',
'initializing',
@ -597,6 +603,7 @@ CREATE TABLE workspace_agents (
shutdown_script_timeout_seconds integer DEFAULT 0 NOT NULL,
startup_logs_length integer DEFAULT 0 NOT NULL,
startup_logs_overflowed boolean DEFAULT false NOT NULL,
subsystem workspace_agent_subsystem DEFAULT 'none'::workspace_agent_subsystem NOT NULL,
CONSTRAINT max_startup_logs_length CHECK ((startup_logs_length <= 1048576))
);

View File

@ -0,0 +1,4 @@
BEGIN;
ALTER TABLE workspace_agents DROP COLUMN subsystem;
DROP TYPE workspace_agent_subsystem;
COMMIT;

View File

@ -0,0 +1,4 @@
BEGIN;
CREATE TYPE workspace_agent_subsystem AS ENUM ('envbuilder', 'envbox', 'none');
ALTER TABLE workspace_agents ADD COLUMN subsystem workspace_agent_subsystem NOT NULL default 'none';
COMMIT;

View File

@ -1096,6 +1096,67 @@ func AllWorkspaceAgentLifecycleStateValues() []WorkspaceAgentLifecycleState {
}
}
type WorkspaceAgentSubsystem string
const (
WorkspaceAgentSubsystemEnvbuilder WorkspaceAgentSubsystem = "envbuilder"
WorkspaceAgentSubsystemEnvbox WorkspaceAgentSubsystem = "envbox"
WorkspaceAgentSubsystemNone WorkspaceAgentSubsystem = "none"
)
func (e *WorkspaceAgentSubsystem) Scan(src interface{}) error {
switch s := src.(type) {
case []byte:
*e = WorkspaceAgentSubsystem(s)
case string:
*e = WorkspaceAgentSubsystem(s)
default:
return fmt.Errorf("unsupported scan type for WorkspaceAgentSubsystem: %T", src)
}
return nil
}
type NullWorkspaceAgentSubsystem struct {
WorkspaceAgentSubsystem WorkspaceAgentSubsystem
Valid bool // Valid is true if WorkspaceAgentSubsystem is not NULL
}
// Scan implements the Scanner interface.
func (ns *NullWorkspaceAgentSubsystem) Scan(value interface{}) error {
if value == nil {
ns.WorkspaceAgentSubsystem, ns.Valid = "", false
return nil
}
ns.Valid = true
return ns.WorkspaceAgentSubsystem.Scan(value)
}
// Value implements the driver Valuer interface.
func (ns NullWorkspaceAgentSubsystem) Value() (driver.Value, error) {
if !ns.Valid {
return nil, nil
}
return string(ns.WorkspaceAgentSubsystem), nil
}
func (e WorkspaceAgentSubsystem) Valid() bool {
switch e {
case WorkspaceAgentSubsystemEnvbuilder,
WorkspaceAgentSubsystemEnvbox,
WorkspaceAgentSubsystemNone:
return true
}
return false
}
func AllWorkspaceAgentSubsystemValues() []WorkspaceAgentSubsystem {
return []WorkspaceAgentSubsystem{
WorkspaceAgentSubsystemEnvbuilder,
WorkspaceAgentSubsystemEnvbox,
WorkspaceAgentSubsystemNone,
}
}
type WorkspaceAppHealth string
const (
@ -1587,7 +1648,8 @@ type WorkspaceAgent struct {
// Total length of startup logs
StartupLogsLength int32 `db:"startup_logs_length" json:"startup_logs_length"`
// Whether the startup logs overflowed in length
StartupLogsOverflowed bool `db:"startup_logs_overflowed" json:"startup_logs_overflowed"`
StartupLogsOverflowed bool `db:"startup_logs_overflowed" json:"startup_logs_overflowed"`
Subsystem WorkspaceAgentSubsystem `db:"subsystem" json:"subsystem"`
}
type WorkspaceAgentMetadatum struct {

View File

@ -5553,7 +5553,7 @@ func (q *sqlQuerier) DeleteOldWorkspaceAgentStartupLogs(ctx context.Context) err
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, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed
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, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed, subsystem
FROM
workspace_agents
WHERE
@ -5596,13 +5596,14 @@ func (q *sqlQuerier) GetWorkspaceAgentByAuthToken(ctx context.Context, authToken
&i.ShutdownScriptTimeoutSeconds,
&i.StartupLogsLength,
&i.StartupLogsOverflowed,
&i.Subsystem,
)
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, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed
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, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed, subsystem
FROM
workspace_agents
WHERE
@ -5643,13 +5644,14 @@ func (q *sqlQuerier) GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (W
&i.ShutdownScriptTimeoutSeconds,
&i.StartupLogsLength,
&i.StartupLogsOverflowed,
&i.Subsystem,
)
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, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed
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, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed, subsystem
FROM
workspace_agents
WHERE
@ -5692,6 +5694,7 @@ func (q *sqlQuerier) GetWorkspaceAgentByInstanceID(ctx context.Context, authInst
&i.ShutdownScriptTimeoutSeconds,
&i.StartupLogsLength,
&i.StartupLogsOverflowed,
&i.Subsystem,
)
return i, err
}
@ -5786,7 +5789,7 @@ func (q *sqlQuerier) GetWorkspaceAgentStartupLogsAfter(ctx context.Context, arg
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, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed
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, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed, subsystem
FROM
workspace_agents
WHERE
@ -5833,6 +5836,7 @@ func (q *sqlQuerier) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids []
&i.ShutdownScriptTimeoutSeconds,
&i.StartupLogsLength,
&i.StartupLogsOverflowed,
&i.Subsystem,
); err != nil {
return nil, err
}
@ -5848,7 +5852,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, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed 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, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed, subsystem FROM workspace_agents WHERE created_at > $1
`
func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceAgent, error) {
@ -5891,6 +5895,7 @@ func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, created
&i.ShutdownScriptTimeoutSeconds,
&i.StartupLogsLength,
&i.StartupLogsOverflowed,
&i.Subsystem,
); err != nil {
return nil, err
}
@ -5907,7 +5912,7 @@ func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, created
const getWorkspaceAgentsInLatestBuildByWorkspaceID = `-- name: GetWorkspaceAgentsInLatestBuildByWorkspaceID :many
SELECT
workspace_agents.id, workspace_agents.created_at, workspace_agents.updated_at, workspace_agents.name, workspace_agents.first_connected_at, workspace_agents.last_connected_at, workspace_agents.disconnected_at, workspace_agents.resource_id, workspace_agents.auth_token, workspace_agents.auth_instance_id, workspace_agents.architecture, workspace_agents.environment_variables, workspace_agents.operating_system, workspace_agents.startup_script, workspace_agents.instance_metadata, workspace_agents.resource_metadata, workspace_agents.directory, workspace_agents.version, workspace_agents.last_connected_replica_id, workspace_agents.connection_timeout_seconds, workspace_agents.troubleshooting_url, workspace_agents.motd_file, workspace_agents.lifecycle_state, workspace_agents.login_before_ready, workspace_agents.startup_script_timeout_seconds, workspace_agents.expanded_directory, workspace_agents.shutdown_script, workspace_agents.shutdown_script_timeout_seconds, workspace_agents.startup_logs_length, workspace_agents.startup_logs_overflowed
workspace_agents.id, workspace_agents.created_at, workspace_agents.updated_at, workspace_agents.name, workspace_agents.first_connected_at, workspace_agents.last_connected_at, workspace_agents.disconnected_at, workspace_agents.resource_id, workspace_agents.auth_token, workspace_agents.auth_instance_id, workspace_agents.architecture, workspace_agents.environment_variables, workspace_agents.operating_system, workspace_agents.startup_script, workspace_agents.instance_metadata, workspace_agents.resource_metadata, workspace_agents.directory, workspace_agents.version, workspace_agents.last_connected_replica_id, workspace_agents.connection_timeout_seconds, workspace_agents.troubleshooting_url, workspace_agents.motd_file, workspace_agents.lifecycle_state, workspace_agents.login_before_ready, workspace_agents.startup_script_timeout_seconds, workspace_agents.expanded_directory, workspace_agents.shutdown_script, workspace_agents.shutdown_script_timeout_seconds, workspace_agents.startup_logs_length, workspace_agents.startup_logs_overflowed, workspace_agents.subsystem
FROM
workspace_agents
JOIN
@ -5966,6 +5971,7 @@ func (q *sqlQuerier) GetWorkspaceAgentsInLatestBuildByWorkspaceID(ctx context.Co
&i.ShutdownScriptTimeoutSeconds,
&i.StartupLogsLength,
&i.StartupLogsOverflowed,
&i.Subsystem,
); err != nil {
return nil, err
}
@ -6006,7 +6012,7 @@ INSERT INTO
shutdown_script_timeout_seconds
)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) 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, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) 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, shutdown_script, shutdown_script_timeout_seconds, startup_logs_length, startup_logs_overflowed, subsystem
`
type InsertWorkspaceAgentParams struct {
@ -6089,6 +6095,7 @@ func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspa
&i.ShutdownScriptTimeoutSeconds,
&i.StartupLogsLength,
&i.StartupLogsOverflowed,
&i.Subsystem,
)
return i, err
}
@ -6275,19 +6282,26 @@ UPDATE
workspace_agents
SET
version = $2,
expanded_directory = $3
expanded_directory = $3,
subsystem = $4
WHERE
id = $1
`
type UpdateWorkspaceAgentStartupByIDParams struct {
ID uuid.UUID `db:"id" json:"id"`
Version string `db:"version" json:"version"`
ExpandedDirectory string `db:"expanded_directory" json:"expanded_directory"`
ID uuid.UUID `db:"id" json:"id"`
Version string `db:"version" json:"version"`
ExpandedDirectory string `db:"expanded_directory" json:"expanded_directory"`
Subsystem WorkspaceAgentSubsystem `db:"subsystem" json:"subsystem"`
}
func (q *sqlQuerier) UpdateWorkspaceAgentStartupByID(ctx context.Context, arg UpdateWorkspaceAgentStartupByIDParams) error {
_, err := q.db.ExecContext(ctx, updateWorkspaceAgentStartupByID, arg.ID, arg.Version, arg.ExpandedDirectory)
_, err := q.db.ExecContext(ctx, updateWorkspaceAgentStartupByID,
arg.ID,
arg.Version,
arg.ExpandedDirectory,
arg.Subsystem,
)
return err
}

View File

@ -82,7 +82,8 @@ UPDATE
workspace_agents
SET
version = $2,
expanded_directory = $3
expanded_directory = $3,
subsystem = $4
WHERE
id = $1;

View File

@ -562,6 +562,7 @@ func ConvertWorkspaceAgent(agent database.WorkspaceAgent) WorkspaceAgent {
Directory: agent.Directory != "",
ConnectionTimeoutSeconds: agent.ConnectionTimeoutSeconds,
ShutdownScript: agent.ShutdownScript.Valid,
Subsystem: string(agent.Subsystem),
}
if agent.FirstConnectedAt.Valid {
snapAgent.FirstConnectedAt = &agent.FirstConnectedAt.Time
@ -783,6 +784,7 @@ type WorkspaceAgent struct {
DisconnectedAt *time.Time `json:"disconnected_at"`
ConnectionTimeoutSeconds int32 `json:"connection_timeout_seconds"`
ShutdownScript bool `json:"shutdown_script"`
Subsystem string `json:"subsystem"`
}
type WorkspaceAgentStat struct {

View File

@ -1,7 +1,6 @@
package telemetry_test
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
@ -22,6 +21,7 @@ import (
"github.com/coder/coder/coderd/database/dbfake"
"github.com/coder/coder/coderd/database/dbgen"
"github.com/coder/coder/coderd/telemetry"
"github.com/coder/coder/testutil"
)
func TestMain(m *testing.M) {
@ -37,7 +37,7 @@ func TestTelemetry(t *testing.T) {
db := dbfake.New()
ctx := context.Background()
ctx := testutil.Context(t, testutil.WaitMedium)
_, _ = dbgen.APIKey(t, db, database.APIKey{})
_ = dbgen.ParameterSchema(t, db, database.ParameterSchema{
DefaultSourceScheme: database.ParameterSourceSchemeNone,
@ -59,7 +59,18 @@ func TestTelemetry(t *testing.T) {
SharingLevel: database.AppSharingLevelOwner,
Health: database.WorkspaceAppHealthDisabled,
})
_ = dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{})
wsagent := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{
Subsystem: database.WorkspaceAgentSubsystemEnvbox,
})
// Update the workspace agent to have a valid subsystem.
err = db.UpdateWorkspaceAgentStartupByID(ctx, database.UpdateWorkspaceAgentStartupByIDParams{
ID: wsagent.ID,
Version: wsagent.Version,
ExpandedDirectory: wsagent.ExpandedDirectory,
Subsystem: database.WorkspaceAgentSubsystemEnvbox,
})
require.NoError(t, err)
_ = dbgen.WorkspaceBuild(t, db, database.WorkspaceBuild{
Transition: database.WorkspaceTransitionStart,
Reason: database.BuildReasonAutostart,
@ -88,6 +99,9 @@ func TestTelemetry(t *testing.T) {
require.Len(t, snapshot.WorkspaceBuilds, 1)
require.Len(t, snapshot.WorkspaceResources, 1)
require.Len(t, snapshot.WorkspaceAgentStats, 1)
wsa := snapshot.WorkspaceAgents[0]
require.Equal(t, string(database.WorkspaceAgentSubsystemEnvbox), wsa.Subsystem)
})
t.Run("HashedEmail", func(t *testing.T) {
t.Parallel()

View File

@ -220,6 +220,7 @@ func (api *API) postWorkspaceAgentStartup(rw http.ResponseWriter, r *http.Reques
ID: apiAgent.ID,
Version: req.Version,
ExpandedDirectory: req.ExpandedDirectory,
Subsystem: convertWorkspaceAgentSubsystem(req.Subsystem),
}); err != nil {
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
Message: "Error setting agent version",
@ -1129,6 +1130,7 @@ func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.Coordin
StartupScriptTimeoutSeconds: dbAgent.StartupScriptTimeoutSeconds,
ShutdownScript: dbAgent.ShutdownScript.String,
ShutdownScriptTimeoutSeconds: dbAgent.ShutdownScriptTimeoutSeconds,
Subsystem: codersdk.AgentSubsystem(dbAgent.Subsystem),
}
node := coordinator.Node(dbAgent.ID)
if node != nil {
@ -1983,3 +1985,12 @@ func convertWorkspaceAgentStartupLog(logEntry database.WorkspaceAgentStartupLog)
Level: codersdk.LogLevel(logEntry.Level),
}
}
func convertWorkspaceAgentSubsystem(ss codersdk.AgentSubsystem) database.WorkspaceAgentSubsystem {
switch ss {
case codersdk.AgentSubsystemEnvbox:
return database.WorkspaceAgentSubsystemEnvbox
default:
return database.WorkspaceAgentSubsystemNone
}
}

View File

@ -1326,3 +1326,85 @@ func TestWorkspaceAgent_Metadata(t *testing.T) {
err = agentClient.PostMetadata(ctx, "unknown", unknownKeyMetadata)
require.NoError(t, err)
}
func TestWorkspaceAgent_Startup(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
})
user := coderdtest.CreateFirstUser(t, client)
authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
agentClient := agentsdk.New(client.URL)
agentClient.SetSessionToken(authToken)
ctx := testutil.Context(t, testutil.WaitMedium)
const (
expectedVersion = "v1.2.3"
expectedDir = "/home/coder"
expectedSubsystem = codersdk.AgentSubsystemEnvbox
)
err := agentClient.PostStartup(ctx, agentsdk.PostStartupRequest{
Version: expectedVersion,
ExpandedDirectory: expectedDir,
Subsystem: expectedSubsystem,
})
require.NoError(t, err)
workspace, err = client.Workspace(ctx, workspace.ID)
require.NoError(t, err)
wsagent, err := client.WorkspaceAgent(ctx, workspace.LatestBuild.Resources[0].Agents[0].ID)
require.NoError(t, err)
require.Equal(t, expectedVersion, wsagent.Version)
require.Equal(t, expectedDir, wsagent.ExpandedDirectory)
require.Equal(t, expectedSubsystem, wsagent.Subsystem)
})
t.Run("InvalidSemver", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, &coderdtest.Options{
IncludeProvisionerDaemon: true,
})
user := coderdtest.CreateFirstUser(t, client)
authToken := uuid.NewString()
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
Parse: echo.ParseComplete,
ProvisionPlan: echo.ProvisionComplete,
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
})
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
agentClient := agentsdk.New(client.URL)
agentClient.SetSessionToken(authToken)
ctx := testutil.Context(t, testutil.WaitMedium)
err := agentClient.PostStartup(ctx, agentsdk.PostStartupRequest{
Version: "1.2.3",
})
require.Error(t, err)
cerr, ok := codersdk.AsError(err)
require.True(t, ok)
require.Equal(t, http.StatusBadRequest, cerr.StatusCode())
})
}

View File

@ -544,8 +544,9 @@ func (c *Client) PostLifecycle(ctx context.Context, req PostLifecycleRequest) er
}
type PostStartupRequest struct {
Version string `json:"version"`
ExpandedDirectory string `json:"expanded_directory"`
Version string `json:"version"`
ExpandedDirectory string `json:"expanded_directory"`
Subsystem codersdk.AgentSubsystem `json:"subsystem"`
}
func (c *Client) PostStartup(ctx context.Context, req PostStartupRequest) error {

View File

@ -130,9 +130,10 @@ type WorkspaceAgent struct {
// LoginBeforeReady if true, the agent will delay logins until it is ready (e.g. executing startup script has ended).
LoginBeforeReady bool `json:"login_before_ready"`
// StartupScriptTimeoutSeconds 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.
StartupScriptTimeoutSeconds int32 `json:"startup_script_timeout_seconds"`
ShutdownScript string `json:"shutdown_script,omitempty"`
ShutdownScriptTimeoutSeconds int32 `json:"shutdown_script_timeout_seconds"`
StartupScriptTimeoutSeconds int32 `json:"startup_script_timeout_seconds"`
ShutdownScript string `json:"shutdown_script,omitempty"`
ShutdownScriptTimeoutSeconds int32 `json:"shutdown_script_timeout_seconds"`
Subsystem AgentSubsystem `json:"subsystem"`
}
type DERPRegion struct {
@ -553,3 +554,9 @@ type WorkspaceAgentStartupLog struct {
Output string `json:"output"`
Level LogLevel `json:"level"`
}
type AgentSubsystem string
const (
AgentSubsystemEnvbox AgentSubsystem = "envbox"
)

View File

@ -474,6 +474,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent} \
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"

View File

@ -111,6 +111,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"
@ -263,6 +264,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"
@ -556,6 +558,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/res
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"
@ -638,6 +641,7 @@ Status Code **200**
| `»» startup_script` | string | false | | |
| `»» startup_script_timeout_seconds` | integer | false | | »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. |
| `»» status` | [codersdk.WorkspaceAgentStatus](schemas.md#codersdkworkspaceagentstatus) | false | | |
| `»» subsystem` | [codersdk.AgentSubsystem](schemas.md#codersdkagentsubsystem) | false | | |
| `»» troubleshooting_url` | string | false | | |
| `»» updated_at` | string(date-time) | false | | |
| `»» version` | string | false | | |
@ -679,6 +683,7 @@ Status Code **200**
| `status` | `connected` |
| `status` | `disconnected` |
| `status` | `timeout` |
| `subsystem` | `envbox` |
| `workspace_transition` | `start` |
| `workspace_transition` | `stop` |
| `workspace_transition` | `delete` |
@ -794,6 +799,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"
@ -951,6 +957,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"
@ -1067,6 +1074,7 @@ Status Code **200**
| `»»» startup_script` | string | false | | |
| `»»» startup_script_timeout_seconds` | integer | false | | »»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. |
| `»»» status` | [codersdk.WorkspaceAgentStatus](schemas.md#codersdkworkspaceagentstatus) | false | | |
| `»»» subsystem` | [codersdk.AgentSubsystem](schemas.md#codersdkagentsubsystem) | false | | |
| `»»» troubleshooting_url` | string | false | | |
| `»»» updated_at` | string(date-time) | false | | |
| `»»» version` | string | false | | |
@ -1128,6 +1136,7 @@ Status Code **200**
| `status` | `connected` |
| `status` | `disconnected` |
| `status` | `timeout` |
| `subsystem` | `envbox` |
| `workspace_transition` | `start` |
| `workspace_transition` | `stop` |
| `workspace_transition` | `delete` |
@ -1286,6 +1295,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"

View File

@ -327,16 +327,18 @@
```json
{
"expanded_directory": "string",
"subsystem": "envbox",
"version": "string"
}
```
### Properties
| Name | Type | Required | Restrictions | Description |
| -------------------- | ------ | -------- | ------------ | ----------- |
| `expanded_directory` | string | false | | |
| `version` | string | false | | |
| Name | Type | Required | Restrictions | Description |
| -------------------- | -------------------------------------------------- | -------- | ------------ | ----------- |
| `expanded_directory` | string | false | | |
| `subsystem` | [codersdk.AgentSubsystem](#codersdkagentsubsystem) | false | | |
| `version` | string | false | | |
## agentsdk.StartupLog
@ -783,6 +785,20 @@
| --------- | ------ | -------- | ------------ | ----------- |
| `license` | string | true | | |
## codersdk.AgentSubsystem
```json
"envbox"
```
### Properties
#### Enumerated Values
| Value |
| -------- |
| `envbox` |
## codersdk.AppHostResponse
```json
@ -4698,6 +4714,7 @@ Parameter represents a set value for the scope.
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"
@ -4828,6 +4845,7 @@ Parameter represents a set value for the scope.
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"
@ -4865,6 +4883,7 @@ Parameter represents a set value for the scope.
| `startup_script` | string | false | | |
| `startup_script_timeout_seconds` | integer | false | | 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. |
| `status` | [codersdk.WorkspaceAgentStatus](#codersdkworkspaceagentstatus) | false | | |
| `subsystem` | [codersdk.AgentSubsystem](#codersdkagentsubsystem) | false | | |
| `troubleshooting_url` | string | false | | |
| `updated_at` | string | false | | |
| `version` | string | false | | |
@ -5219,6 +5238,7 @@ Parameter represents a set value for the scope.
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"
@ -5500,6 +5520,7 @@ Parameter represents a set value for the scope.
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"
@ -5698,6 +5719,7 @@ Parameter represents a set value for the scope.
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"

View File

@ -1680,6 +1680,7 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/d
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"
@ -1762,6 +1763,7 @@ Status Code **200**
| `»» startup_script` | string | false | | |
| `»» startup_script_timeout_seconds` | integer | false | | »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. |
| `»» status` | [codersdk.WorkspaceAgentStatus](schemas.md#codersdkworkspaceagentstatus) | false | | |
| `»» subsystem` | [codersdk.AgentSubsystem](schemas.md#codersdkagentsubsystem) | false | | |
| `»» troubleshooting_url` | string | false | | |
| `»» updated_at` | string(date-time) | false | | |
| `»» version` | string | false | | |
@ -1803,6 +1805,7 @@ Status Code **200**
| `status` | `connected` |
| `status` | `disconnected` |
| `status` | `timeout` |
| `subsystem` | `envbox` |
| `workspace_transition` | `start` |
| `workspace_transition` | `stop` |
| `workspace_transition` | `delete` |
@ -2109,6 +2112,7 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/r
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"
@ -2191,6 +2195,7 @@ Status Code **200**
| `»» startup_script` | string | false | | |
| `»» startup_script_timeout_seconds` | integer | false | | »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. |
| `»» status` | [codersdk.WorkspaceAgentStatus](schemas.md#codersdkworkspaceagentstatus) | false | | |
| `»» subsystem` | [codersdk.AgentSubsystem](schemas.md#codersdkagentsubsystem) | false | | |
| `»» troubleshooting_url` | string | false | | |
| `»» updated_at` | string(date-time) | false | | |
| `»» version` | string | false | | |
@ -2232,6 +2237,7 @@ Status Code **200**
| `status` | `connected` |
| `status` | `disconnected` |
| `status` | `timeout` |
| `subsystem` | `envbox` |
| `workspace_transition` | `start` |
| `workspace_transition` | `stop` |
| `workspace_transition` | `delete` |

View File

@ -144,6 +144,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"
@ -317,6 +318,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"
@ -509,6 +511,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"
@ -683,6 +686,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
"subsystem": "envbox",
"troubleshooting_url": "string",
"updated_at": "2019-08-24T14:15:22Z",
"version": "string"

View File

@ -1147,6 +1147,7 @@ export interface WorkspaceAgent {
readonly startup_script_timeout_seconds: number
readonly shutdown_script?: string
readonly shutdown_script_timeout_seconds: number
readonly subsystem: AgentSubsystem
}
// From codersdk/workspaceagentconn.go
@ -1341,6 +1342,10 @@ export interface WorkspacesResponse {
export type APIKeyScope = "all" | "application_connect"
export const APIKeyScopes: APIKeyScope[] = ["all", "application_connect"]
// From codersdk/workspaceagents.go
export type AgentSubsystem = "envbox"
export const AgentSubsystems: AgentSubsystem[] = ["envbox"]
// From codersdk/audit.go
export type AuditAction =
| "create"

View File

@ -501,6 +501,7 @@ export const MockWorkspaceAgent: TypesGen.WorkspaceAgent = {
startup_logs_overflowed: false,
startup_script_timeout_seconds: 120,
shutdown_script_timeout_seconds: 120,
subsystem: "envbox",
}
export const MockWorkspaceAgentDisconnected: TypesGen.WorkspaceAgent = {