feat: add support for coder_script (#9584)

* Add basic migrations

* Improve schema

* Refactor agent scripts into it's own package

* Support legacy start and stop script format

* Pipe the scripts!

* Finish the piping

* Fix context usage

* It works!

* Fix sql query

* Fix SQL query

* Rename `LogSourceID` -> `SourceID`

* Fix the FE

* fmt

* Rename migrations

* Fix log tests

* Fix lint err

* Fix gen

* Fix story type

* Rename source to script

* Fix schema jank

* Uncomment test

* Rename proto to TimeoutSeconds

* Fix comments

* Fix comments

* Fix legacy endpoint without specified log_source

* Fix non-blocking by default in agent

* Fix resources tests

* Fix dbfake

* Fix resources

* Fix linting I think

* Add fixtures

* fmt

* Fix startup script behavior

* Fix comments

* Fix context

* Fix cancel

* Fix SQL tests

* Fix e2e tests

* Interrupt on Windows

* Fix agent leaking script process

* Fix migrations

* Fix stories

* Fix duplicate logs appearing

* Gen

* Fix log location

* Fix tests

* Fix tests

* Fix log output

* Show display name in output

* Fix print

* Return timeout on start context

* Gen

* Fix fixture

* Fix the agent status

* Fix startup timeout msg

* Fix command using shared context

* Fix timeout draining

* Change signal type

* Add deterministic colors to startup script logs

---------

Co-authored-by: Muhammad Atif Ali <atif@coder.com>
This commit is contained in:
Kyle Carberry
2023-09-25 16:47:17 -05:00
committed by GitHub
parent dac1375880
commit 1262eef2c0
61 changed files with 3820 additions and 2117 deletions

View File

@ -1298,76 +1298,6 @@ func AllWorkspaceAgentLifecycleStateValues() []WorkspaceAgentLifecycleState {
}
}
type WorkspaceAgentLogSource string
const (
WorkspaceAgentLogSourceStartupScript WorkspaceAgentLogSource = "startup_script"
WorkspaceAgentLogSourceShutdownScript WorkspaceAgentLogSource = "shutdown_script"
WorkspaceAgentLogSourceKubernetesLogs WorkspaceAgentLogSource = "kubernetes_logs"
WorkspaceAgentLogSourceEnvbox WorkspaceAgentLogSource = "envbox"
WorkspaceAgentLogSourceEnvbuilder WorkspaceAgentLogSource = "envbuilder"
WorkspaceAgentLogSourceExternal WorkspaceAgentLogSource = "external"
)
func (e *WorkspaceAgentLogSource) Scan(src interface{}) error {
switch s := src.(type) {
case []byte:
*e = WorkspaceAgentLogSource(s)
case string:
*e = WorkspaceAgentLogSource(s)
default:
return fmt.Errorf("unsupported scan type for WorkspaceAgentLogSource: %T", src)
}
return nil
}
type NullWorkspaceAgentLogSource struct {
WorkspaceAgentLogSource WorkspaceAgentLogSource `json:"workspace_agent_log_source"`
Valid bool `json:"valid"` // Valid is true if WorkspaceAgentLogSource is not NULL
}
// Scan implements the Scanner interface.
func (ns *NullWorkspaceAgentLogSource) Scan(value interface{}) error {
if value == nil {
ns.WorkspaceAgentLogSource, ns.Valid = "", false
return nil
}
ns.Valid = true
return ns.WorkspaceAgentLogSource.Scan(value)
}
// Value implements the driver Valuer interface.
func (ns NullWorkspaceAgentLogSource) Value() (driver.Value, error) {
if !ns.Valid {
return nil, nil
}
return string(ns.WorkspaceAgentLogSource), nil
}
func (e WorkspaceAgentLogSource) Valid() bool {
switch e {
case WorkspaceAgentLogSourceStartupScript,
WorkspaceAgentLogSourceShutdownScript,
WorkspaceAgentLogSourceKubernetesLogs,
WorkspaceAgentLogSourceEnvbox,
WorkspaceAgentLogSourceEnvbuilder,
WorkspaceAgentLogSourceExternal:
return true
}
return false
}
func AllWorkspaceAgentLogSourceValues() []WorkspaceAgentLogSource {
return []WorkspaceAgentLogSource{
WorkspaceAgentLogSourceStartupScript,
WorkspaceAgentLogSourceShutdownScript,
WorkspaceAgentLogSourceKubernetesLogs,
WorkspaceAgentLogSourceEnvbox,
WorkspaceAgentLogSourceEnvbuilder,
WorkspaceAgentLogSourceExternal,
}
}
type WorkspaceAgentSubsystem string
const (
@ -2018,7 +1948,6 @@ type WorkspaceAgent struct {
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"`
InstanceMetadata pqtype.NullRawMessage `db:"instance_metadata" json:"instance_metadata"`
ResourceMetadata pqtype.NullRawMessage `db:"resource_metadata" json:"resource_metadata"`
Directory string `db:"directory" json:"directory"`
@ -2033,20 +1962,12 @@ type WorkspaceAgent struct {
MOTDFile string `db:"motd_file" json:"motd_file"`
// The current lifecycle state reported by the workspace agent.
LifecycleState WorkspaceAgentLifecycleState `db:"lifecycle_state" json:"lifecycle_state"`
// 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"`
// 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"`
// Total length of startup logs
LogsLength int32 `db:"logs_length" json:"logs_length"`
// Whether the startup logs overflowed in length
LogsOverflowed bool `db:"logs_overflowed" json:"logs_overflowed"`
// When startup script behavior is non-blocking, the workspace will be ready and accessible upon agent connection, when it is blocking, workspace will wait for the startup script to complete before becoming ready and accessible.
StartupScriptBehavior StartupScriptBehavior `db:"startup_script_behavior" json:"startup_script_behavior"`
// The time the agent entered the starting lifecycle state
StartedAt sql.NullTime `db:"started_at" json:"started_at"`
// The time the agent entered the ready or start_error lifecycle state
@ -2056,12 +1977,20 @@ type WorkspaceAgent struct {
}
type WorkspaceAgentLog struct {
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
Output string `db:"output" json:"output"`
ID int64 `db:"id" json:"id"`
Level LogLevel `db:"level" json:"level"`
Source WorkspaceAgentLogSource `db:"source" json:"source"`
AgentID uuid.UUID `db:"agent_id" json:"agent_id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
Output string `db:"output" json:"output"`
ID int64 `db:"id" json:"id"`
Level LogLevel `db:"level" json:"level"`
LogSourceID uuid.UUID `db:"log_source_id" json:"log_source_id"`
}
type WorkspaceAgentLogSource struct {
WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"`
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
DisplayName string `db:"display_name" json:"display_name"`
Icon string `db:"icon" json:"icon"`
}
type WorkspaceAgentMetadatum struct {
@ -2076,6 +2005,19 @@ type WorkspaceAgentMetadatum struct {
CollectedAt time.Time `db:"collected_at" json:"collected_at"`
}
type WorkspaceAgentScript struct {
WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"`
LogSourceID uuid.UUID `db:"log_source_id" json:"log_source_id"`
LogPath string `db:"log_path" json:"log_path"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
Script string `db:"script" json:"script"`
Cron string `db:"cron" json:"cron"`
StartBlocksLogin bool `db:"start_blocks_login" json:"start_blocks_login"`
RunOnStart bool `db:"run_on_start" json:"run_on_start"`
RunOnStop bool `db:"run_on_stop" json:"run_on_stop"`
TimeoutSeconds int32 `db:"timeout_seconds" json:"timeout_seconds"`
}
type WorkspaceAgentStat struct {
ID uuid.UUID `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"created_at"`