feat(agent): Add shutdown lifecycle states and shutdown_script support (#6139)

* feat(api): Add agent shutdown lifecycle states

* feat(agent): Add shutdown_script support

* feat(agent): Add shutdown_script timeout

* feat(site): Support new agent lifecycle states

---

Co-authored-by: Marcin Tojek <marcin@coder.com>
This commit is contained in:
Mathias Fredriksson
2023-03-06 21:34:00 +02:00
committed by GitHub
parent 02100c64b5
commit 22e3ff96be
41 changed files with 1439 additions and 635 deletions

24
coderd/apidoc/docs.go generated
View File

@ -5198,6 +5198,12 @@ const docTemplate = `{
"motd_file": {
"type": "string"
},
"shutdown_script": {
"type": "string"
},
"shutdown_script_timeout": {
"type": "integer"
},
"startup_script": {
"type": "string"
},
@ -8425,6 +8431,12 @@ const docTemplate = `{
"type": "string",
"format": "uuid"
},
"shutdown_script": {
"type": "string"
},
"shutdown_script_timeout_seconds": {
"type": "integer"
},
"startup_script": {
"type": "string"
},
@ -8462,14 +8474,22 @@ const docTemplate = `{
"starting",
"start_timeout",
"start_error",
"ready"
"ready",
"shutting_down",
"shutdown_timeout",
"shutdown_error",
"off"
],
"x-enum-varnames": [
"WorkspaceAgentLifecycleCreated",
"WorkspaceAgentLifecycleStarting",
"WorkspaceAgentLifecycleStartTimeout",
"WorkspaceAgentLifecycleStartError",
"WorkspaceAgentLifecycleReady"
"WorkspaceAgentLifecycleReady",
"WorkspaceAgentLifecycleShuttingDown",
"WorkspaceAgentLifecycleShutdownTimeout",
"WorkspaceAgentLifecycleShutdownError",
"WorkspaceAgentLifecycleOff"
]
},
"codersdk.WorkspaceAgentListeningPort": {

View File

@ -4587,6 +4587,12 @@
"motd_file": {
"type": "string"
},
"shutdown_script": {
"type": "string"
},
"shutdown_script_timeout": {
"type": "integer"
},
"startup_script": {
"type": "string"
},
@ -7592,6 +7598,12 @@
"type": "string",
"format": "uuid"
},
"shutdown_script": {
"type": "string"
},
"shutdown_script_timeout_seconds": {
"type": "integer"
},
"startup_script": {
"type": "string"
},
@ -7624,13 +7636,27 @@
},
"codersdk.WorkspaceAgentLifecycle": {
"type": "string",
"enum": ["created", "starting", "start_timeout", "start_error", "ready"],
"enum": [
"created",
"starting",
"start_timeout",
"start_error",
"ready",
"shutting_down",
"shutdown_timeout",
"shutdown_error",
"off"
],
"x-enum-varnames": [
"WorkspaceAgentLifecycleCreated",
"WorkspaceAgentLifecycleStarting",
"WorkspaceAgentLifecycleStartTimeout",
"WorkspaceAgentLifecycleStartError",
"WorkspaceAgentLifecycleReady"
"WorkspaceAgentLifecycleReady",
"WorkspaceAgentLifecycleShuttingDown",
"WorkspaceAgentLifecycleShutdownTimeout",
"WorkspaceAgentLifecycleShutdownError",
"WorkspaceAgentLifecycleOff"
]
},
"codersdk.WorkspaceAgentListeningPort": {

View File

@ -2856,6 +2856,7 @@ func (q *fakeQuerier) InsertWorkspaceAgent(_ context.Context, arg database.Inser
TroubleshootingURL: arg.TroubleshootingURL,
MOTDFile: arg.MOTDFile,
LifecycleState: database.WorkspaceAgentLifecycleStateCreated,
ShutdownScript: arg.ShutdownScript,
}
q.workspaceAgents = append(q.workspaceAgents, agent)

View File

@ -107,7 +107,11 @@ CREATE TYPE workspace_agent_lifecycle_state AS ENUM (
'starting',
'start_timeout',
'start_error',
'ready'
'ready',
'shutting_down',
'shutdown_timeout',
'shutdown_error',
'off'
);
CREATE TYPE workspace_app_health AS ENUM (
@ -509,7 +513,9 @@ CREATE TABLE workspace_agents (
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,
expanded_directory character varying(4096) DEFAULT ''::character varying NOT NULL
expanded_directory character varying(4096) DEFAULT ''::character varying NOT NULL,
shutdown_script character varying(65534),
shutdown_script_timeout_seconds integer DEFAULT 0 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.';
@ -528,6 +534,10 @@ COMMENT ON COLUMN workspace_agents.startup_script_timeout_seconds IS 'The number
COMMENT ON COLUMN workspace_agents.expanded_directory IS 'The resolved path of a user-specified directory. e.g. ~/coder -> /home/coder/coder';
COMMENT ON COLUMN workspace_agents.shutdown_script IS 'Script that is executed before the agent is stopped.';
COMMENT ON COLUMN workspace_agents.shutdown_script_timeout_seconds IS 'The number of seconds to wait for the shutdown script to complete. If the script does not complete within this time, the agent lifecycle will be marked as shutdown_timeout.';
CREATE TABLE workspace_apps (
id uuid NOT NULL,
created_at timestamp with time zone NOT NULL,

View File

@ -0,0 +1,12 @@
ALTER TABLE workspace_agents DROP COLUMN shutdown_script;
ALTER TABLE workspace_agents DROP COLUMN shutdown_script_timeout_seconds;
-- We can't drop values from enums, so we have to create a new one and convert the data.
UPDATE workspace_agents SET lifecycle_state = 'ready' WHERE lifecycle_state IN ('shutting_down', 'shutdown_timeout', 'shutdown_error', 'off');
ALTER TYPE workspace_agent_lifecycle_state RENAME TO workspace_agent_lifecycle_state_old;
CREATE TYPE workspace_agent_lifecycle_state AS ENUM ('created', 'starting', 'start_timeout', 'start_error', 'ready');
ALTER TABLE workspace_agents ALTER COLUMN lifecycle_state DROP DEFAULT;
ALTER TABLE workspace_agents ALTER COLUMN lifecycle_state TYPE workspace_agent_lifecycle_state USING lifecycle_state::text::workspace_agent_lifecycle_state;
ALTER TABLE workspace_agents ALTER COLUMN lifecycle_state SET DEFAULT 'created';
DROP TYPE workspace_agent_lifecycle_state_old;

View File

@ -0,0 +1,14 @@
ALTER TABLE workspace_agents ADD COLUMN shutdown_script varchar(65534);
COMMENT ON COLUMN workspace_agents.shutdown_script IS 'Script that is executed before the agent is stopped.';
-- Disable shutdown script timeouts by default.
ALTER TABLE workspace_agents ADD COLUMN shutdown_script_timeout_seconds int4 NOT NULL DEFAULT 0;
COMMENT ON COLUMN workspace_agents.shutdown_script_timeout_seconds IS 'The number of seconds to wait for the shutdown script to complete. If the script does not complete within this time, the agent lifecycle will be marked as shutdown_timeout.';
-- Add enum fields
ALTER TYPE workspace_agent_lifecycle_state ADD VALUE 'shutting_down';
ALTER TYPE workspace_agent_lifecycle_state ADD VALUE 'shutdown_timeout';
ALTER TYPE workspace_agent_lifecycle_state ADD VALUE 'shutdown_error';
ALTER TYPE workspace_agent_lifecycle_state ADD VALUE 'off';

View File

@ -0,0 +1,2 @@
-- Set a non-default lifecycle_state.
UPDATE workspace_agents SET lifecycle_state = 'ready' WHERE id = '7a1ce5f8-8d00-431c-ad1b-97a846512804';

View File

@ -0,0 +1,2 @@
-- Set lifecycle_state to enum value not available in previous migration.
UPDATE workspace_agents SET lifecycle_state = 'off' WHERE id = '7a1ce5f8-8d00-431c-ad1b-97a846512804';

View File

@ -1014,11 +1014,15 @@ func AllUserStatusValues() []UserStatus {
type WorkspaceAgentLifecycleState string
const (
WorkspaceAgentLifecycleStateCreated WorkspaceAgentLifecycleState = "created"
WorkspaceAgentLifecycleStateStarting WorkspaceAgentLifecycleState = "starting"
WorkspaceAgentLifecycleStateStartTimeout WorkspaceAgentLifecycleState = "start_timeout"
WorkspaceAgentLifecycleStateStartError WorkspaceAgentLifecycleState = "start_error"
WorkspaceAgentLifecycleStateReady WorkspaceAgentLifecycleState = "ready"
WorkspaceAgentLifecycleStateCreated WorkspaceAgentLifecycleState = "created"
WorkspaceAgentLifecycleStateStarting WorkspaceAgentLifecycleState = "starting"
WorkspaceAgentLifecycleStateStartTimeout WorkspaceAgentLifecycleState = "start_timeout"
WorkspaceAgentLifecycleStateStartError WorkspaceAgentLifecycleState = "start_error"
WorkspaceAgentLifecycleStateReady WorkspaceAgentLifecycleState = "ready"
WorkspaceAgentLifecycleStateShuttingDown WorkspaceAgentLifecycleState = "shutting_down"
WorkspaceAgentLifecycleStateShutdownTimeout WorkspaceAgentLifecycleState = "shutdown_timeout"
WorkspaceAgentLifecycleStateShutdownError WorkspaceAgentLifecycleState = "shutdown_error"
WorkspaceAgentLifecycleStateOff WorkspaceAgentLifecycleState = "off"
)
func (e *WorkspaceAgentLifecycleState) Scan(src interface{}) error {
@ -1062,7 +1066,11 @@ func (e WorkspaceAgentLifecycleState) Valid() bool {
WorkspaceAgentLifecycleStateStarting,
WorkspaceAgentLifecycleStateStartTimeout,
WorkspaceAgentLifecycleStateStartError,
WorkspaceAgentLifecycleStateReady:
WorkspaceAgentLifecycleStateReady,
WorkspaceAgentLifecycleStateShuttingDown,
WorkspaceAgentLifecycleStateShutdownTimeout,
WorkspaceAgentLifecycleStateShutdownError,
WorkspaceAgentLifecycleStateOff:
return true
}
return false
@ -1075,6 +1083,10 @@ func AllWorkspaceAgentLifecycleStateValues() []WorkspaceAgentLifecycleState {
WorkspaceAgentLifecycleStateStartTimeout,
WorkspaceAgentLifecycleStateStartError,
WorkspaceAgentLifecycleStateReady,
WorkspaceAgentLifecycleStateShuttingDown,
WorkspaceAgentLifecycleStateShutdownTimeout,
WorkspaceAgentLifecycleStateShutdownError,
WorkspaceAgentLifecycleStateOff,
}
}
@ -1547,6 +1559,10 @@ type WorkspaceAgent struct {
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"`
// Script that is executed before the agent is stopped.
ShutdownScript sql.NullString `db:"shutdown_script" json:"shutdown_script"`
// The number of seconds to wait for the shutdown script to complete. If the script does not complete within this time, the agent lifecycle will be marked as shutdown_timeout.
ShutdownScriptTimeoutSeconds int32 `db:"shutdown_script_timeout_seconds" json:"shutdown_script_timeout_seconds"`
}
type WorkspaceAgentStat struct {

View File

@ -4937,7 +4937,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, expanded_directory
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
FROM
workspace_agents
WHERE
@ -4976,13 +4976,15 @@ func (q *sqlQuerier) GetWorkspaceAgentByAuthToken(ctx context.Context, authToken
&i.LoginBeforeReady,
&i.StartupScriptTimeoutSeconds,
&i.ExpandedDirectory,
&i.ShutdownScript,
&i.ShutdownScriptTimeoutSeconds,
)
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
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
FROM
workspace_agents
WHERE
@ -5019,13 +5021,15 @@ func (q *sqlQuerier) GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (W
&i.LoginBeforeReady,
&i.StartupScriptTimeoutSeconds,
&i.ExpandedDirectory,
&i.ShutdownScript,
&i.ShutdownScriptTimeoutSeconds,
)
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
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
FROM
workspace_agents
WHERE
@ -5064,13 +5068,15 @@ func (q *sqlQuerier) GetWorkspaceAgentByInstanceID(ctx context.Context, authInst
&i.LoginBeforeReady,
&i.StartupScriptTimeoutSeconds,
&i.ExpandedDirectory,
&i.ShutdownScript,
&i.ShutdownScriptTimeoutSeconds,
)
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, expanded_directory
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
FROM
workspace_agents
WHERE
@ -5113,6 +5119,8 @@ func (q *sqlQuerier) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids []
&i.LoginBeforeReady,
&i.StartupScriptTimeoutSeconds,
&i.ExpandedDirectory,
&i.ShutdownScript,
&i.ShutdownScriptTimeoutSeconds,
); err != nil {
return nil, err
}
@ -5128,7 +5136,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 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 FROM workspace_agents WHERE created_at > $1
`
func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceAgent, error) {
@ -5167,6 +5175,8 @@ func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, created
&i.LoginBeforeReady,
&i.StartupScriptTimeoutSeconds,
&i.ExpandedDirectory,
&i.ShutdownScript,
&i.ShutdownScriptTimeoutSeconds,
); err != nil {
return nil, err
}
@ -5202,32 +5212,36 @@ INSERT INTO
troubleshooting_url,
motd_file,
login_before_ready,
startup_script_timeout_seconds
startup_script_timeout_seconds,
shutdown_script,
shutdown_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, expanded_directory
($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
`
type InsertWorkspaceAgentParams struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
Name string `db:"name" json:"name"`
ResourceID uuid.UUID `db:"resource_id" json:"resource_id"`
AuthToken uuid.UUID `db:"auth_token" json:"auth_token"`
AuthInstanceID sql.NullString `db:"auth_instance_id" json:"auth_instance_id"`
Architecture string `db:"architecture" json:"architecture"`
EnvironmentVariables pqtype.NullRawMessage `db:"environment_variables" json:"environment_variables"`
OperatingSystem string `db:"operating_system" json:"operating_system"`
StartupScript sql.NullString `db:"startup_script" json:"startup_script"`
Directory string `db:"directory" json:"directory"`
InstanceMetadata pqtype.NullRawMessage `db:"instance_metadata" json:"instance_metadata"`
ResourceMetadata pqtype.NullRawMessage `db:"resource_metadata" json:"resource_metadata"`
ConnectionTimeoutSeconds int32 `db:"connection_timeout_seconds" json:"connection_timeout_seconds"`
TroubleshootingURL string `db:"troubleshooting_url" json:"troubleshooting_url"`
MOTDFile string `db:"motd_file" json:"motd_file"`
LoginBeforeReady bool `db:"login_before_ready" json:"login_before_ready"`
StartupScriptTimeoutSeconds int32 `db:"startup_script_timeout_seconds" json:"startup_script_timeout_seconds"`
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
Name string `db:"name" json:"name"`
ResourceID uuid.UUID `db:"resource_id" json:"resource_id"`
AuthToken uuid.UUID `db:"auth_token" json:"auth_token"`
AuthInstanceID sql.NullString `db:"auth_instance_id" json:"auth_instance_id"`
Architecture string `db:"architecture" json:"architecture"`
EnvironmentVariables pqtype.NullRawMessage `db:"environment_variables" json:"environment_variables"`
OperatingSystem string `db:"operating_system" json:"operating_system"`
StartupScript sql.NullString `db:"startup_script" json:"startup_script"`
Directory string `db:"directory" json:"directory"`
InstanceMetadata pqtype.NullRawMessage `db:"instance_metadata" json:"instance_metadata"`
ResourceMetadata pqtype.NullRawMessage `db:"resource_metadata" json:"resource_metadata"`
ConnectionTimeoutSeconds int32 `db:"connection_timeout_seconds" json:"connection_timeout_seconds"`
TroubleshootingURL string `db:"troubleshooting_url" json:"troubleshooting_url"`
MOTDFile string `db:"motd_file" json:"motd_file"`
LoginBeforeReady bool `db:"login_before_ready" json:"login_before_ready"`
StartupScriptTimeoutSeconds int32 `db:"startup_script_timeout_seconds" json:"startup_script_timeout_seconds"`
ShutdownScript sql.NullString `db:"shutdown_script" json:"shutdown_script"`
ShutdownScriptTimeoutSeconds int32 `db:"shutdown_script_timeout_seconds" json:"shutdown_script_timeout_seconds"`
}
func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspaceAgentParams) (WorkspaceAgent, error) {
@ -5251,6 +5265,8 @@ func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspa
arg.MOTDFile,
arg.LoginBeforeReady,
arg.StartupScriptTimeoutSeconds,
arg.ShutdownScript,
arg.ShutdownScriptTimeoutSeconds,
)
var i WorkspaceAgent
err := row.Scan(
@ -5280,6 +5296,8 @@ func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspa
&i.LoginBeforeReady,
&i.StartupScriptTimeoutSeconds,
&i.ExpandedDirectory,
&i.ShutdownScript,
&i.ShutdownScriptTimeoutSeconds,
)
return i, err
}

View File

@ -58,10 +58,12 @@ INSERT INTO
troubleshooting_url,
motd_file,
login_before_ready,
startup_script_timeout_seconds
startup_script_timeout_seconds,
shutdown_script,
shutdown_script_timeout_seconds
)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) RETURNING *;
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) RETURNING *;
-- name: UpdateWorkspaceAgentConnectionByID :exec
UPDATE

View File

@ -1144,6 +1144,11 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
MOTDFile: prAgent.GetMotdFile(),
LoginBeforeReady: prAgent.GetLoginBeforeReady(),
StartupScriptTimeoutSeconds: prAgent.GetStartupScriptTimeoutSeconds(),
ShutdownScript: sql.NullString{
String: prAgent.ShutdownScript,
Valid: prAgent.ShutdownScript != "",
},
ShutdownScriptTimeoutSeconds: prAgent.GetShutdownScriptTimeoutSeconds(),
})
if err != nil {
return xerrors.Errorf("insert agent: %w", err)

View File

@ -957,6 +957,7 @@ func TestInsertWorkspaceResource(t *testing.T) {
Apps: []*sdkproto.App{{
Slug: "a",
}},
ShutdownScript: "shutdown",
}},
})
require.NoError(t, err)
@ -971,6 +972,7 @@ func TestInsertWorkspaceResource(t *testing.T) {
require.Equal(t, "amd64", agent.Architecture)
require.Equal(t, "linux", agent.OperatingSystem)
require.Equal(t, "value", agent.StartupScript.String)
require.Equal(t, "shutdown", agent.ShutdownScript.String)
want, err := json.Marshal(map[string]string{
"something": "test",
})

View File

@ -550,6 +550,7 @@ func ConvertWorkspaceAgent(agent database.WorkspaceAgent) WorkspaceAgent {
StartupScript: agent.StartupScript.Valid,
Directory: agent.Directory != "",
ConnectionTimeoutSeconds: agent.ConnectionTimeoutSeconds,
ShutdownScript: agent.ShutdownScript.Valid,
}
if agent.FirstConnectedAt.Valid {
snapAgent.FirstConnectedAt = &agent.FirstConnectedAt.Time
@ -750,6 +751,7 @@ type WorkspaceAgent struct {
LastConnectedAt *time.Time `json:"last_connected_at"`
DisconnectedAt *time.Time `json:"disconnected_at"`
ConnectionTimeoutSeconds int32 `json:"connection_timeout_seconds"`
ShutdownScript bool `json:"shutdown_script"`
}
type WorkspaceApp struct {

View File

@ -144,15 +144,17 @@ func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request)
}
httpapi.Write(ctx, rw, http.StatusOK, agentsdk.Metadata{
Apps: convertApps(dbApps),
DERPMap: api.DERPMap,
GitAuthConfigs: len(api.GitAuthConfigs),
EnvironmentVariables: apiAgent.EnvironmentVariables,
StartupScript: apiAgent.StartupScript,
Directory: apiAgent.Directory,
VSCodePortProxyURI: vscodeProxyURI,
MOTDFile: workspaceAgent.MOTDFile,
StartupScriptTimeout: time.Duration(apiAgent.StartupScriptTimeoutSeconds) * time.Second,
Apps: convertApps(dbApps),
DERPMap: api.DERPMap,
GitAuthConfigs: len(api.GitAuthConfigs),
EnvironmentVariables: apiAgent.EnvironmentVariables,
StartupScript: apiAgent.StartupScript,
Directory: apiAgent.Directory,
VSCodePortProxyURI: vscodeProxyURI,
MOTDFile: workspaceAgent.MOTDFile,
StartupScriptTimeout: time.Duration(apiAgent.StartupScriptTimeoutSeconds) * time.Second,
ShutdownScript: apiAgent.ShutdownScript,
ShutdownScriptTimeout: time.Duration(apiAgent.ShutdownScriptTimeoutSeconds) * time.Second,
})
}
@ -803,25 +805,27 @@ func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.Coordin
troubleshootingURL = dbAgent.TroubleshootingURL
}
workspaceAgent := codersdk.WorkspaceAgent{
ID: dbAgent.ID,
CreatedAt: dbAgent.CreatedAt,
UpdatedAt: dbAgent.UpdatedAt,
ResourceID: dbAgent.ResourceID,
InstanceID: dbAgent.AuthInstanceID.String,
Name: dbAgent.Name,
Architecture: dbAgent.Architecture,
OperatingSystem: dbAgent.OperatingSystem,
StartupScript: dbAgent.StartupScript.String,
Version: dbAgent.Version,
EnvironmentVariables: envs,
Directory: dbAgent.Directory,
ExpandedDirectory: dbAgent.ExpandedDirectory,
Apps: apps,
ConnectionTimeoutSeconds: dbAgent.ConnectionTimeoutSeconds,
TroubleshootingURL: troubleshootingURL,
LifecycleState: codersdk.WorkspaceAgentLifecycle(dbAgent.LifecycleState),
LoginBeforeReady: dbAgent.LoginBeforeReady,
StartupScriptTimeoutSeconds: dbAgent.StartupScriptTimeoutSeconds,
ID: dbAgent.ID,
CreatedAt: dbAgent.CreatedAt,
UpdatedAt: dbAgent.UpdatedAt,
ResourceID: dbAgent.ResourceID,
InstanceID: dbAgent.AuthInstanceID.String,
Name: dbAgent.Name,
Architecture: dbAgent.Architecture,
OperatingSystem: dbAgent.OperatingSystem,
StartupScript: dbAgent.StartupScript.String,
Version: dbAgent.Version,
EnvironmentVariables: envs,
Directory: dbAgent.Directory,
ExpandedDirectory: dbAgent.ExpandedDirectory,
Apps: apps,
ConnectionTimeoutSeconds: dbAgent.ConnectionTimeoutSeconds,
TroubleshootingURL: troubleshootingURL,
LifecycleState: codersdk.WorkspaceAgentLifecycle(dbAgent.LifecycleState),
LoginBeforeReady: dbAgent.LoginBeforeReady,
StartupScriptTimeoutSeconds: dbAgent.StartupScriptTimeoutSeconds,
ShutdownScript: dbAgent.ShutdownScript.String,
ShutdownScriptTimeoutSeconds: dbAgent.ShutdownScriptTimeoutSeconds,
}
node := coordinator.Node(dbAgent.ID)
if node != nil {

View File

@ -1256,6 +1256,10 @@ func TestWorkspaceAgent_LifecycleState(t *testing.T) {
{codersdk.WorkspaceAgentLifecycleStartTimeout, false},
{codersdk.WorkspaceAgentLifecycleStartError, false},
{codersdk.WorkspaceAgentLifecycleReady, false},
{codersdk.WorkspaceAgentLifecycleShuttingDown, false},
{codersdk.WorkspaceAgentLifecycleShutdownTimeout, false},
{codersdk.WorkspaceAgentLifecycleShutdownError, false},
{codersdk.WorkspaceAgentLifecycleOff, false},
{codersdk.WorkspaceAgentLifecycle("nonexistent_state"), true},
{codersdk.WorkspaceAgentLifecycle(""), true},
}