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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1439 additions and 635 deletions

View File

@ -121,6 +121,7 @@ func New(options Options) io.Closer {
logDir: options.LogDir,
tempDir: options.TempDir,
lifecycleUpdate: make(chan struct{}, 1),
lifecycleReported: make(chan codersdk.WorkspaceAgentLifecycle, 1),
connStatsChan: make(chan *agentsdk.Stats, 1),
}
a.init(ctx)
@ -149,9 +150,10 @@ type agent struct {
sessionToken atomic.Pointer[string]
sshServer *ssh.Server
lifecycleUpdate chan struct{}
lifecycleMu sync.Mutex // Protects following.
lifecycleState codersdk.WorkspaceAgentLifecycle
lifecycleUpdate chan struct{}
lifecycleReported chan codersdk.WorkspaceAgentLifecycle
lifecycleMu sync.RWMutex // Protects following.
lifecycleState codersdk.WorkspaceAgentLifecycle
network *tailnet.Conn
connStatsChan chan *agentsdk.Stats
@ -207,9 +209,9 @@ func (a *agent) reportLifecycleLoop(ctx context.Context) {
}
for r := retry.New(time.Second, 15*time.Second); r.Wait(ctx); {
a.lifecycleMu.Lock()
a.lifecycleMu.RLock()
state := a.lifecycleState
a.lifecycleMu.Unlock()
a.lifecycleMu.RUnlock()
if state == lastReported {
break
@ -222,6 +224,11 @@ func (a *agent) reportLifecycleLoop(ctx context.Context) {
})
if err == nil {
lastReported = state
select {
case a.lifecycleReported <- state:
case <-a.lifecycleReported:
a.lifecycleReported <- state
}
break
}
if xerrors.Is(err, context.Canceled) || xerrors.Is(err, context.DeadlineExceeded) {
@ -233,13 +240,20 @@ func (a *agent) reportLifecycleLoop(ctx context.Context) {
}
}
// setLifecycle sets the lifecycle state and notifies the lifecycle loop.
// The state is only updated if it's a valid state transition.
func (a *agent) setLifecycle(ctx context.Context, state codersdk.WorkspaceAgentLifecycle) {
a.lifecycleMu.Lock()
defer a.lifecycleMu.Unlock()
a.logger.Debug(ctx, "set lifecycle state", slog.F("state", state), slog.F("previous", a.lifecycleState))
lastState := a.lifecycleState
if slices.Index(codersdk.WorkspaceAgentLifecycleOrder, lastState) > slices.Index(codersdk.WorkspaceAgentLifecycleOrder, state) {
a.logger.Warn(ctx, "attempted to set lifecycle state to a previous state", slog.F("last", lastState), slog.F("state", state))
a.lifecycleMu.Unlock()
return
}
a.lifecycleState = state
a.logger.Debug(ctx, "set lifecycle state", slog.F("state", state), slog.F("last", lastState))
a.lifecycleMu.Unlock()
select {
case a.lifecycleUpdate <- struct{}{}:
default:
@ -299,9 +313,10 @@ func (a *agent) run(ctx context.Context) error {
}
}
lifecycleState := codersdk.WorkspaceAgentLifecycleReady
scriptDone := make(chan error, 1)
scriptStart := time.Now()
err := a.trackConnGoroutine(func() {
err = a.trackConnGoroutine(func() {
defer close(scriptDone)
scriptDone <- a.runStartupScript(ctx, metadata.StartupScript)
})
@ -329,16 +344,17 @@ func (a *agent) run(ctx context.Context) error {
if errors.Is(err, context.Canceled) {
return
}
execTime := time.Since(scriptStart)
lifecycleStatus := codersdk.WorkspaceAgentLifecycleReady
if err != nil {
a.logger.Warn(ctx, "startup script failed", slog.F("execution_time", execTime), slog.Error(err))
lifecycleStatus = codersdk.WorkspaceAgentLifecycleStartError
} else {
a.logger.Info(ctx, "startup script completed", slog.F("execution_time", execTime))
// Only log if there was a startup script.
if metadata.StartupScript != "" {
execTime := time.Since(scriptStart)
if err != nil {
a.logger.Warn(ctx, "startup script failed", slog.F("execution_time", execTime), slog.Error(err))
lifecycleState = codersdk.WorkspaceAgentLifecycleStartError
} else {
a.logger.Info(ctx, "startup script completed", slog.F("execution_time", execTime))
}
}
a.setLifecycle(ctx, lifecycleStatus)
a.setLifecycle(ctx, lifecycleState)
}()
}
@ -606,14 +622,22 @@ func (a *agent) runCoordinator(ctx context.Context, network *tailnet.Conn) error
}
func (a *agent) runStartupScript(ctx context.Context, script string) error {
return a.runScript(ctx, "startup", script)
}
func (a *agent) runShutdownScript(ctx context.Context, script string) error {
return a.runScript(ctx, "shutdown", script)
}
func (a *agent) runScript(ctx context.Context, lifecycle, script string) error {
if script == "" {
return nil
}
a.logger.Info(ctx, "running startup script", slog.F("script", script))
writer, err := a.filesystem.OpenFile(filepath.Join(a.logDir, "coder-startup-script.log"), os.O_CREATE|os.O_RDWR, 0o600)
a.logger.Info(ctx, "running script", slog.F("lifecycle", lifecycle), slog.F("script", script))
writer, err := a.filesystem.OpenFile(filepath.Join(a.logDir, fmt.Sprintf("coder-%s-script.log", lifecycle)), os.O_CREATE|os.O_RDWR, 0o600)
if err != nil {
return xerrors.Errorf("open startup script log file: %w", err)
return xerrors.Errorf("open %s script log file: %w", lifecycle, err)
}
defer func() {
_ = writer.Close()
@ -774,7 +798,7 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
rawMetadata := a.metadata.Load()
if rawMetadata == nil {
return nil, xerrors.Errorf("no metadata was provided: %w", err)
return nil, xerrors.Errorf("no metadata was provided")
}
metadata, valid := rawMetadata.(agentsdk.Metadata)
if !valid {
@ -1290,13 +1314,73 @@ func (a *agent) Close() error {
if a.isClosed() {
return nil
}
ctx := context.Background()
a.setLifecycle(ctx, codersdk.WorkspaceAgentLifecycleShuttingDown)
lifecycleState := codersdk.WorkspaceAgentLifecycleOff
if metadata, ok := a.metadata.Load().(agentsdk.Metadata); ok && metadata.ShutdownScript != "" {
scriptDone := make(chan error, 1)
scriptStart := time.Now()
go func() {
defer close(scriptDone)
scriptDone <- a.runShutdownScript(ctx, metadata.ShutdownScript)
}()
var timeout <-chan time.Time
// If timeout is zero, an older version of the coder
// provider was used. Otherwise a timeout is always > 0.
if metadata.ShutdownScriptTimeout > 0 {
t := time.NewTimer(metadata.ShutdownScriptTimeout)
defer t.Stop()
timeout = t.C
}
var err error
select {
case err = <-scriptDone:
case <-timeout:
a.logger.Warn(ctx, "shutdown script timed out")
a.setLifecycle(ctx, codersdk.WorkspaceAgentLifecycleShutdownTimeout)
err = <-scriptDone // The script can still complete after a timeout.
}
execTime := time.Since(scriptStart)
if err != nil {
a.logger.Warn(ctx, "shutdown script failed", slog.F("execution_time", execTime), slog.Error(err))
lifecycleState = codersdk.WorkspaceAgentLifecycleShutdownError
} else {
a.logger.Info(ctx, "shutdown script completed", slog.F("execution_time", execTime))
}
}
// Set final state and wait for it to be reported because context
// cancellation will stop the report loop.
a.setLifecycle(ctx, lifecycleState)
// Wait for the lifecycle to be reported, but don't wait forever so
// that we don't break user expectations.
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
lifecycleWaitLoop:
for {
select {
case <-ctx.Done():
break lifecycleWaitLoop
case s := <-a.lifecycleReported:
if s == lifecycleState {
break lifecycleWaitLoop
}
}
}
close(a.closed)
a.closeCancel()
_ = a.sshServer.Close()
if a.network != nil {
_ = a.network.Close()
}
_ = a.sshServer.Close()
a.connCloseWait.Wait()
return nil
}

View File

@ -59,7 +59,8 @@ func TestAgent_Stats_SSH(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
conn, _, stats, _ := setupAgent(t, agentsdk.Metadata{}, 0)
//nolint:dogsled
conn, _, stats, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
sshClient, err := conn.SSHClient(ctx)
require.NoError(t, err)
@ -85,7 +86,8 @@ func TestAgent_Stats_ReconnectingPTY(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
conn, _, stats, _ := setupAgent(t, agentsdk.Metadata{}, 0)
//nolint:dogsled
conn, _, stats, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
ptyConn, err := conn.ReconnectingPTY(ctx, uuid.New(), 128, 128, "/bin/bash")
require.NoError(t, err)
@ -114,7 +116,8 @@ func TestAgent_Stats_Magic(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
conn, _, stats, _ := setupAgent(t, agentsdk.Metadata{}, 0)
//nolint:dogsled
conn, _, stats, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
sshClient, err := conn.SSHClient(ctx)
require.NoError(t, err)
defer sshClient.Close()
@ -572,7 +575,7 @@ func TestAgent_SFTP(t *testing.T) {
home = "/" + strings.ReplaceAll(home, "\\", "/")
}
//nolint:dogsled
conn, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
sshClient, err := conn.SSHClient(ctx)
require.NoError(t, err)
defer sshClient.Close()
@ -604,7 +607,7 @@ func TestAgent_SCP(t *testing.T) {
defer cancel()
//nolint:dogsled
conn, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
sshClient, err := conn.SSHClient(ctx)
require.NoError(t, err)
defer sshClient.Close()
@ -709,7 +712,7 @@ func TestAgent_StartupScript(t *testing.T) {
}
content := "output"
//nolint:dogsled
_, _, _, fs := setupAgent(t, agentsdk.Metadata{
_, _, _, fs, _ := setupAgent(t, agentsdk.Metadata{
StartupScript: "echo " + content,
}, 0)
var gotContent string
@ -740,10 +743,10 @@ func TestAgent_StartupScript(t *testing.T) {
func TestAgent_Lifecycle(t *testing.T) {
t.Parallel()
t.Run("Timeout", func(t *testing.T) {
t.Run("StartTimeout", func(t *testing.T) {
t.Parallel()
_, client, _, _ := setupAgent(t, agentsdk.Metadata{
_, client, _, _, _ := setupAgent(t, agentsdk.Metadata{
StartupScript: "sleep 5",
StartupScriptTimeout: time.Nanosecond,
}, 0)
@ -769,10 +772,10 @@ func TestAgent_Lifecycle(t *testing.T) {
}
})
t.Run("Error", func(t *testing.T) {
t.Run("StartError", func(t *testing.T) {
t.Parallel()
_, client, _, _ := setupAgent(t, agentsdk.Metadata{
_, client, _, _, _ := setupAgent(t, agentsdk.Metadata{
StartupScript: "false",
StartupScriptTimeout: 30 * time.Second,
}, 0)
@ -801,7 +804,7 @@ func TestAgent_Lifecycle(t *testing.T) {
t.Run("Ready", func(t *testing.T) {
t.Parallel()
_, client, _, _ := setupAgent(t, agentsdk.Metadata{
_, client, _, _, _ := setupAgent(t, agentsdk.Metadata{
StartupScript: "true",
StartupScriptTimeout: 30 * time.Second,
}, 0)
@ -826,6 +829,191 @@ func TestAgent_Lifecycle(t *testing.T) {
require.Equal(t, want, got)
}
})
t.Run("ShuttingDown", func(t *testing.T) {
t.Parallel()
_, client, _, _, closer := setupAgent(t, agentsdk.Metadata{
ShutdownScript: "sleep 5",
StartupScriptTimeout: 30 * time.Second,
}, 0)
var ready []codersdk.WorkspaceAgentLifecycle
assert.Eventually(t, func() bool {
ready = client.getLifecycleStates()
return len(ready) > 0 && ready[len(ready)-1] == codersdk.WorkspaceAgentLifecycleReady
}, testutil.WaitShort, testutil.IntervalMedium)
// Start close asynchronously so that we an inspect the state.
done := make(chan struct{})
go func() {
defer close(done)
err := closer.Close()
assert.NoError(t, err)
}()
t.Cleanup(func() {
<-done
})
want := []codersdk.WorkspaceAgentLifecycle{
codersdk.WorkspaceAgentLifecycleShuttingDown,
}
var got []codersdk.WorkspaceAgentLifecycle
assert.Eventually(t, func() bool {
got = client.getLifecycleStates()[len(ready):]
return len(got) > 0 && got[len(got)-1] == want[len(want)-1]
}, testutil.WaitShort, testutil.IntervalMedium)
require.Equal(t, want, got)
})
t.Run("ShutdownTimeout", func(t *testing.T) {
t.Parallel()
_, client, _, _, closer := setupAgent(t, agentsdk.Metadata{
ShutdownScript: "sleep 5",
ShutdownScriptTimeout: time.Nanosecond,
}, 0)
var ready []codersdk.WorkspaceAgentLifecycle
assert.Eventually(t, func() bool {
ready = client.getLifecycleStates()
return len(ready) > 0 && ready[len(ready)-1] == codersdk.WorkspaceAgentLifecycleReady
}, testutil.WaitShort, testutil.IntervalMedium)
// Start close asynchronously so that we an inspect the state.
done := make(chan struct{})
go func() {
defer close(done)
err := closer.Close()
assert.NoError(t, err)
}()
t.Cleanup(func() {
<-done
})
want := []codersdk.WorkspaceAgentLifecycle{
codersdk.WorkspaceAgentLifecycleShuttingDown,
codersdk.WorkspaceAgentLifecycleShutdownTimeout,
}
var got []codersdk.WorkspaceAgentLifecycle
assert.Eventually(t, func() bool {
got = client.getLifecycleStates()[len(ready):]
return len(got) > 0 && got[len(got)-1] == want[len(want)-1]
}, testutil.WaitShort, testutil.IntervalMedium)
switch len(got) {
case 1:
// This can happen if lifecycle state updates are
// too fast, only the latest one is reported.
require.Equal(t, want[1:], got)
default:
// This is the expected case.
require.Equal(t, want, got)
}
})
t.Run("ShutdownError", func(t *testing.T) {
t.Parallel()
_, client, _, _, closer := setupAgent(t, agentsdk.Metadata{
ShutdownScript: "false",
ShutdownScriptTimeout: 30 * time.Second,
}, 0)
var ready []codersdk.WorkspaceAgentLifecycle
assert.Eventually(t, func() bool {
ready = client.getLifecycleStates()
return len(ready) > 0 && ready[len(ready)-1] == codersdk.WorkspaceAgentLifecycleReady
}, testutil.WaitShort, testutil.IntervalMedium)
// Start close asynchronously so that we an inspect the state.
done := make(chan struct{})
go func() {
defer close(done)
err := closer.Close()
assert.NoError(t, err)
}()
t.Cleanup(func() {
<-done
})
want := []codersdk.WorkspaceAgentLifecycle{
codersdk.WorkspaceAgentLifecycleShuttingDown,
codersdk.WorkspaceAgentLifecycleShutdownError,
}
var got []codersdk.WorkspaceAgentLifecycle
assert.Eventually(t, func() bool {
got = client.getLifecycleStates()[len(ready):]
return len(got) > 0 && got[len(got)-1] == want[len(want)-1]
}, testutil.WaitShort, testutil.IntervalMedium)
switch len(got) {
case 1:
// This can happen if lifecycle state updates are
// too fast, only the latest one is reported.
require.Equal(t, want[1:], got)
default:
// This is the expected case.
require.Equal(t, want, got)
}
})
t.Run("ShutdownScriptOnce", func(t *testing.T) {
t.Parallel()
expected := "this-is-shutdown"
client := &client{
t: t,
agentID: uuid.New(),
metadata: agentsdk.Metadata{
DERPMap: tailnettest.RunDERPAndSTUN(t),
StartupScript: "echo 1",
ShutdownScript: "echo " + expected,
},
statsChan: make(chan *agentsdk.Stats),
coordinator: tailnet.NewCoordinator(),
}
fs := afero.NewMemMapFs()
agent := agent.New(agent.Options{
Client: client,
Logger: slogtest.Make(t, nil).Leveled(slog.LevelInfo),
Filesystem: fs,
})
// agent.Close() loads the shutdown script from the agent metadata.
// The metadata is populated just before execution of the startup script, so it's mandatory to wait
// until the startup starts.
require.Eventually(t, func() bool {
outputPath := filepath.Join(os.TempDir(), "coder-startup-script.log")
content, err := afero.ReadFile(fs, outputPath)
if err != nil {
t.Logf("read file %q: %s", outputPath, err)
return false
}
return len(content) > 0 // something is in the startup log file
}, testutil.WaitShort, testutil.IntervalMedium)
err := agent.Close()
require.NoError(t, err, "agent should be closed successfully")
outputPath := filepath.Join(os.TempDir(), "coder-shutdown-script.log")
logFirstRead, err := afero.ReadFile(fs, outputPath)
require.NoError(t, err, "log file should be present")
require.Equal(t, expected, string(bytes.TrimSpace(logFirstRead)))
// Make sure that script can't be executed twice.
err = agent.Close()
require.NoError(t, err, "don't need to close the agent twice, no effect")
logSecondRead, err := afero.ReadFile(fs, outputPath)
require.NoError(t, err, "log file should be present")
require.Equal(t, string(bytes.TrimSpace(logFirstRead)), string(bytes.TrimSpace(logSecondRead)))
})
}
func TestAgent_Startup(t *testing.T) {
@ -834,7 +1022,7 @@ func TestAgent_Startup(t *testing.T) {
t.Run("EmptyDirectory", func(t *testing.T) {
t.Parallel()
_, client, _, _ := setupAgent(t, agentsdk.Metadata{
_, client, _, _, _ := setupAgent(t, agentsdk.Metadata{
StartupScript: "true",
StartupScriptTimeout: 30 * time.Second,
Directory: "",
@ -848,7 +1036,7 @@ func TestAgent_Startup(t *testing.T) {
t.Run("HomeDirectory", func(t *testing.T) {
t.Parallel()
_, client, _, _ := setupAgent(t, agentsdk.Metadata{
_, client, _, _, _ := setupAgent(t, agentsdk.Metadata{
StartupScript: "true",
StartupScriptTimeout: 30 * time.Second,
Directory: "~",
@ -864,7 +1052,7 @@ func TestAgent_Startup(t *testing.T) {
t.Run("HomeEnvironmentVariable", func(t *testing.T) {
t.Parallel()
_, client, _, _ := setupAgent(t, agentsdk.Metadata{
_, client, _, _, _ := setupAgent(t, agentsdk.Metadata{
StartupScript: "true",
StartupScriptTimeout: 30 * time.Second,
Directory: "$HOME",
@ -891,7 +1079,7 @@ func TestAgent_ReconnectingPTY(t *testing.T) {
defer cancel()
//nolint:dogsled
conn, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
id := uuid.New()
netConn, err := conn.ReconnectingPTY(ctx, id, 100, 100, "/bin/bash")
require.NoError(t, err)
@ -993,7 +1181,7 @@ func TestAgent_Dial(t *testing.T) {
}()
//nolint:dogsled
conn, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
require.True(t, conn.AwaitReachable(context.Background()))
conn1, err := conn.DialContext(context.Background(), l.Addr().Network(), l.Addr().String())
require.NoError(t, err)
@ -1015,7 +1203,7 @@ func TestAgent_Speedtest(t *testing.T) {
defer cancel()
derpMap := tailnettest.RunDERPAndSTUN(t)
//nolint:dogsled
conn, _, _, _ := setupAgent(t, agentsdk.Metadata{
conn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{
DERPMap: derpMap,
}, 0)
defer conn.Close()
@ -1101,7 +1289,7 @@ func TestAgent_WriteVSCodeConfigs(t *testing.T) {
func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) *exec.Cmd {
//nolint:dogsled
agentConn, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
agentConn, _, _, _, _ := setupAgent(t, agentsdk.Metadata{}, 0)
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
waitGroup := sync.WaitGroup{}
@ -1148,7 +1336,7 @@ func setupSSHSession(t *testing.T, options agentsdk.Metadata) *ssh.Session {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
//nolint:dogsled
conn, _, _, _ := setupAgent(t, options, 0)
conn, _, _, _, _ := setupAgent(t, options, 0)
sshClient, err := conn.SSHClient(ctx)
require.NoError(t, err)
t.Cleanup(func() {
@ -1173,6 +1361,7 @@ func setupAgent(t *testing.T, metadata agentsdk.Metadata, ptyTimeout time.Durati
*client,
<-chan *agentsdk.Stats,
afero.Fs,
io.Closer,
) {
if metadata.DERPMap == nil {
metadata.DERPMap = tailnettest.RunDERPAndSTUN(t)
@ -1233,7 +1422,7 @@ func setupAgent(t *testing.T, metadata agentsdk.Metadata, ptyTimeout time.Durati
if !agentConn.AwaitReachable(ctx) {
t.Fatal("agent not reachable")
}
return agentConn, c, statsCh, fs
return agentConn, c, statsCh, fs, closer
}
var dialTestPayload = []byte("dean-was-here123")

View File

@ -17,7 +17,10 @@ import (
"github.com/coder/coder/codersdk"
)
var AgentStartError = xerrors.New("agent startup exited with non-zero exit status")
var (
AgentStartError = xerrors.New("agent startup exited with non-zero exit status")
AgentShuttingDown = xerrors.New("agent is shutting down")
)
type AgentOptions struct {
WorkspaceName string
@ -146,6 +149,10 @@ func Agent(ctx context.Context, writer io.Writer, opts AgentOptions) error {
case codersdk.WorkspaceAgentLifecycleStartError:
showMessage()
return AgentStartError
case codersdk.WorkspaceAgentLifecycleShuttingDown, codersdk.WorkspaceAgentLifecycleShutdownTimeout,
codersdk.WorkspaceAgentLifecycleShutdownError, codersdk.WorkspaceAgentLifecycleOff:
showMessage()
return AgentShuttingDown
default:
select {
case <-warningShown:
@ -229,6 +236,22 @@ func waitingMessage(agent codersdk.WorkspaceAgent, opts AgentOptions) (m *messag
m.Spin = ""
m.Prompt = "The workspace ran into a problem while getting ready, the agent startup script exited with non-zero status."
default:
switch agent.LifecycleState {
case codersdk.WorkspaceAgentLifecycleShutdownTimeout:
m.Spin = ""
m.Prompt = "The workspace is shutting down, but is taking longer than expected to shut down and the agent shutdown script is still executing."
m.Troubleshoot = true
case codersdk.WorkspaceAgentLifecycleShutdownError:
m.Spin = ""
m.Prompt = "The workspace ran into a problem while shutting down, the agent shutdown script exited with non-zero status."
m.Troubleshoot = true
case codersdk.WorkspaceAgentLifecycleShuttingDown:
m.Spin = ""
m.Prompt = "The workspace is shutting down."
case codersdk.WorkspaceAgentLifecycleOff:
m.Spin = ""
m.Prompt = "The workspace is not running."
}
// Not a failure state, no troubleshooting necessary.
return m
}

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},
}

View File

@ -69,15 +69,17 @@ type Metadata struct {
// GitAuthConfigs stores the number of Git configurations
// the Coder deployment has. If this number is >0, we
// set up special configuration in the workspace.
GitAuthConfigs int `json:"git_auth_configs"`
VSCodePortProxyURI string `json:"vscode_port_proxy_uri"`
Apps []codersdk.WorkspaceApp `json:"apps"`
DERPMap *tailcfg.DERPMap `json:"derpmap"`
EnvironmentVariables map[string]string `json:"environment_variables"`
StartupScript string `json:"startup_script"`
StartupScriptTimeout time.Duration `json:"startup_script_timeout"`
Directory string `json:"directory"`
MOTDFile string `json:"motd_file"`
GitAuthConfigs int `json:"git_auth_configs"`
VSCodePortProxyURI string `json:"vscode_port_proxy_uri"`
Apps []codersdk.WorkspaceApp `json:"apps"`
DERPMap *tailcfg.DERPMap `json:"derpmap"`
EnvironmentVariables map[string]string `json:"environment_variables"`
StartupScript string `json:"startup_script"`
StartupScriptTimeout time.Duration `json:"startup_script_timeout"`
Directory string `json:"directory"`
MOTDFile string `json:"motd_file"`
ShutdownScript string `json:"shutdown_script"`
ShutdownScriptTimeout time.Duration `json:"shutdown_script_timeout"`
}
// Metadata fetches metadata for the currently authenticated workspace agent.

View File

@ -44,13 +44,35 @@ type WorkspaceAgentLifecycle string
// WorkspaceAgentLifecycle enums.
const (
WorkspaceAgentLifecycleCreated WorkspaceAgentLifecycle = "created"
WorkspaceAgentLifecycleStarting WorkspaceAgentLifecycle = "starting"
WorkspaceAgentLifecycleStartTimeout WorkspaceAgentLifecycle = "start_timeout"
WorkspaceAgentLifecycleStartError WorkspaceAgentLifecycle = "start_error"
WorkspaceAgentLifecycleReady WorkspaceAgentLifecycle = "ready"
WorkspaceAgentLifecycleCreated WorkspaceAgentLifecycle = "created"
WorkspaceAgentLifecycleStarting WorkspaceAgentLifecycle = "starting"
WorkspaceAgentLifecycleStartTimeout WorkspaceAgentLifecycle = "start_timeout"
WorkspaceAgentLifecycleStartError WorkspaceAgentLifecycle = "start_error"
WorkspaceAgentLifecycleReady WorkspaceAgentLifecycle = "ready"
WorkspaceAgentLifecycleShuttingDown WorkspaceAgentLifecycle = "shutting_down"
WorkspaceAgentLifecycleShutdownTimeout WorkspaceAgentLifecycle = "shutdown_timeout"
WorkspaceAgentLifecycleShutdownError WorkspaceAgentLifecycle = "shutdown_error"
WorkspaceAgentLifecycleOff WorkspaceAgentLifecycle = "off"
)
// WorkspaceAgentLifecycleOrder is the order in which workspace agent
// lifecycle states are expected to be reported during the lifetime of
// the agent process. For instance, the agent can go from starting to
// ready without reporting timeout or error, but it should not go from
// ready to starting. This is merely a hint for the agent process, and
// is not enforced by the server.
var WorkspaceAgentLifecycleOrder = []WorkspaceAgentLifecycle{
WorkspaceAgentLifecycleCreated,
WorkspaceAgentLifecycleStarting,
WorkspaceAgentLifecycleStartTimeout,
WorkspaceAgentLifecycleStartError,
WorkspaceAgentLifecycleReady,
WorkspaceAgentLifecycleShuttingDown,
WorkspaceAgentLifecycleShutdownTimeout,
WorkspaceAgentLifecycleShutdownError,
WorkspaceAgentLifecycleOff,
}
type WorkspaceAgent struct {
ID uuid.UUID `json:"id" format:"uuid"`
CreatedAt time.Time `json:"created_at" format:"date-time"`
@ -76,9 +98,11 @@ type WorkspaceAgent struct {
ConnectionTimeoutSeconds int32 `json:"connection_timeout_seconds"`
TroubleshootingURL string `json:"troubleshooting_url"`
// LoginBeforeReady if true, the agent will delay logins until it is ready (e.g. executing startup script has ended).
LoginBeforeReady bool `db:"login_before_ready" json:"login_before_ready"`
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 `db:"startup_script_timeout_seconds" json:"startup_script_timeout_seconds"`
StartupScriptTimeoutSeconds int32 `json:"startup_script_timeout_seconds"`
ShutdownScript string `json:"shutdown_script,omitempty"`
ShutdownScriptTimeoutSeconds int32 `json:"shutdown_script_timeout_seconds"`
}
type DERPRegion struct {

View File

@ -369,6 +369,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/metadata \
},
"git_auth_configs": 0,
"motd_file": "string",
"shutdown_script": "string",
"shutdown_script_timeout": 0,
"startup_script": "string",
"startup_script_timeout": 0,
"vscode_port_proxy_uri": "string"
@ -515,6 +517,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent} \
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",

View File

@ -102,6 +102,8 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
@ -248,6 +250,8 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
@ -537,6 +541,8 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/res
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
@ -575,89 +581,95 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/res
Status Code **200**
| Name | Type | Required | Restrictions | Description |
| ----------------------------------- | -------------------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `[array item]` | array | false | | |
| `» agents` | array | false | | |
| `»» apps` | array | false | | |
| `»»» command` | string | false | | |
| `»»» display_name` | string | false | | »»display name is a friendly name for the app. |
| `»»» external` | boolean | false | | External specifies whether the URL should be opened externally on the client or not. |
| `»»» health` | [codersdk.WorkspaceAppHealth](schemas.md#codersdkworkspaceapphealth) | false | | |
| `»»» healthcheck` | [codersdk.Healthcheck](schemas.md#codersdkhealthcheck) | false | | Healthcheck specifies the configuration for checking app health. |
| `»»»» interval` | integer | false | | Interval specifies the seconds between each health check. |
| `»»»» threshold` | integer | false | | Threshold specifies the number of consecutive failed health checks before returning "unhealthy". |
| `»»»» url` | string | false | | »»»url specifies the endpoint to check for the app health. |
| `»»» icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. |
| `»»» id` | string(uuid) | false | | |
| `»»» sharing_level` | [codersdk.WorkspaceAppSharingLevel](schemas.md#codersdkworkspaceappsharinglevel) | false | | |
| `»»» slug` | string | false | | Slug is a unique identifier within the agent. |
| `»»» subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. |
| `»»» url` | string | false | | »»url is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. |
| `»» architecture` | string | false | | |
| `»» connection_timeout_seconds` | integer | false | | |
| `»» created_at` | string(date-time) | false | | |
| `»» directory` | string | false | | |
| `»» 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 | | |
| `»» last_connected_at` | string(date-time) | false | | |
| `»» latency` | object | false | | »latency is mapped by region name (e.g. "New York City", "Seattle"). |
| `»»» [any property]` | [codersdk.DERPRegion](schemas.md#codersdkderpregion) | false | | |
| `»»»» latency_ms` | number | false | | |
| `»»»» preferred` | boolean | false | | |
| `»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | |
| `»» login_before_ready` | boolean | false | | »login before ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). |
| `»» name` | string | false | | |
| `»» operating_system` | string | false | | |
| `»» resource_id` | string(uuid) | false | | |
| `»» 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 | | |
| `»» troubleshooting_url` | string | false | | |
| `»» updated_at` | string(date-time) | false | | |
| `»» version` | string | false | | |
| `» created_at` | string(date-time) | false | | |
| `» daily_cost` | integer | false | | |
| `» hide` | boolean | false | | |
| `» icon` | string | false | | |
| `» id` | string(uuid) | false | | |
| `» job_id` | string(uuid) | false | | |
| `» metadata` | array | false | | |
| `»» key` | string | false | | |
| `»» sensitive` | boolean | false | | |
| `»» value` | string | false | | |
| `» name` | string | false | | |
| `» type` | string | false | | |
| `» workspace_transition` | [codersdk.WorkspaceTransition](schemas.md#codersdkworkspacetransition) | false | | |
| Name | Type | Required | Restrictions | Description |
| ------------------------------------ | -------------------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `[array item]` | array | false | | |
| `» agents` | array | false | | |
| `»» apps` | array | false | | |
| `»»» command` | string | false | | |
| `»»» display_name` | string | false | | »»display name is a friendly name for the app. |
| `»»» external` | boolean | false | | External specifies whether the URL should be opened externally on the client or not. |
| `»»» health` | [codersdk.WorkspaceAppHealth](schemas.md#codersdkworkspaceapphealth) | false | | |
| `»»» healthcheck` | [codersdk.Healthcheck](schemas.md#codersdkhealthcheck) | false | | Healthcheck specifies the configuration for checking app health. |
| `»»»» interval` | integer | false | | Interval specifies the seconds between each health check. |
| `»»»» threshold` | integer | false | | Threshold specifies the number of consecutive failed health checks before returning "unhealthy". |
| `»»»» url` | string | false | | »»»url specifies the endpoint to check for the app health. |
| `»»» icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. |
| `»»» id` | string(uuid) | false | | |
| `»»» sharing_level` | [codersdk.WorkspaceAppSharingLevel](schemas.md#codersdkworkspaceappsharinglevel) | false | | |
| `»»» slug` | string | false | | Slug is a unique identifier within the agent. |
| `»»» subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. |
| `»»» url` | string | false | | »»url is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. |
| `»» architecture` | string | false | | |
| `»» connection_timeout_seconds` | integer | false | | |
| `»» created_at` | string(date-time) | false | | |
| `»» directory` | string | false | | |
| `»» 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 | | |
| `»» last_connected_at` | string(date-time) | false | | |
| `»» latency` | object | false | | »latency is mapped by region name (e.g. "New York City", "Seattle"). |
| `»»» [any property]` | [codersdk.DERPRegion](schemas.md#codersdkderpregion) | false | | |
| `»»»» latency_ms` | number | false | | |
| `»»»» preferred` | boolean | false | | |
| `»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | |
| `»» login_before_ready` | boolean | false | | »login before ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). |
| `»» name` | string | false | | |
| `»» operating_system` | string | false | | |
| `»» resource_id` | string(uuid) | false | | |
| `»» shutdown_script` | string | false | | |
| `»» shutdown_script_timeout_seconds` | integer | false | | |
| `»» 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 | | |
| `»» troubleshooting_url` | string | false | | |
| `»» updated_at` | string(date-time) | false | | |
| `»» version` | string | false | | |
| `» created_at` | string(date-time) | false | | |
| `» daily_cost` | integer | false | | |
| `» hide` | boolean | false | | |
| `» icon` | string | false | | |
| `» id` | string(uuid) | false | | |
| `» job_id` | string(uuid) | false | | |
| `» metadata` | array | false | | |
| `»» key` | string | false | | |
| `»» sensitive` | boolean | false | | |
| `»» value` | string | false | | |
| `» name` | string | false | | |
| `» type` | string | false | | |
| `» workspace_transition` | [codersdk.WorkspaceTransition](schemas.md#codersdkworkspacetransition) | false | | |
#### Enumerated Values
| Property | Value |
| ---------------------- | --------------- |
| `health` | `disabled` |
| `health` | `initializing` |
| `health` | `healthy` |
| `health` | `unhealthy` |
| `sharing_level` | `owner` |
| `sharing_level` | `authenticated` |
| `sharing_level` | `public` |
| `lifecycle_state` | `created` |
| `lifecycle_state` | `starting` |
| `lifecycle_state` | `start_timeout` |
| `lifecycle_state` | `start_error` |
| `lifecycle_state` | `ready` |
| `status` | `connecting` |
| `status` | `connected` |
| `status` | `disconnected` |
| `status` | `timeout` |
| `workspace_transition` | `start` |
| `workspace_transition` | `stop` |
| `workspace_transition` | `delete` |
| Property | Value |
| ---------------------- | ------------------ |
| `health` | `disabled` |
| `health` | `initializing` |
| `health` | `healthy` |
| `health` | `unhealthy` |
| `sharing_level` | `owner` |
| `sharing_level` | `authenticated` |
| `sharing_level` | `public` |
| `lifecycle_state` | `created` |
| `lifecycle_state` | `starting` |
| `lifecycle_state` | `start_timeout` |
| `lifecycle_state` | `start_error` |
| `lifecycle_state` | `ready` |
| `lifecycle_state` | `shutting_down` |
| `lifecycle_state` | `shutdown_timeout` |
| `lifecycle_state` | `shutdown_error` |
| `lifecycle_state` | `off` |
| `status` | `connecting` |
| `status` | `connected` |
| `status` | `disconnected` |
| `status` | `timeout` |
| `workspace_transition` | `start` |
| `workspace_transition` | `stop` |
| `workspace_transition` | `delete` |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
@ -761,6 +773,8 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
@ -912,6 +926,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
@ -961,141 +977,147 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \
Status Code **200**
| Name | Type | Required | Restrictions | Description |
| ------------------------------------ | -------------------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `[array item]` | array | false | | |
| `» build_number` | integer | false | | |
| `» created_at` | string(date-time) | false | | |
| `» daily_cost` | integer | false | | |
| `» deadline` | string(date-time) | false | | |
| `» id` | string(uuid) | false | | |
| `» initiator_id` | string(uuid) | false | | |
| `» initiator_name` | string | false | | |
| `» job` | [codersdk.ProvisionerJob](schemas.md#codersdkprovisionerjob) | false | | |
| `»» canceled_at` | string(date-time) | false | | |
| `»» completed_at` | string(date-time) | false | | |
| `»» created_at` | string(date-time) | false | | |
| `»» error` | string | false | | |
| `»» file_id` | string(uuid) | false | | |
| `»» id` | string(uuid) | false | | |
| `»» started_at` | string(date-time) | false | | |
| `»» status` | [codersdk.ProvisionerJobStatus](schemas.md#codersdkprovisionerjobstatus) | false | | |
| `»» tags` | object | false | | |
| `»»» [any property]` | string | false | | |
| `»» worker_id` | string(uuid) | false | | |
| `» reason` | [codersdk.BuildReason](schemas.md#codersdkbuildreason) | false | | |
| `» resources` | array | false | | |
| `»» agents` | array | false | | |
| `»»» apps` | array | false | | |
| `»»»» command` | string | false | | |
| `»»»» display_name` | string | false | | »»»display name is a friendly name for the app. |
| `»»»» external` | boolean | false | | External specifies whether the URL should be opened externally on the client or not. |
| `»»»» health` | [codersdk.WorkspaceAppHealth](schemas.md#codersdkworkspaceapphealth) | false | | |
| `»»»» healthcheck` | [codersdk.Healthcheck](schemas.md#codersdkhealthcheck) | false | | Healthcheck specifies the configuration for checking app health. |
| `»»»»» interval` | integer | false | | Interval specifies the seconds between each health check. |
| `»»»»» threshold` | integer | false | | Threshold specifies the number of consecutive failed health checks before returning "unhealthy". |
| `»»»»» url` | string | false | | »»»»url specifies the endpoint to check for the app health. |
| `»»»» icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. |
| `»»»» id` | string(uuid) | false | | |
| `»»»» sharing_level` | [codersdk.WorkspaceAppSharingLevel](schemas.md#codersdkworkspaceappsharinglevel) | false | | |
| `»»»» slug` | string | false | | Slug is a unique identifier within the agent. |
| `»»»» subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. |
| `»»»» url` | string | false | | »»»url is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. |
| `»»» architecture` | string | false | | |
| `»»» connection_timeout_seconds` | integer | false | | |
| `»»» created_at` | string(date-time) | false | | |
| `»»» directory` | string | false | | |
| `»»» 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 | | |
| `»»» last_connected_at` | string(date-time) | false | | |
| `»»» latency` | object | false | | »»latency is mapped by region name (e.g. "New York City", "Seattle"). |
| `»»»» [any property]` | [codersdk.DERPRegion](schemas.md#codersdkderpregion) | false | | |
| `»»»»» latency_ms` | number | false | | |
| `»»»»» preferred` | boolean | false | | |
| `»»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | |
| `»»» login_before_ready` | boolean | false | | »»login before ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). |
| `»»» name` | string | false | | |
| `»»» operating_system` | string | false | | |
| `»»» resource_id` | string(uuid) | false | | |
| `»»» 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 | | |
| `»»» troubleshooting_url` | string | false | | |
| `»»» updated_at` | string(date-time) | false | | |
| `»»» version` | string | false | | |
| `»» created_at` | string(date-time) | false | | |
| `»» daily_cost` | integer | false | | |
| `»» hide` | boolean | false | | |
| `»» icon` | string | false | | |
| `»» id` | string(uuid) | false | | |
| `»» job_id` | string(uuid) | false | | |
| `»» metadata` | array | false | | |
| `»»» key` | string | false | | |
| `»»» sensitive` | boolean | false | | |
| `»»» value` | string | false | | |
| `»» name` | string | false | | |
| `»» type` | string | false | | |
| `»» workspace_transition` | [codersdk.WorkspaceTransition](schemas.md#codersdkworkspacetransition) | false | | |
| `» status` | [codersdk.WorkspaceStatus](schemas.md#codersdkworkspacestatus) | false | | |
| `» template_version_id` | string(uuid) | false | | |
| `» template_version_name` | string | false | | |
| `» transition` | [codersdk.WorkspaceTransition](schemas.md#codersdkworkspacetransition) | false | | |
| `» updated_at` | string(date-time) | false | | |
| `» workspace_id` | string(uuid) | false | | |
| `» workspace_name` | string | false | | |
| `» workspace_owner_id` | string(uuid) | false | | |
| `» workspace_owner_name` | string | false | | |
| Name | Type | Required | Restrictions | Description |
| ------------------------------------- | -------------------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `[array item]` | array | false | | |
| `» build_number` | integer | false | | |
| `» created_at` | string(date-time) | false | | |
| `» daily_cost` | integer | false | | |
| `» deadline` | string(date-time) | false | | |
| `» id` | string(uuid) | false | | |
| `» initiator_id` | string(uuid) | false | | |
| `» initiator_name` | string | false | | |
| `» job` | [codersdk.ProvisionerJob](schemas.md#codersdkprovisionerjob) | false | | |
| `»» canceled_at` | string(date-time) | false | | |
| `»» completed_at` | string(date-time) | false | | |
| `»» created_at` | string(date-time) | false | | |
| `»» error` | string | false | | |
| `»» file_id` | string(uuid) | false | | |
| `»» id` | string(uuid) | false | | |
| `»» started_at` | string(date-time) | false | | |
| `»» status` | [codersdk.ProvisionerJobStatus](schemas.md#codersdkprovisionerjobstatus) | false | | |
| `»» tags` | object | false | | |
| `»»» [any property]` | string | false | | |
| `»» worker_id` | string(uuid) | false | | |
| `» reason` | [codersdk.BuildReason](schemas.md#codersdkbuildreason) | false | | |
| `» resources` | array | false | | |
| `»» agents` | array | false | | |
| `»»» apps` | array | false | | |
| `»»»» command` | string | false | | |
| `»»»» display_name` | string | false | | »»»display name is a friendly name for the app. |
| `»»»» external` | boolean | false | | External specifies whether the URL should be opened externally on the client or not. |
| `»»»» health` | [codersdk.WorkspaceAppHealth](schemas.md#codersdkworkspaceapphealth) | false | | |
| `»»»» healthcheck` | [codersdk.Healthcheck](schemas.md#codersdkhealthcheck) | false | | Healthcheck specifies the configuration for checking app health. |
| `»»»»» interval` | integer | false | | Interval specifies the seconds between each health check. |
| `»»»»» threshold` | integer | false | | Threshold specifies the number of consecutive failed health checks before returning "unhealthy". |
| `»»»»» url` | string | false | | »»»»url specifies the endpoint to check for the app health. |
| `»»»» icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. |
| `»»»» id` | string(uuid) | false | | |
| `»»»» sharing_level` | [codersdk.WorkspaceAppSharingLevel](schemas.md#codersdkworkspaceappsharinglevel) | false | | |
| `»»»» slug` | string | false | | Slug is a unique identifier within the agent. |
| `»»»» subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. |
| `»»»» url` | string | false | | »»»url is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. |
| `»»» architecture` | string | false | | |
| `»»» connection_timeout_seconds` | integer | false | | |
| `»»» created_at` | string(date-time) | false | | |
| `»»» directory` | string | false | | |
| `»»» 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 | | |
| `»»» last_connected_at` | string(date-time) | false | | |
| `»»» latency` | object | false | | »»latency is mapped by region name (e.g. "New York City", "Seattle"). |
| `»»»» [any property]` | [codersdk.DERPRegion](schemas.md#codersdkderpregion) | false | | |
| `»»»»» latency_ms` | number | false | | |
| `»»»»» preferred` | boolean | false | | |
| `»»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | |
| `»»» login_before_ready` | boolean | false | | »»login before ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). |
| `»»» name` | string | false | | |
| `»»» operating_system` | string | false | | |
| `»»» resource_id` | string(uuid) | false | | |
| `»»» shutdown_script` | string | false | | |
| `»»» shutdown_script_timeout_seconds` | integer | false | | |
| `»»» 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 | | |
| `»»» troubleshooting_url` | string | false | | |
| `»»» updated_at` | string(date-time) | false | | |
| `»»» version` | string | false | | |
| `»» created_at` | string(date-time) | false | | |
| `»» daily_cost` | integer | false | | |
| `»» hide` | boolean | false | | |
| `»» icon` | string | false | | |
| `»» id` | string(uuid) | false | | |
| `»» job_id` | string(uuid) | false | | |
| `»» metadata` | array | false | | |
| `»»» key` | string | false | | |
| `»»» sensitive` | boolean | false | | |
| `»»» value` | string | false | | |
| `»» name` | string | false | | |
| `»» type` | string | false | | |
| `»» workspace_transition` | [codersdk.WorkspaceTransition](schemas.md#codersdkworkspacetransition) | false | | |
| `» status` | [codersdk.WorkspaceStatus](schemas.md#codersdkworkspacestatus) | false | | |
| `» template_version_id` | string(uuid) | false | | |
| `» template_version_name` | string | false | | |
| `» transition` | [codersdk.WorkspaceTransition](schemas.md#codersdkworkspacetransition) | false | | |
| `» updated_at` | string(date-time) | false | | |
| `» workspace_id` | string(uuid) | false | | |
| `» workspace_name` | string | false | | |
| `» workspace_owner_id` | string(uuid) | false | | |
| `» workspace_owner_name` | string | false | | |
#### Enumerated Values
| Property | Value |
| ---------------------- | --------------- |
| `status` | `pending` |
| `status` | `running` |
| `status` | `succeeded` |
| `status` | `canceling` |
| `status` | `canceled` |
| `status` | `failed` |
| `reason` | `initiator` |
| `reason` | `autostart` |
| `reason` | `autostop` |
| `health` | `disabled` |
| `health` | `initializing` |
| `health` | `healthy` |
| `health` | `unhealthy` |
| `sharing_level` | `owner` |
| `sharing_level` | `authenticated` |
| `sharing_level` | `public` |
| `lifecycle_state` | `created` |
| `lifecycle_state` | `starting` |
| `lifecycle_state` | `start_timeout` |
| `lifecycle_state` | `start_error` |
| `lifecycle_state` | `ready` |
| `status` | `connecting` |
| `status` | `connected` |
| `status` | `disconnected` |
| `status` | `timeout` |
| `workspace_transition` | `start` |
| `workspace_transition` | `stop` |
| `workspace_transition` | `delete` |
| `status` | `pending` |
| `status` | `starting` |
| `status` | `running` |
| `status` | `stopping` |
| `status` | `stopped` |
| `status` | `failed` |
| `status` | `canceling` |
| `status` | `canceled` |
| `status` | `deleting` |
| `status` | `deleted` |
| `transition` | `start` |
| `transition` | `stop` |
| `transition` | `delete` |
| Property | Value |
| ---------------------- | ------------------ |
| `status` | `pending` |
| `status` | `running` |
| `status` | `succeeded` |
| `status` | `canceling` |
| `status` | `canceled` |
| `status` | `failed` |
| `reason` | `initiator` |
| `reason` | `autostart` |
| `reason` | `autostop` |
| `health` | `disabled` |
| `health` | `initializing` |
| `health` | `healthy` |
| `health` | `unhealthy` |
| `sharing_level` | `owner` |
| `sharing_level` | `authenticated` |
| `sharing_level` | `public` |
| `lifecycle_state` | `created` |
| `lifecycle_state` | `starting` |
| `lifecycle_state` | `start_timeout` |
| `lifecycle_state` | `start_error` |
| `lifecycle_state` | `ready` |
| `lifecycle_state` | `shutting_down` |
| `lifecycle_state` | `shutdown_timeout` |
| `lifecycle_state` | `shutdown_error` |
| `lifecycle_state` | `off` |
| `status` | `connecting` |
| `status` | `connected` |
| `status` | `disconnected` |
| `status` | `timeout` |
| `workspace_transition` | `start` |
| `workspace_transition` | `stop` |
| `workspace_transition` | `delete` |
| `status` | `pending` |
| `status` | `starting` |
| `status` | `running` |
| `status` | `stopping` |
| `status` | `stopped` |
| `status` | `failed` |
| `status` | `canceling` |
| `status` | `canceled` |
| `status` | `deleting` |
| `status` | `deleted` |
| `transition` | `start` |
| `transition` | `stop` |
| `transition` | `delete` |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
@ -1228,6 +1250,8 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",

View File

@ -175,6 +175,8 @@
},
"git_auth_configs": 0,
"motd_file": "string",
"shutdown_script": "string",
"shutdown_script_timeout": 0,
"startup_script": "string",
"startup_script_timeout": 0,
"vscode_port_proxy_uri": "string"
@ -183,18 +185,20 @@
### Properties
| Name | Type | Required | Restrictions | Description |
| ------------------------ | ------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apps` | array of [codersdk.WorkspaceApp](#codersdkworkspaceapp) | false | | |
| `derpmap` | [tailcfg.DERPMap](#tailcfgderpmap) | false | | |
| `directory` | string | false | | |
| `environment_variables` | object | false | | |
| » `[any property]` | string | false | | |
| `git_auth_configs` | integer | false | | Git auth configs stores the number of Git configurations the Coder deployment has. If this number is >0, we set up special configuration in the workspace. |
| `motd_file` | string | false | | |
| `startup_script` | string | false | | |
| `startup_script_timeout` | integer | false | | |
| `vscode_port_proxy_uri` | string | false | | |
| Name | Type | Required | Restrictions | Description |
| ------------------------- | ------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apps` | array of [codersdk.WorkspaceApp](#codersdkworkspaceapp) | false | | |
| `derpmap` | [tailcfg.DERPMap](#tailcfgderpmap) | false | | |
| `directory` | string | false | | |
| `environment_variables` | object | false | | |
| » `[any property]` | string | false | | |
| `git_auth_configs` | integer | false | | Git auth configs stores the number of Git configurations the Coder deployment has. If this number is >0, we set up special configuration in the workspace. |
| `motd_file` | string | false | | |
| `shutdown_script` | string | false | | |
| `shutdown_script_timeout` | integer | false | | |
| `startup_script` | string | false | | |
| `startup_script_timeout` | integer | false | | |
| `vscode_port_proxy_uri` | string | false | | |
## agentsdk.PostAppHealthsRequest
@ -5357,6 +5361,8 @@ Parameter represents a set value for the scope.
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
@ -5480,6 +5486,8 @@ Parameter represents a set value for the scope.
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
@ -5491,34 +5499,36 @@ Parameter represents a set value for the scope.
### Properties
| Name | Type | Required | Restrictions | Description |
| -------------------------------- | -------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apps` | array of [codersdk.WorkspaceApp](#codersdkworkspaceapp) | false | | |
| `architecture` | string | false | | |
| `connection_timeout_seconds` | integer | false | | |
| `created_at` | string | false | | |
| `directory` | string | false | | |
| `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 | | |
| `last_connected_at` | string | false | | |
| `latency` | object | false | | Latency is mapped by region name (e.g. "New York City", "Seattle"). |
| » `[any property]` | [codersdk.DERPRegion](#codersdkderpregion) | false | | |
| `lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](#codersdkworkspaceagentlifecycle) | false | | |
| `login_before_ready` | boolean | false | | Login before ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). |
| `name` | string | false | | |
| `operating_system` | string | false | | |
| `resource_id` | string | false | | |
| `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 | | |
| `troubleshooting_url` | string | false | | |
| `updated_at` | string | false | | |
| `version` | string | false | | |
| Name | Type | Required | Restrictions | Description |
| --------------------------------- | -------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apps` | array of [codersdk.WorkspaceApp](#codersdkworkspaceapp) | false | | |
| `architecture` | string | false | | |
| `connection_timeout_seconds` | integer | false | | |
| `created_at` | string | false | | |
| `directory` | string | false | | |
| `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 | | |
| `last_connected_at` | string | false | | |
| `latency` | object | false | | Latency is mapped by region name (e.g. "New York City", "Seattle"). |
| » `[any property]` | [codersdk.DERPRegion](#codersdkderpregion) | false | | |
| `lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](#codersdkworkspaceagentlifecycle) | false | | |
| `login_before_ready` | boolean | false | | Login before ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). |
| `name` | string | false | | |
| `operating_system` | string | false | | |
| `resource_id` | string | false | | |
| `shutdown_script` | string | false | | |
| `shutdown_script_timeout_seconds` | integer | false | | |
| `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 | | |
| `troubleshooting_url` | string | false | | |
| `updated_at` | string | false | | |
| `version` | string | false | | |
## codersdk.WorkspaceAgentConnectionInfo
@ -5594,13 +5604,17 @@ Parameter represents a set value for the scope.
#### Enumerated Values
| Value |
| --------------- |
| `created` |
| `starting` |
| `start_timeout` |
| `start_error` |
| `ready` |
| Value |
| ------------------ |
| `created` |
| `starting` |
| `start_timeout` |
| `start_error` |
| `ready` |
| `shutting_down` |
| `shutdown_timeout` |
| `shutdown_error` |
| `off` |
## codersdk.WorkspaceAgentListeningPort
@ -5815,6 +5829,8 @@ Parameter represents a set value for the scope.
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
@ -5984,6 +6000,8 @@ Parameter represents a set value for the scope.
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
@ -6175,6 +6193,8 @@ Parameter represents a set value for the scope.
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",

View File

@ -1601,6 +1601,8 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/d
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
@ -1639,89 +1641,95 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/d
Status Code **200**
| Name | Type | Required | Restrictions | Description |
| ----------------------------------- | -------------------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `[array item]` | array | false | | |
| `» agents` | array | false | | |
| `»» apps` | array | false | | |
| `»»» command` | string | false | | |
| `»»» display_name` | string | false | | »»display name is a friendly name for the app. |
| `»»» external` | boolean | false | | External specifies whether the URL should be opened externally on the client or not. |
| `»»» health` | [codersdk.WorkspaceAppHealth](schemas.md#codersdkworkspaceapphealth) | false | | |
| `»»» healthcheck` | [codersdk.Healthcheck](schemas.md#codersdkhealthcheck) | false | | Healthcheck specifies the configuration for checking app health. |
| `»»»» interval` | integer | false | | Interval specifies the seconds between each health check. |
| `»»»» threshold` | integer | false | | Threshold specifies the number of consecutive failed health checks before returning "unhealthy". |
| `»»»» url` | string | false | | »»»url specifies the endpoint to check for the app health. |
| `»»» icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. |
| `»»» id` | string(uuid) | false | | |
| `»»» sharing_level` | [codersdk.WorkspaceAppSharingLevel](schemas.md#codersdkworkspaceappsharinglevel) | false | | |
| `»»» slug` | string | false | | Slug is a unique identifier within the agent. |
| `»»» subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. |
| `»»» url` | string | false | | »»url is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. |
| `»» architecture` | string | false | | |
| `»» connection_timeout_seconds` | integer | false | | |
| `»» created_at` | string(date-time) | false | | |
| `»» directory` | string | false | | |
| `»» 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 | | |
| `»» last_connected_at` | string(date-time) | false | | |
| `»» latency` | object | false | | »latency is mapped by region name (e.g. "New York City", "Seattle"). |
| `»»» [any property]` | [codersdk.DERPRegion](schemas.md#codersdkderpregion) | false | | |
| `»»»» latency_ms` | number | false | | |
| `»»»» preferred` | boolean | false | | |
| `»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | |
| `»» login_before_ready` | boolean | false | | »login before ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). |
| `»» name` | string | false | | |
| `»» operating_system` | string | false | | |
| `»» resource_id` | string(uuid) | false | | |
| `»» 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 | | |
| `»» troubleshooting_url` | string | false | | |
| `»» updated_at` | string(date-time) | false | | |
| `»» version` | string | false | | |
| `» created_at` | string(date-time) | false | | |
| `» daily_cost` | integer | false | | |
| `» hide` | boolean | false | | |
| `» icon` | string | false | | |
| `» id` | string(uuid) | false | | |
| `» job_id` | string(uuid) | false | | |
| `» metadata` | array | false | | |
| `»» key` | string | false | | |
| `»» sensitive` | boolean | false | | |
| `»» value` | string | false | | |
| `» name` | string | false | | |
| `» type` | string | false | | |
| `» workspace_transition` | [codersdk.WorkspaceTransition](schemas.md#codersdkworkspacetransition) | false | | |
| Name | Type | Required | Restrictions | Description |
| ------------------------------------ | -------------------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `[array item]` | array | false | | |
| `» agents` | array | false | | |
| `»» apps` | array | false | | |
| `»»» command` | string | false | | |
| `»»» display_name` | string | false | | »»display name is a friendly name for the app. |
| `»»» external` | boolean | false | | External specifies whether the URL should be opened externally on the client or not. |
| `»»» health` | [codersdk.WorkspaceAppHealth](schemas.md#codersdkworkspaceapphealth) | false | | |
| `»»» healthcheck` | [codersdk.Healthcheck](schemas.md#codersdkhealthcheck) | false | | Healthcheck specifies the configuration for checking app health. |
| `»»»» interval` | integer | false | | Interval specifies the seconds between each health check. |
| `»»»» threshold` | integer | false | | Threshold specifies the number of consecutive failed health checks before returning "unhealthy". |
| `»»»» url` | string | false | | »»»url specifies the endpoint to check for the app health. |
| `»»» icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. |
| `»»» id` | string(uuid) | false | | |
| `»»» sharing_level` | [codersdk.WorkspaceAppSharingLevel](schemas.md#codersdkworkspaceappsharinglevel) | false | | |
| `»»» slug` | string | false | | Slug is a unique identifier within the agent. |
| `»»» subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. |
| `»»» url` | string | false | | »»url is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. |
| `»» architecture` | string | false | | |
| `»» connection_timeout_seconds` | integer | false | | |
| `»» created_at` | string(date-time) | false | | |
| `»» directory` | string | false | | |
| `»» 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 | | |
| `»» last_connected_at` | string(date-time) | false | | |
| `»» latency` | object | false | | »latency is mapped by region name (e.g. "New York City", "Seattle"). |
| `»»» [any property]` | [codersdk.DERPRegion](schemas.md#codersdkderpregion) | false | | |
| `»»»» latency_ms` | number | false | | |
| `»»»» preferred` | boolean | false | | |
| `»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | |
| `»» login_before_ready` | boolean | false | | »login before ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). |
| `»» name` | string | false | | |
| `»» operating_system` | string | false | | |
| `»» resource_id` | string(uuid) | false | | |
| `»» shutdown_script` | string | false | | |
| `»» shutdown_script_timeout_seconds` | integer | false | | |
| `»» 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 | | |
| `»» troubleshooting_url` | string | false | | |
| `»» updated_at` | string(date-time) | false | | |
| `»» version` | string | false | | |
| `» created_at` | string(date-time) | false | | |
| `» daily_cost` | integer | false | | |
| `» hide` | boolean | false | | |
| `» icon` | string | false | | |
| `» id` | string(uuid) | false | | |
| `» job_id` | string(uuid) | false | | |
| `» metadata` | array | false | | |
| `»» key` | string | false | | |
| `»» sensitive` | boolean | false | | |
| `»» value` | string | false | | |
| `» name` | string | false | | |
| `» type` | string | false | | |
| `» workspace_transition` | [codersdk.WorkspaceTransition](schemas.md#codersdkworkspacetransition) | false | | |
#### Enumerated Values
| Property | Value |
| ---------------------- | --------------- |
| `health` | `disabled` |
| `health` | `initializing` |
| `health` | `healthy` |
| `health` | `unhealthy` |
| `sharing_level` | `owner` |
| `sharing_level` | `authenticated` |
| `sharing_level` | `public` |
| `lifecycle_state` | `created` |
| `lifecycle_state` | `starting` |
| `lifecycle_state` | `start_timeout` |
| `lifecycle_state` | `start_error` |
| `lifecycle_state` | `ready` |
| `status` | `connecting` |
| `status` | `connected` |
| `status` | `disconnected` |
| `status` | `timeout` |
| `workspace_transition` | `start` |
| `workspace_transition` | `stop` |
| `workspace_transition` | `delete` |
| Property | Value |
| ---------------------- | ------------------ |
| `health` | `disabled` |
| `health` | `initializing` |
| `health` | `healthy` |
| `health` | `unhealthy` |
| `sharing_level` | `owner` |
| `sharing_level` | `authenticated` |
| `sharing_level` | `public` |
| `lifecycle_state` | `created` |
| `lifecycle_state` | `starting` |
| `lifecycle_state` | `start_timeout` |
| `lifecycle_state` | `start_error` |
| `lifecycle_state` | `ready` |
| `lifecycle_state` | `shutting_down` |
| `lifecycle_state` | `shutdown_timeout` |
| `lifecycle_state` | `shutdown_error` |
| `lifecycle_state` | `off` |
| `status` | `connecting` |
| `status` | `connected` |
| `status` | `disconnected` |
| `status` | `timeout` |
| `workspace_transition` | `start` |
| `workspace_transition` | `stop` |
| `workspace_transition` | `delete` |
To perform this operation, you must be authenticated. [Learn more](authentication.md).
@ -2018,6 +2026,8 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/r
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
@ -2056,89 +2066,95 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/r
Status Code **200**
| Name | Type | Required | Restrictions | Description |
| ----------------------------------- | -------------------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `[array item]` | array | false | | |
| `» agents` | array | false | | |
| `»» apps` | array | false | | |
| `»»» command` | string | false | | |
| `»»» display_name` | string | false | | »»display name is a friendly name for the app. |
| `»»» external` | boolean | false | | External specifies whether the URL should be opened externally on the client or not. |
| `»»» health` | [codersdk.WorkspaceAppHealth](schemas.md#codersdkworkspaceapphealth) | false | | |
| `»»» healthcheck` | [codersdk.Healthcheck](schemas.md#codersdkhealthcheck) | false | | Healthcheck specifies the configuration for checking app health. |
| `»»»» interval` | integer | false | | Interval specifies the seconds between each health check. |
| `»»»» threshold` | integer | false | | Threshold specifies the number of consecutive failed health checks before returning "unhealthy". |
| `»»»» url` | string | false | | »»»url specifies the endpoint to check for the app health. |
| `»»» icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. |
| `»»» id` | string(uuid) | false | | |
| `»»» sharing_level` | [codersdk.WorkspaceAppSharingLevel](schemas.md#codersdkworkspaceappsharinglevel) | false | | |
| `»»» slug` | string | false | | Slug is a unique identifier within the agent. |
| `»»» subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. |
| `»»» url` | string | false | | »»url is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. |
| `»» architecture` | string | false | | |
| `»» connection_timeout_seconds` | integer | false | | |
| `»» created_at` | string(date-time) | false | | |
| `»» directory` | string | false | | |
| `»» 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 | | |
| `»» last_connected_at` | string(date-time) | false | | |
| `»» latency` | object | false | | »latency is mapped by region name (e.g. "New York City", "Seattle"). |
| `»»» [any property]` | [codersdk.DERPRegion](schemas.md#codersdkderpregion) | false | | |
| `»»»» latency_ms` | number | false | | |
| `»»»» preferred` | boolean | false | | |
| `»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | |
| `»» login_before_ready` | boolean | false | | »login before ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). |
| `»» name` | string | false | | |
| `»» operating_system` | string | false | | |
| `»» resource_id` | string(uuid) | false | | |
| `»» 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 | | |
| `»» troubleshooting_url` | string | false | | |
| `»» updated_at` | string(date-time) | false | | |
| `»» version` | string | false | | |
| `» created_at` | string(date-time) | false | | |
| `» daily_cost` | integer | false | | |
| `» hide` | boolean | false | | |
| `» icon` | string | false | | |
| `» id` | string(uuid) | false | | |
| `» job_id` | string(uuid) | false | | |
| `» metadata` | array | false | | |
| `»» key` | string | false | | |
| `»» sensitive` | boolean | false | | |
| `»» value` | string | false | | |
| `» name` | string | false | | |
| `» type` | string | false | | |
| `» workspace_transition` | [codersdk.WorkspaceTransition](schemas.md#codersdkworkspacetransition) | false | | |
| Name | Type | Required | Restrictions | Description |
| ------------------------------------ | -------------------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `[array item]` | array | false | | |
| `» agents` | array | false | | |
| `»» apps` | array | false | | |
| `»»» command` | string | false | | |
| `»»» display_name` | string | false | | »»display name is a friendly name for the app. |
| `»»» external` | boolean | false | | External specifies whether the URL should be opened externally on the client or not. |
| `»»» health` | [codersdk.WorkspaceAppHealth](schemas.md#codersdkworkspaceapphealth) | false | | |
| `»»» healthcheck` | [codersdk.Healthcheck](schemas.md#codersdkhealthcheck) | false | | Healthcheck specifies the configuration for checking app health. |
| `»»»» interval` | integer | false | | Interval specifies the seconds between each health check. |
| `»»»» threshold` | integer | false | | Threshold specifies the number of consecutive failed health checks before returning "unhealthy". |
| `»»»» url` | string | false | | »»»url specifies the endpoint to check for the app health. |
| `»»» icon` | string | false | | Icon is a relative path or external URL that specifies an icon to be displayed in the dashboard. |
| `»»» id` | string(uuid) | false | | |
| `»»» sharing_level` | [codersdk.WorkspaceAppSharingLevel](schemas.md#codersdkworkspaceappsharinglevel) | false | | |
| `»»» slug` | string | false | | Slug is a unique identifier within the agent. |
| `»»» subdomain` | boolean | false | | Subdomain denotes whether the app should be accessed via a path on the `coder server` or via a hostname-based dev URL. If this is set to true and there is no app wildcard configured on the server, the app will not be accessible in the UI. |
| `»»» url` | string | false | | »»url is the address being proxied to inside the workspace. If external is specified, this will be opened on the client. |
| `»» architecture` | string | false | | |
| `»» connection_timeout_seconds` | integer | false | | |
| `»» created_at` | string(date-time) | false | | |
| `»» directory` | string | false | | |
| `»» 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 | | |
| `»» last_connected_at` | string(date-time) | false | | |
| `»» latency` | object | false | | »latency is mapped by region name (e.g. "New York City", "Seattle"). |
| `»»» [any property]` | [codersdk.DERPRegion](schemas.md#codersdkderpregion) | false | | |
| `»»»» latency_ms` | number | false | | |
| `»»»» preferred` | boolean | false | | |
| `»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | |
| `»» login_before_ready` | boolean | false | | »login before ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). |
| `»» name` | string | false | | |
| `»» operating_system` | string | false | | |
| `»» resource_id` | string(uuid) | false | | |
| `»» shutdown_script` | string | false | | |
| `»» shutdown_script_timeout_seconds` | integer | false | | |
| `»» 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 | | |
| `»» troubleshooting_url` | string | false | | |
| `»» updated_at` | string(date-time) | false | | |
| `»» version` | string | false | | |
| `» created_at` | string(date-time) | false | | |
| `» daily_cost` | integer | false | | |
| `» hide` | boolean | false | | |
| `» icon` | string | false | | |
| `» id` | string(uuid) | false | | |
| `» job_id` | string(uuid) | false | | |
| `» metadata` | array | false | | |
| `»» key` | string | false | | |
| `»» sensitive` | boolean | false | | |
| `»» value` | string | false | | |
| `» name` | string | false | | |
| `» type` | string | false | | |
| `» workspace_transition` | [codersdk.WorkspaceTransition](schemas.md#codersdkworkspacetransition) | false | | |
#### Enumerated Values
| Property | Value |
| ---------------------- | --------------- |
| `health` | `disabled` |
| `health` | `initializing` |
| `health` | `healthy` |
| `health` | `unhealthy` |
| `sharing_level` | `owner` |
| `sharing_level` | `authenticated` |
| `sharing_level` | `public` |
| `lifecycle_state` | `created` |
| `lifecycle_state` | `starting` |
| `lifecycle_state` | `start_timeout` |
| `lifecycle_state` | `start_error` |
| `lifecycle_state` | `ready` |
| `status` | `connecting` |
| `status` | `connected` |
| `status` | `disconnected` |
| `status` | `timeout` |
| `workspace_transition` | `start` |
| `workspace_transition` | `stop` |
| `workspace_transition` | `delete` |
| Property | Value |
| ---------------------- | ------------------ |
| `health` | `disabled` |
| `health` | `initializing` |
| `health` | `healthy` |
| `health` | `unhealthy` |
| `sharing_level` | `owner` |
| `sharing_level` | `authenticated` |
| `sharing_level` | `public` |
| `lifecycle_state` | `created` |
| `lifecycle_state` | `starting` |
| `lifecycle_state` | `start_timeout` |
| `lifecycle_state` | `start_error` |
| `lifecycle_state` | `ready` |
| `lifecycle_state` | `shutting_down` |
| `lifecycle_state` | `shutdown_timeout` |
| `lifecycle_state` | `shutdown_error` |
| `lifecycle_state` | `off` |
| `status` | `connecting` |
| `status` | `connected` |
| `status` | `disconnected` |
| `status` | `timeout` |
| `workspace_transition` | `start` |
| `workspace_transition` | `stop` |
| `workspace_transition` | `delete` |
To perform this operation, you must be authenticated. [Learn more](authentication.md).

View File

@ -134,6 +134,8 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
@ -299,6 +301,8 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
@ -483,6 +487,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",
@ -649,6 +655,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \
"name": "string",
"operating_system": "string",
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
"shutdown_script": "string",
"shutdown_script_timeout_seconds": 0,
"startup_script": "string",
"startup_script_timeout_seconds": 0,
"status": "connecting",

View File

@ -444,8 +444,8 @@ practices:
URL](./admin/configure.md#access-url)
- Manually connect to the resource and check the agent logs (e.g., `kubectl exec`, `docker exec` or AWS console)
- The Coder agent logs are typically stored in `/tmp/coder-agent.log`
- The Coder agent startup script logs are typically stored in
`/tmp/coder-startup-script.log`
- The Coder agent startup script logs are typically stored in `/tmp/coder-startup-script.log`
- The Coder agent shutdown script logs are typically stored in `/tmp/coder-shutdown-script.log`
- This can also happen if the websockets are not being forwarded correctly when running Coder behind a reverse proxy. [Read our reverse-proxy docs](https://coder.com/docs/v2/latest/admin/configure#tls--reverse-proxy)
### Agent does not become ready

View File

@ -84,10 +84,11 @@ coder update <your workspace name> --always-prompt
Coder stores macOS and Linux logs at the following locations:
| Service | Location |
| ---------------- | ------------------------------- |
| `startup_script` | `/tmp/coder-startup-script.log` |
| Agent | `/tmp/coder-agent.log` |
| Service | Location |
| ----------------- | -------------------------------- |
| `startup_script` | `/tmp/coder-startup-script.log` |
| `shutdown_script` | `/tmp/coder-shutdown-script.log` |
| Agent | `/tmp/coder-agent.log` |
---

View File

@ -16,19 +16,21 @@ import (
// A mapping of attributes on the "coder_agent" resource.
type agentAttributes struct {
Auth string `mapstructure:"auth"`
OperatingSystem string `mapstructure:"os"`
Architecture string `mapstructure:"arch"`
Directory string `mapstructure:"dir"`
ID string `mapstructure:"id"`
Token string `mapstructure:"token"`
Env map[string]string `mapstructure:"env"`
StartupScript string `mapstructure:"startup_script"`
ConnectionTimeoutSeconds int32 `mapstructure:"connection_timeout"`
TroubleshootingURL string `mapstructure:"troubleshooting_url"`
MOTDFile string `mapstructure:"motd_file"`
LoginBeforeReady bool `mapstructure:"login_before_ready"`
StartupScriptTimeoutSeconds int32 `mapstructure:"startup_script_timeout"`
Auth string `mapstructure:"auth"`
OperatingSystem string `mapstructure:"os"`
Architecture string `mapstructure:"arch"`
Directory string `mapstructure:"dir"`
ID string `mapstructure:"id"`
Token string `mapstructure:"token"`
Env map[string]string `mapstructure:"env"`
StartupScript string `mapstructure:"startup_script"`
ConnectionTimeoutSeconds int32 `mapstructure:"connection_timeout"`
TroubleshootingURL string `mapstructure:"troubleshooting_url"`
MOTDFile string `mapstructure:"motd_file"`
LoginBeforeReady bool `mapstructure:"login_before_ready"`
StartupScriptTimeoutSeconds int32 `mapstructure:"startup_script_timeout"`
ShutdownScript string `mapstructure:"shutdown_script"`
ShutdownScriptTimeoutSeconds int32 `mapstructure:"shutdown_script_timeout"`
}
// A mapping of attributes on the "coder_app" resource.
@ -139,18 +141,20 @@ func ConvertState(modules []*tfjson.StateModule, rawGraph string) (*State, error
}
agent := &proto.Agent{
Name: tfResource.Name,
Id: attrs.ID,
Env: attrs.Env,
StartupScript: attrs.StartupScript,
OperatingSystem: attrs.OperatingSystem,
Architecture: attrs.Architecture,
Directory: attrs.Directory,
ConnectionTimeoutSeconds: attrs.ConnectionTimeoutSeconds,
TroubleshootingUrl: attrs.TroubleshootingURL,
MotdFile: attrs.MOTDFile,
LoginBeforeReady: loginBeforeReady,
StartupScriptTimeoutSeconds: attrs.StartupScriptTimeoutSeconds,
Name: tfResource.Name,
Id: attrs.ID,
Env: attrs.Env,
StartupScript: attrs.StartupScript,
OperatingSystem: attrs.OperatingSystem,
Architecture: attrs.Architecture,
Directory: attrs.Directory,
ConnectionTimeoutSeconds: attrs.ConnectionTimeoutSeconds,
TroubleshootingUrl: attrs.TroubleshootingURL,
MotdFile: attrs.MOTDFile,
LoginBeforeReady: loginBeforeReady,
StartupScriptTimeoutSeconds: attrs.StartupScriptTimeoutSeconds,
ShutdownScript: attrs.ShutdownScript,
ShutdownScriptTimeoutSeconds: attrs.ShutdownScriptTimeoutSeconds,
}
switch attrs.Auth {
case "token":

View File

@ -105,31 +105,35 @@ func TestConvertResources(t *testing.T) {
Name: "dev",
Type: "null_resource",
Agents: []*proto.Agent{{
Name: "dev1",
OperatingSystem: "linux",
Architecture: "amd64",
Auth: &proto.Agent_Token{},
ConnectionTimeoutSeconds: 120,
LoginBeforeReady: true,
StartupScriptTimeoutSeconds: 300,
Name: "dev1",
OperatingSystem: "linux",
Architecture: "amd64",
Auth: &proto.Agent_Token{},
ConnectionTimeoutSeconds: 120,
LoginBeforeReady: true,
StartupScriptTimeoutSeconds: 300,
ShutdownScriptTimeoutSeconds: 300,
}, {
Name: "dev2",
OperatingSystem: "darwin",
Architecture: "amd64",
Auth: &proto.Agent_Token{},
ConnectionTimeoutSeconds: 1,
MotdFile: "/etc/motd",
LoginBeforeReady: true,
StartupScriptTimeoutSeconds: 30,
Name: "dev2",
OperatingSystem: "darwin",
Architecture: "amd64",
Auth: &proto.Agent_Token{},
ConnectionTimeoutSeconds: 1,
MotdFile: "/etc/motd",
LoginBeforeReady: true,
StartupScriptTimeoutSeconds: 30,
ShutdownScript: "echo bye bye",
ShutdownScriptTimeoutSeconds: 30,
}, {
Name: "dev3",
OperatingSystem: "windows",
Architecture: "arm64",
Auth: &proto.Agent_Token{},
ConnectionTimeoutSeconds: 120,
TroubleshootingUrl: "https://coder.com/troubleshoot",
LoginBeforeReady: false,
StartupScriptTimeoutSeconds: 300,
Name: "dev3",
OperatingSystem: "windows",
Architecture: "arm64",
Auth: &proto.Agent_Token{},
ConnectionTimeoutSeconds: 120,
TroubleshootingUrl: "https://coder.com/troubleshoot",
LoginBeforeReady: false,
StartupScriptTimeoutSeconds: 300,
ShutdownScriptTimeoutSeconds: 300,
}},
}},
},
@ -300,13 +304,14 @@ func TestConvertResources(t *testing.T) {
Name: "dev",
Type: "null_resource",
Agents: []*proto.Agent{{
Name: "main",
OperatingSystem: "linux",
Architecture: "amd64",
Auth: &proto.Agent_Token{},
LoginBeforeReady: true,
ConnectionTimeoutSeconds: 120,
StartupScriptTimeoutSeconds: 300,
Name: "main",
OperatingSystem: "linux",
Architecture: "amd64",
Auth: &proto.Agent_Token{},
LoginBeforeReady: true,
ConnectionTimeoutSeconds: 120,
StartupScriptTimeoutSeconds: 300,
ShutdownScriptTimeoutSeconds: 300,
}},
}},
gitAuthProviders: []string{"github", "gitlab"},

View File

@ -2,7 +2,7 @@ terraform {
required_providers {
coder = {
source = "coder/coder"
version = "0.6.10"
version = "0.6.12"
}
}
}
@ -13,12 +13,14 @@ resource "coder_agent" "dev1" {
}
resource "coder_agent" "dev2" {
os = "darwin"
arch = "amd64"
connection_timeout = 1
motd_file = "/etc/motd"
startup_script_timeout = 30
login_before_ready = true
os = "darwin"
arch = "amd64"
connection_timeout = 1
motd_file = "/etc/motd"
startup_script_timeout = 30
login_before_ready = true
shutdown_script = "echo bye bye"
shutdown_script_timeout = 30
}
resource "coder_agent" "dev3" {

View File

@ -21,6 +21,7 @@
"motd_file": null,
"os": "linux",
"shutdown_script": null,
"shutdown_script_timeout": 300,
"startup_script": null,
"startup_script_timeout": 300,
"troubleshooting_url": null
@ -43,7 +44,8 @@
"login_before_ready": true,
"motd_file": "/etc/motd",
"os": "darwin",
"shutdown_script": null,
"shutdown_script": "echo bye bye",
"shutdown_script_timeout": 30,
"startup_script": null,
"startup_script_timeout": 30,
"troubleshooting_url": null
@ -67,6 +69,7 @@
"motd_file": null,
"os": "windows",
"shutdown_script": null,
"shutdown_script_timeout": 300,
"startup_script": null,
"startup_script_timeout": 300,
"troubleshooting_url": "https://coder.com/troubleshoot"
@ -110,6 +113,7 @@
"motd_file": null,
"os": "linux",
"shutdown_script": null,
"shutdown_script_timeout": 300,
"startup_script": null,
"startup_script_timeout": 300,
"troubleshooting_url": null
@ -145,7 +149,8 @@
"login_before_ready": true,
"motd_file": "/etc/motd",
"os": "darwin",
"shutdown_script": null,
"shutdown_script": "echo bye bye",
"shutdown_script_timeout": 30,
"startup_script": null,
"startup_script_timeout": 30,
"troubleshooting_url": null
@ -182,6 +187,7 @@
"motd_file": null,
"os": "windows",
"shutdown_script": null,
"shutdown_script_timeout": 300,
"startup_script": null,
"startup_script_timeout": 300,
"troubleshooting_url": "https://coder.com/troubleshoot"
@ -224,7 +230,7 @@
"coder": {
"name": "coder",
"full_name": "registry.terraform.io/coder/coder",
"version_constraint": "0.6.10"
"version_constraint": "0.6.12"
},
"null": {
"name": "null",
@ -271,6 +277,12 @@
"os": {
"constant_value": "darwin"
},
"shutdown_script": {
"constant_value": "echo bye bye"
},
"shutdown_script_timeout": {
"constant_value": 30
},
"startup_script_timeout": {
"constant_value": 30
}

View File

@ -23,6 +23,7 @@
"motd_file": null,
"os": "linux",
"shutdown_script": null,
"shutdown_script_timeout": 300,
"startup_script": null,
"startup_script_timeout": 300,
"token": "d296a9cd-6f7c-4c6b-b2f3-7a647512efe8",
@ -48,7 +49,8 @@
"login_before_ready": true,
"motd_file": "/etc/motd",
"os": "darwin",
"shutdown_script": null,
"shutdown_script": "echo bye bye",
"shutdown_script_timeout": 30,
"startup_script": null,
"startup_script_timeout": 30,
"token": "b1e0fba4-5bba-439f-b3ea-3f6a8ba4d301",
@ -75,6 +77,7 @@
"motd_file": null,
"os": "windows",
"shutdown_script": null,
"shutdown_script_timeout": 300,
"startup_script": null,
"startup_script_timeout": 300,
"token": "238ff017-12ae-403f-b3f8-4dea4dc87a7d",

View File

@ -1236,12 +1236,14 @@ type Agent struct {
//
// *Agent_Token
// *Agent_InstanceId
Auth isAgent_Auth `protobuf_oneof:"auth"`
ConnectionTimeoutSeconds int32 `protobuf:"varint,11,opt,name=connection_timeout_seconds,json=connectionTimeoutSeconds,proto3" json:"connection_timeout_seconds,omitempty"`
TroubleshootingUrl string `protobuf:"bytes,12,opt,name=troubleshooting_url,json=troubleshootingUrl,proto3" json:"troubleshooting_url,omitempty"`
MotdFile string `protobuf:"bytes,13,opt,name=motd_file,json=motdFile,proto3" json:"motd_file,omitempty"`
LoginBeforeReady bool `protobuf:"varint,14,opt,name=login_before_ready,json=loginBeforeReady,proto3" json:"login_before_ready,omitempty"`
StartupScriptTimeoutSeconds int32 `protobuf:"varint,15,opt,name=startup_script_timeout_seconds,json=startupScriptTimeoutSeconds,proto3" json:"startup_script_timeout_seconds,omitempty"`
Auth isAgent_Auth `protobuf_oneof:"auth"`
ConnectionTimeoutSeconds int32 `protobuf:"varint,11,opt,name=connection_timeout_seconds,json=connectionTimeoutSeconds,proto3" json:"connection_timeout_seconds,omitempty"`
TroubleshootingUrl string `protobuf:"bytes,12,opt,name=troubleshooting_url,json=troubleshootingUrl,proto3" json:"troubleshooting_url,omitempty"`
MotdFile string `protobuf:"bytes,13,opt,name=motd_file,json=motdFile,proto3" json:"motd_file,omitempty"`
LoginBeforeReady bool `protobuf:"varint,14,opt,name=login_before_ready,json=loginBeforeReady,proto3" json:"login_before_ready,omitempty"`
StartupScriptTimeoutSeconds int32 `protobuf:"varint,15,opt,name=startup_script_timeout_seconds,json=startupScriptTimeoutSeconds,proto3" json:"startup_script_timeout_seconds,omitempty"`
ShutdownScript string `protobuf:"bytes,16,opt,name=shutdown_script,json=shutdownScript,proto3" json:"shutdown_script,omitempty"`
ShutdownScriptTimeoutSeconds int32 `protobuf:"varint,17,opt,name=shutdown_script_timeout_seconds,json=shutdownScriptTimeoutSeconds,proto3" json:"shutdown_script_timeout_seconds,omitempty"`
}
func (x *Agent) Reset() {
@ -1388,6 +1390,20 @@ func (x *Agent) GetStartupScriptTimeoutSeconds() int32 {
return 0
}
func (x *Agent) GetShutdownScript() string {
if x != nil {
return x.ShutdownScript
}
return ""
}
func (x *Agent) GetShutdownScriptTimeoutSeconds() int32 {
if x != nil {
return x.ShutdownScriptTimeoutSeconds
}
return 0
}
type isAgent_Auth interface {
isAgent_Auth()
}
@ -2774,7 +2790,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{
0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12,
0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b,
0x65, 0x6e, 0x22, 0x8e, 0x05, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02,
0x65, 0x6e, 0x22, 0xfe, 0x05, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02,
0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04,
0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x12, 0x2d, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e,
@ -2811,6 +2827,13 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{
0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73,
0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x1b, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x53,
0x63, 0x72, 0x69, 0x70, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f,
0x6e, 0x64, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x5f,
0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x68,
0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x45, 0x0a, 0x1f,
0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f,
0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18,
0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x1c, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x53,
0x63, 0x72, 0x69, 0x70, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f,
0x6e, 0x64, 0x73, 0x1a, 0x36, 0x0a, 0x08, 0x45, 0x6e, 0x76, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,
0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,

View File

@ -142,6 +142,8 @@ message Agent {
string motd_file = 13;
bool login_before_ready = 14;
int32 startup_script_timeout_seconds = 15;
string shutdown_script = 16;
int32 shutdown_script_timeout_seconds = 17;
}
enum AppSharingLevel {

View File

@ -994,6 +994,8 @@ export interface WorkspaceAgent {
readonly troubleshooting_url: string
readonly login_before_ready: boolean
readonly startup_script_timeout_seconds: number
readonly shutdown_script?: string
readonly shutdown_script_timeout_seconds: number
}
// From codersdk/workspaceagentconn.go
@ -1295,13 +1297,21 @@ export const ValidationMonotonicOrders: ValidationMonotonicOrder[] = [
// From codersdk/workspaceagents.go
export type WorkspaceAgentLifecycle =
| "created"
| "off"
| "ready"
| "shutdown_error"
| "shutdown_timeout"
| "shutting_down"
| "start_error"
| "start_timeout"
| "starting"
export const WorkspaceAgentLifecycles: WorkspaceAgentLifecycle[] = [
"created",
"off",
"ready",
"shutdown_error",
"shutdown_timeout",
"shutting_down",
"start_error",
"start_timeout",
"starting",

View File

@ -3,7 +3,11 @@ import {
MockWorkspace,
MockWorkspaceAgent,
MockWorkspaceAgentConnecting,
MockWorkspaceAgentOff,
MockWorkspaceAgentOutdated,
MockWorkspaceAgentShutdownError,
MockWorkspaceAgentShutdownTimeout,
MockWorkspaceAgentShuttingDown,
MockWorkspaceAgentStartError,
MockWorkspaceAgentStarting,
MockWorkspaceAgentStartTimeout,
@ -114,6 +118,38 @@ StartError.args = {
showApps: true,
}
export const ShuttingDown = Template.bind({})
ShuttingDown.args = {
agent: MockWorkspaceAgentShuttingDown,
workspace: MockWorkspace,
applicationsHost: "",
showApps: true,
}
export const ShutdownTimeout = Template.bind({})
ShutdownTimeout.args = {
agent: MockWorkspaceAgentShutdownTimeout,
workspace: MockWorkspace,
applicationsHost: "",
showApps: true,
}
export const ShutdownError = Template.bind({})
ShutdownError.args = {
agent: MockWorkspaceAgentShutdownError,
workspace: MockWorkspace,
applicationsHost: "",
showApps: true,
}
export const Off = Template.bind({})
Off.args = {
agent: MockWorkspaceAgentOff,
workspace: MockWorkspace,
applicationsHost: "",
showApps: true,
}
export const ShowingPortForward = Template.bind({})
ShowingPortForward.args = {
agent: MockWorkspaceAgent,

View File

@ -16,9 +16,10 @@ import Link from "@material-ui/core/Link"
// If we think in the agent status and lifecycle into a single enum/state Id
// say we would have: connecting, timeout, disconnected, connected:created,
// connected:starting, connected:start_timeout, connected:start_error,
// connected:ready
// connected:ready, connected:shutting_down, connected:shutdown_timeout,
// connected:shutdown_error, connected:off.
const ReadyLifeCycle: React.FC = () => {
const ReadyLifecycle: React.FC = () => {
const styles = useStyles()
const { t } = useTranslation("workspacePage")
@ -132,6 +133,122 @@ const StartErrorLifecycle: React.FC<{
)
}
const ShuttingDownLifecycle: React.FC = () => {
const styles = useStyles()
const { t } = useTranslation("workspacePage")
return (
<Tooltip title={t("agentStatus.connected.shuttingDown")}>
<div
role="status"
aria-label={t("agentStatus.connected.shuttingDown")}
className={combineClasses([styles.status, styles.connecting])}
/>
</Tooltip>
)
}
const ShutdownTimeoutLifecycle: React.FC<{
agent: WorkspaceAgent
}> = ({ agent }) => {
const { t } = useTranslation("agent")
const styles = useStyles()
const anchorRef = useRef<SVGSVGElement>(null)
const [isOpen, setIsOpen] = useState(false)
const id = isOpen ? "timeout-popover" : undefined
return (
<>
<WarningRounded
ref={anchorRef}
onMouseEnter={() => setIsOpen(true)}
onMouseLeave={() => setIsOpen(false)}
role="status"
aria-label={t("status.shutdownTimeout")}
className={styles.timeoutWarning}
/>
<HelpPopover
id={id}
open={isOpen}
anchorEl={anchorRef.current}
onOpen={() => setIsOpen(true)}
onClose={() => setIsOpen(false)}
>
<HelpTooltipTitle>{t("shutdownTimeoutTooltip.title")}</HelpTooltipTitle>
<HelpTooltipText>
{t("shutdownTimeoutTooltip.message")}{" "}
<Link
target="_blank"
rel="noreferrer"
href={agent.troubleshooting_url}
>
{t("shutdownTimeoutTooltip.link")}
</Link>
.
</HelpTooltipText>
</HelpPopover>
</>
)
}
const ShutdownErrorLifecycle: React.FC<{
agent: WorkspaceAgent
}> = ({ agent }) => {
const { t } = useTranslation("agent")
const styles = useStyles()
const anchorRef = useRef<SVGSVGElement>(null)
const [isOpen, setIsOpen] = useState(false)
const id = isOpen ? "timeout-popover" : undefined
return (
<>
<WarningRounded
ref={anchorRef}
onMouseEnter={() => setIsOpen(true)}
onMouseLeave={() => setIsOpen(false)}
role="status"
aria-label={t("status.error")}
className={styles.errorWarning}
/>
<HelpPopover
id={id}
open={isOpen}
anchorEl={anchorRef.current}
onOpen={() => setIsOpen(true)}
onClose={() => setIsOpen(false)}
>
<HelpTooltipTitle>{t("shutdownErrorTooltip.title")}</HelpTooltipTitle>
<HelpTooltipText>
{t("shutdownErrorTooltip.message")}{" "}
<Link
target="_blank"
rel="noreferrer"
href={agent.troubleshooting_url}
>
{t("shutdownErrorTooltip.link")}
</Link>
.
</HelpTooltipText>
</HelpPopover>
</>
)
}
const OffLifecycle: React.FC = () => {
const styles = useStyles()
const { t } = useTranslation("workspacePage")
return (
<Tooltip title={t("agentStatus.connected.off")}>
<div
role="status"
aria-label={t("agentStatus.connected.off")}
className={combineClasses([styles.status, styles.disconnected])}
/>
</Tooltip>
)
}
const ConnectedStatus: React.FC<{
agent: WorkspaceAgent
}> = ({ agent }) => {
@ -143,12 +260,12 @@ const ConnectedStatus: React.FC<{
// release indicating startup script behavior has changed.
// https://github.com/coder/coder/issues/5749
if (agent.login_before_ready) {
return <ReadyLifeCycle />
return <ReadyLifecycle />
}
return (
<ChooseOne>
<Cond condition={agent.lifecycle_state === "ready"}>
<ReadyLifeCycle />
<ReadyLifecycle />
</Cond>
<Cond condition={agent.lifecycle_state === "start_timeout"}>
<StartTimeoutLifecycle agent={agent} />
@ -156,6 +273,18 @@ const ConnectedStatus: React.FC<{
<Cond condition={agent.lifecycle_state === "start_error"}>
<StartErrorLifecycle agent={agent} />
</Cond>
<Cond condition={agent.lifecycle_state === "shutting_down"}>
<ShuttingDownLifecycle />
</Cond>
<Cond condition={agent.lifecycle_state === "shutdown_timeout"}>
<ShutdownTimeoutLifecycle agent={agent} />
</Cond>
<Cond condition={agent.lifecycle_state === "shutdown_error"}>
<ShutdownErrorLifecycle agent={agent} />
</Cond>
<Cond condition={agent.lifecycle_state === "off"}>
<OffLifecycle />
</Cond>
<Cond>
<StartingLifecycle />
</Cond>

View File

@ -14,7 +14,9 @@
"status": {
"timeout": "Timeout",
"startTimeout": "Start Timeout",
"startError": "Error"
"startError": "Start Error",
"shutdownTimeout": "Stop Timeout",
"shutdownError": "Stop Error"
},
"timeoutTooltip": {
"title": "Agent is taking too long to connect",
@ -27,8 +29,18 @@
"link": "Troubleshoot"
},
"startErrorTooltip": {
"title": "Error starting agent",
"message": "Something went wrong during the agent start.",
"title": "Error starting the agent",
"message": "Something went wrong during the agent startup.",
"link": "Troubleshoot"
},
"shutdownTimeoutTooltip": {
"title": "Agent is taking too long to stop",
"message": "We noticed this agent is taking longer than expected to stop.",
"link": "Troubleshoot"
},
"shutdownErrorTooltip": {
"title": "Error stopping the agent",
"message": "Something went wrong while trying to stop the agent.",
"link": "Troubleshoot"
},
"unableToConnect": "Unable to connect"

View File

@ -39,7 +39,9 @@
"agentStatus": {
"connected": {
"ready": "Ready",
"starting": "Starting..."
"starting": "Starting...",
"shuttingDown": "Stopping...",
"off": "Stopped"
},
"connecting": "Connecting...",
"disconnected": "Disconnected",

View File

@ -401,6 +401,7 @@ export const MockWorkspaceAgent: TypesGen.WorkspaceAgent = {
lifecycle_state: "starting",
login_before_ready: false,
startup_script_timeout_seconds: 120,
shutdown_script_timeout_seconds: 120,
}
export const MockWorkspaceAgentDisconnected: TypesGen.WorkspaceAgent = {
@ -478,6 +479,34 @@ export const MockWorkspaceAgentStartError: TypesGen.WorkspaceAgent = {
lifecycle_state: "start_error",
}
export const MockWorkspaceAgentShuttingDown: TypesGen.WorkspaceAgent = {
...MockWorkspaceAgent,
id: "test-workspace-agent-shutting-down",
name: "a-shutting-down-workspace-agent",
lifecycle_state: "shutting_down",
}
export const MockWorkspaceAgentShutdownTimeout: TypesGen.WorkspaceAgent = {
...MockWorkspaceAgent,
id: "test-workspace-agent-shutdown-timeout",
name: "a-workspace-agent-timed-out-while-running-shutdownup-script",
lifecycle_state: "shutdown_timeout",
}
export const MockWorkspaceAgentShutdownError: TypesGen.WorkspaceAgent = {
...MockWorkspaceAgent,
id: "test-workspace-agent-shutdown-error",
name: "a-workspace-agent-errored-while-running-shutdownup-script",
lifecycle_state: "shutdown_error",
}
export const MockWorkspaceAgentOff: TypesGen.WorkspaceAgent = {
...MockWorkspaceAgent,
id: "test-workspace-agent-off",
name: "a-workspace-agent-is-shut-down",
lifecycle_state: "off",
}
export const MockWorkspaceResource: TypesGen.WorkspaceResource = {
agents: [
MockWorkspaceAgent,