diff --git a/agent/agent.go b/agent/agent.go index 95ef8d713e3fd..3083beb8ac00e 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -71,6 +71,7 @@ type Client interface { WorkspaceAgentMetadata(ctx context.Context) (codersdk.WorkspaceAgentMetadata, error) ListenWorkspaceAgent(ctx context.Context) (net.Conn, error) AgentReportStats(ctx context.Context, log slog.Logger, stats func() *codersdk.AgentStats) (io.Closer, error) + PostWorkspaceAgentLifecycle(ctx context.Context, state codersdk.PostWorkspaceAgentLifecycleRequest) error PostWorkspaceAgentAppHealth(ctx context.Context, req codersdk.PostWorkspaceAppHealthsRequest) error PostWorkspaceAgentVersion(ctx context.Context, version string) error } @@ -101,6 +102,7 @@ func New(options Options) io.Closer { exchangeToken: options.ExchangeToken, filesystem: options.Filesystem, tempDir: options.TempDir, + lifecycleUpdate: make(chan struct{}, 1), } a.init(ctx) return a @@ -127,6 +129,10 @@ type agent struct { sessionToken atomic.Pointer[string] sshServer *ssh.Server + lifecycleUpdate chan struct{} + lifecycleMu sync.Mutex // Protects following. + lifecycleState codersdk.WorkspaceAgentLifecycle + network *tailnet.Conn } @@ -135,6 +141,8 @@ type agent struct { // may be happening, but regardless after the intermittent // failure, you'll want the agent to reconnect. func (a *agent) runLoop(ctx context.Context) { + go a.reportLifecycleLoop(ctx) + for retrier := retry.New(100*time.Millisecond, 10*time.Second); retrier.Wait(ctx); { a.logger.Info(ctx, "running loop") err := a.run(ctx) @@ -156,6 +164,58 @@ func (a *agent) runLoop(ctx context.Context) { } } +// reportLifecycleLoop reports the current lifecycle state once. +// Only the latest state is reported, intermediate states may be +// lost if the agent can't communicate with the API. +func (a *agent) reportLifecycleLoop(ctx context.Context) { + var lastReported codersdk.WorkspaceAgentLifecycle + for { + select { + case <-a.lifecycleUpdate: + case <-ctx.Done(): + return + } + + for r := retry.New(time.Second, 15*time.Second); r.Wait(ctx); { + a.lifecycleMu.Lock() + state := a.lifecycleState + a.lifecycleMu.Unlock() + + if state == lastReported { + break + } + + a.logger.Debug(ctx, "post lifecycle state", slog.F("state", state)) + + err := a.client.PostWorkspaceAgentLifecycle(ctx, codersdk.PostWorkspaceAgentLifecycleRequest{ + State: state, + }) + if err == nil { + lastReported = state + break + } + if xerrors.Is(err, context.Canceled) || xerrors.Is(err, context.DeadlineExceeded) { + return + } + // If we fail to report the state we probably shouldn't exit, log only. + a.logger.Error(ctx, "post state", slog.Error(err)) + } + } +} + +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)) + + a.lifecycleState = state + select { + case a.lifecycleUpdate <- struct{}{}: + default: + } +} + func (a *agent) run(ctx context.Context) error { // This allows the agent to refresh it's token if necessary. // For instance identity this is required, since the instance @@ -180,22 +240,60 @@ func (a *agent) run(ctx context.Context) error { // The startup script should only execute on the first run! if oldMetadata == nil { + a.setLifecycle(ctx, codersdk.WorkspaceAgentLifecycleStarting) + + // Perform overrides early so that Git auth can work even if users + // connect to a workspace that is not yet ready. We don't run this + // concurrently with the startup script to avoid conflicts between + // them. + if metadata.GitAuthConfigs > 0 { + // If this fails, we should consider surfacing the error in the + // startup log and setting the lifecycle state to be "start_error" + // (after startup script completion), but for now we'll just log it. + err := gitauth.OverrideVSCodeConfigs(a.filesystem) + if err != nil { + a.logger.Warn(ctx, "failed to override vscode git auth configs", slog.Error(err)) + } + } + + scriptDone := make(chan error, 1) + scriptStart := time.Now() + go func() { + defer close(scriptDone) + scriptDone <- a.runStartupScript(ctx, metadata.StartupScript) + }() go func() { - err := a.runStartupScript(ctx, metadata.StartupScript) + 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.StartupScriptTimeout > 0 { + t := time.NewTimer(metadata.StartupScriptTimeout) + defer t.Stop() + timeout = t.C + } + + var err error + select { + case err = <-scriptDone: + case <-timeout: + a.logger.Warn(ctx, "startup script timed out") + a.setLifecycle(ctx, codersdk.WorkspaceAgentLifecycleStartTimeout) + err = <-scriptDone // The script can still complete after a timeout. + } if errors.Is(err, context.Canceled) { return } + execTime := time.Since(scriptStart) + lifecycleStatus := codersdk.WorkspaceAgentLifecycleReady if err != nil { - a.logger.Warn(ctx, "agent script failed", slog.Error(err)) + 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)) } - }() - } - if metadata.GitAuthConfigs > 0 { - err = gitauth.OverrideVSCodeConfigs(a.filesystem) - if err != nil { - return xerrors.Errorf("override vscode configuration for git auth: %w", err) - } + a.setLifecycle(ctx, lifecycleStatus) + }() } // This automatically closes when the context ends! diff --git a/agent/agent_test.go b/agent/agent_test.go index 7c588fbe028fc..0f0d93f923937 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -57,7 +57,7 @@ func TestAgent_Stats_SSH(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - conn, stats, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) + conn, _, stats, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) sshClient, err := conn.SSHClient(ctx) require.NoError(t, err) @@ -83,7 +83,7 @@ func TestAgent_Stats_ReconnectingPTY(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - conn, stats, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) + conn, _, stats, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) ptyConn, err := conn.ReconnectingPTY(ctx, uuid.New(), 128, 128, "/bin/bash") require.NoError(t, err) @@ -529,7 +529,8 @@ func TestAgent_SFTP(t *testing.T) { if runtime.GOOS == "windows" { home = "/" + strings.ReplaceAll(home, "\\", "/") } - conn, _, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) + //nolint:dogsled + conn, _, _, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) sshClient, err := conn.SSHClient(ctx) require.NoError(t, err) defer sshClient.Close() @@ -560,7 +561,8 @@ func TestAgent_SCP(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - conn, _, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) + //nolint:dogsled + conn, _, _, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) sshClient, err := conn.SSHClient(ctx) require.NoError(t, err) defer sshClient.Close() @@ -664,7 +666,8 @@ func TestAgent_StartupScript(t *testing.T) { t.Skip("This test doesn't work on Windows for some reason...") } content := "output" - _, _, fs := setupAgent(t, codersdk.WorkspaceAgentMetadata{ + //nolint:dogsled + _, _, _, fs := setupAgent(t, codersdk.WorkspaceAgentMetadata{ StartupScript: "echo " + content, }, 0) var gotContent string @@ -692,6 +695,97 @@ func TestAgent_StartupScript(t *testing.T) { require.Equal(t, content, strings.TrimSpace(gotContent)) } +func TestAgent_Lifecycle(t *testing.T) { + t.Parallel() + + t.Run("Timeout", func(t *testing.T) { + t.Parallel() + + _, client, _, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{ + StartupScript: "sleep 10", + StartupScriptTimeout: time.Nanosecond, + }, 0) + + want := []codersdk.WorkspaceAgentLifecycle{ + codersdk.WorkspaceAgentLifecycleStarting, + codersdk.WorkspaceAgentLifecycleStartTimeout, + } + + var got []codersdk.WorkspaceAgentLifecycle + assert.Eventually(t, func() bool { + got = client.getLifecycleStates() + 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("Error", func(t *testing.T) { + t.Parallel() + + _, client, _, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{ + StartupScript: "false", + StartupScriptTimeout: 30 * time.Second, + }, 0) + + want := []codersdk.WorkspaceAgentLifecycle{ + codersdk.WorkspaceAgentLifecycleStarting, + codersdk.WorkspaceAgentLifecycleStartError, + } + + var got []codersdk.WorkspaceAgentLifecycle + assert.Eventually(t, func() bool { + got = client.getLifecycleStates() + 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("Ready", func(t *testing.T) { + t.Parallel() + + _, client, _, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{ + StartupScript: "true", + StartupScriptTimeout: 30 * time.Second, + }, 0) + + want := []codersdk.WorkspaceAgentLifecycle{ + codersdk.WorkspaceAgentLifecycleStarting, + codersdk.WorkspaceAgentLifecycleReady, + } + + var got []codersdk.WorkspaceAgentLifecycle + assert.Eventually(t, func() bool { + got = client.getLifecycleStates() + 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) + } + }) +} + func TestAgent_ReconnectingPTY(t *testing.T) { t.Parallel() if runtime.GOOS == "windows" { @@ -704,7 +798,8 @@ func TestAgent_ReconnectingPTY(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - conn, _, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) + //nolint:dogsled + conn, _, _, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) id := uuid.New() netConn, err := conn.ReconnectingPTY(ctx, id, 100, 100, "/bin/bash") require.NoError(t, err) @@ -805,7 +900,8 @@ func TestAgent_Dial(t *testing.T) { } }() - conn, _, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) + //nolint:dogsled + conn, _, _, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) require.True(t, conn.AwaitReachable(context.Background())) conn1, err := conn.DialContext(context.Background(), l.Addr().Network(), l.Addr().String()) require.NoError(t, err) @@ -826,7 +922,8 @@ func TestAgent_Speedtest(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() derpMap := tailnettest.RunDERPAndSTUN(t) - conn, _, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{ + //nolint:dogsled + conn, _, _, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{ DERPMap: derpMap, }, 0) defer conn.Close() @@ -911,7 +1008,8 @@ func TestAgent_WriteVSCodeConfigs(t *testing.T) { } func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) *exec.Cmd { - agentConn, _, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) + //nolint:dogsled + agentConn, _, _, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0) listener, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) waitGroup := sync.WaitGroup{} @@ -957,7 +1055,8 @@ func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) *exe func setupSSHSession(t *testing.T, options codersdk.WorkspaceAgentMetadata) *ssh.Session { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - conn, _, _ := setupAgent(t, options, 0) + //nolint:dogsled + conn, _, _, _ := setupAgent(t, options, 0) sshClient, err := conn.SSHClient(ctx) require.NoError(t, err) t.Cleanup(func() { @@ -979,6 +1078,7 @@ func (c closeFunc) Close() error { func setupAgent(t *testing.T, metadata codersdk.WorkspaceAgentMetadata, ptyTimeout time.Duration) ( *codersdk.AgentConn, + *client, <-chan *codersdk.AgentStats, afero.Fs, ) { @@ -992,14 +1092,15 @@ func setupAgent(t *testing.T, metadata codersdk.WorkspaceAgentMetadata, ptyTimeo agentID := uuid.New() statsCh := make(chan *codersdk.AgentStats, 50) fs := afero.NewMemMapFs() + c := &client{ + t: t, + agentID: agentID, + metadata: metadata, + statsChan: statsCh, + coordinator: coordinator, + } closer := agent.New(agent.Options{ - Client: &client{ - t: t, - agentID: agentID, - metadata: metadata, - statsChan: statsCh, - coordinator: coordinator, - }, + Client: c, Filesystem: fs, Logger: slogtest.Make(t, nil).Leveled(slog.LevelDebug), ReconnectingPTYTimeout: ptyTimeout, @@ -1032,7 +1133,7 @@ func setupAgent(t *testing.T, metadata codersdk.WorkspaceAgentMetadata, ptyTimeo conn.SetNodeCallback(sendNode) return &codersdk.AgentConn{ Conn: conn, - }, statsCh, fs + }, c, statsCh, fs } var dialTestPayload = []byte("dean-was-here123") @@ -1073,6 +1174,9 @@ type client struct { statsChan chan *codersdk.AgentStats coordinator tailnet.Coordinator lastWorkspaceAgent func() + + mu sync.Mutex // Protects following. + lifecycleStates []codersdk.WorkspaceAgentLifecycle } func (c *client) WorkspaceAgentMetadata(_ context.Context) (codersdk.WorkspaceAgentMetadata, error) { @@ -1128,6 +1232,19 @@ func (c *client) AgentReportStats(ctx context.Context, _ slog.Logger, stats func }), nil } +func (c *client) getLifecycleStates() []codersdk.WorkspaceAgentLifecycle { + c.mu.Lock() + defer c.mu.Unlock() + return c.lifecycleStates +} + +func (c *client) PostWorkspaceAgentLifecycle(_ context.Context, req codersdk.PostWorkspaceAgentLifecycleRequest) error { + c.mu.Lock() + defer c.mu.Unlock() + c.lifecycleStates = append(c.lifecycleStates, req.State) + return nil +} + func (*client) PostWorkspaceAgentAppHealth(_ context.Context, _ codersdk.PostWorkspaceAppHealthsRequest) error { return nil } diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index c7b08e9ac0e3e..01b931f489010 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -3973,6 +3973,42 @@ const docTemplate = `{ } } }, + "/workspaceagents/me/report-lifecycle": { + "post": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "consumes": [ + "application/json" + ], + "tags": [ + "Agents" + ], + "summary": "Submit workspace agent lifecycle state", + "operationId": "submit-workspace-agent-lifecycle-state", + "parameters": [ + { + "description": "Workspace agent lifecycle request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/codersdk.PostWorkspaceAgentLifecycleRequest" + } + } + ], + "responses": { + "204": { + "description": "Success" + } + }, + "x-apidocgen": { + "skip": true + } + } + }, "/workspaceagents/me/report-stats": { "post": { "security": [ @@ -6723,6 +6759,14 @@ const docTemplate = `{ "ParameterSourceSchemeData" ] }, + "codersdk.PostWorkspaceAgentLifecycleRequest": { + "type": "object", + "properties": { + "state": { + "$ref": "#/definitions/codersdk.WorkspaceAgentLifecycle" + } + } + }, "codersdk.PostWorkspaceAgentVersionRequest": { "description": "x-apidocgen:skip", "type": "object", @@ -7651,6 +7695,10 @@ const docTemplate = `{ "type": "string", "format": "date-time" }, + "delay_login_until_ready": { + "description": "DelayLoginUntilReady if true, the agent will delay logins until it is ready (e.g. executing startup script has ended).", + "type": "boolean" + }, "directory": { "type": "string" }, @@ -7686,6 +7734,9 @@ const docTemplate = `{ "$ref": "#/definitions/codersdk.DERPRegion" } }, + "lifecycle_state": { + "$ref": "#/definitions/codersdk.WorkspaceAgentLifecycle" + }, "name": { "type": "string" }, @@ -7699,18 +7750,12 @@ const docTemplate = `{ "startup_script": { "type": "string" }, + "startup_script_timeout_seconds": { + "description": "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.", + "type": "integer" + }, "status": { - "enum": [ - "connecting", - "connected", - "disconnected", - "timeout" - ], - "allOf": [ - { - "$ref": "#/definitions/codersdk.WorkspaceAgentStatus" - } - ] + "$ref": "#/definitions/codersdk.WorkspaceAgentStatus" }, "troubleshooting_url": { "type": "string" @@ -7754,6 +7799,23 @@ const docTemplate = `{ } } }, + "codersdk.WorkspaceAgentLifecycle": { + "type": "string", + "enum": [ + "created", + "starting", + "start_timeout", + "start_error", + "ready" + ], + "x-enum-varnames": [ + "WorkspaceAgentLifecycleCreated", + "WorkspaceAgentLifecycleStarting", + "WorkspaceAgentLifecycleStartTimeout", + "WorkspaceAgentLifecycleStartError", + "WorkspaceAgentLifecycleReady" + ] + }, "codersdk.WorkspaceAgentMetadata": { "type": "object", "properties": { @@ -7785,6 +7847,9 @@ const docTemplate = `{ "startup_script": { "type": "string" }, + "startup_script_timeout": { + "type": "integer" + }, "vscode_port_proxy_uri": { "type": "string" } diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 23ef9e49cfd53..aa8bc00cbe81c 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -3493,6 +3493,38 @@ } } }, + "/workspaceagents/me/report-lifecycle": { + "post": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "consumes": ["application/json"], + "tags": ["Agents"], + "summary": "Submit workspace agent lifecycle state", + "operationId": "submit-workspace-agent-lifecycle-state", + "parameters": [ + { + "description": "Workspace agent lifecycle request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/codersdk.PostWorkspaceAgentLifecycleRequest" + } + } + ], + "responses": { + "204": { + "description": "Success" + } + }, + "x-apidocgen": { + "skip": true + } + } + }, "/workspaceagents/me/report-stats": { "post": { "security": [ @@ -6006,6 +6038,14 @@ "ParameterSourceSchemeData" ] }, + "codersdk.PostWorkspaceAgentLifecycleRequest": { + "type": "object", + "properties": { + "state": { + "$ref": "#/definitions/codersdk.WorkspaceAgentLifecycle" + } + } + }, "codersdk.PostWorkspaceAgentVersionRequest": { "description": "x-apidocgen:skip", "type": "object", @@ -6886,6 +6926,10 @@ "type": "string", "format": "date-time" }, + "delay_login_until_ready": { + "description": "DelayLoginUntilReady if true, the agent will delay logins until it is ready (e.g. executing startup script has ended).", + "type": "boolean" + }, "directory": { "type": "string" }, @@ -6921,6 +6965,9 @@ "$ref": "#/definitions/codersdk.DERPRegion" } }, + "lifecycle_state": { + "$ref": "#/definitions/codersdk.WorkspaceAgentLifecycle" + }, "name": { "type": "string" }, @@ -6934,13 +6981,12 @@ "startup_script": { "type": "string" }, + "startup_script_timeout_seconds": { + "description": "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.", + "type": "integer" + }, "status": { - "enum": ["connecting", "connected", "disconnected", "timeout"], - "allOf": [ - { - "$ref": "#/definitions/codersdk.WorkspaceAgentStatus" - } - ] + "$ref": "#/definitions/codersdk.WorkspaceAgentStatus" }, "troubleshooting_url": { "type": "string" @@ -6984,6 +7030,17 @@ } } }, + "codersdk.WorkspaceAgentLifecycle": { + "type": "string", + "enum": ["created", "starting", "start_timeout", "start_error", "ready"], + "x-enum-varnames": [ + "WorkspaceAgentLifecycleCreated", + "WorkspaceAgentLifecycleStarting", + "WorkspaceAgentLifecycleStartTimeout", + "WorkspaceAgentLifecycleStartError", + "WorkspaceAgentLifecycleReady" + ] + }, "codersdk.WorkspaceAgentMetadata": { "type": "object", "properties": { @@ -7015,6 +7072,9 @@ "startup_script": { "type": "string" }, + "startup_script_timeout": { + "type": "integer" + }, "vscode_port_proxy_uri": { "type": "string" } diff --git a/coderd/coderd.go b/coderd/coderd.go index 5429eeee9cad9..6119497796354 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -541,6 +541,7 @@ func New(options *Options) *API { r.Get("/gitsshkey", api.agentGitSSHKey) r.Get("/coordinate", api.workspaceAgentCoordinate) r.Post("/report-stats", api.workspaceAgentReportStats) + r.Post("/report-lifecycle", api.workspaceAgentReportLifecycle) }) r.Route("/{workspaceagent}", func(r chi.Router) { r.Use( diff --git a/coderd/coderdtest/authorize.go b/coderd/coderdtest/authorize.go index 1924ca2e41b9d..462dcc644f56f 100644 --- a/coderd/coderdtest/authorize.go +++ b/coderd/coderdtest/authorize.go @@ -75,6 +75,7 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) { "POST:/api/v2/workspaceagents/me/version": {NoAuthorize: true}, "POST:/api/v2/workspaceagents/me/app-health": {NoAuthorize: true}, "POST:/api/v2/workspaceagents/me/report-stats": {NoAuthorize: true}, + "POST:/api/v2/workspaceagents/me/report-lifecycle": {NoAuthorize: true}, // These endpoints have more assertions. This is good, add more endpoints to assert if you can! "GET:/api/v2/organizations/{organization}": {AssertObject: rbac.ResourceOrganization.WithID(a.Admin.OrganizationID).InOrg(a.Admin.OrganizationID)}, @@ -276,9 +277,11 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) { // Routes like proxy routes support all HTTP methods. A helper func to expand // 1 url to all http methods. assertAllHTTPMethods := func(url string, check RouteCheck) { - methods := []string{http.MethodGet, http.MethodHead, http.MethodPost, + methods := []string{ + http.MethodGet, http.MethodHead, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, - http.MethodConnect, http.MethodOptions, http.MethodTrace} + http.MethodConnect, http.MethodOptions, http.MethodTrace, + } for _, method := range methods { route := method + ":" + url diff --git a/coderd/database/databasefake/databasefake.go b/coderd/database/databasefake/databasefake.go index 51a7134ab1d33..42aca99b1d00d 100644 --- a/coderd/database/databasefake/databasefake.go +++ b/coderd/database/databasefake/databasefake.go @@ -2789,6 +2789,7 @@ func (q *fakeQuerier) InsertWorkspaceAgent(_ context.Context, arg database.Inser ConnectionTimeoutSeconds: arg.ConnectionTimeoutSeconds, TroubleshootingURL: arg.TroubleshootingURL, MOTDFile: arg.MOTDFile, + LifecycleState: database.WorkspaceAgentLifecycleStateCreated, } q.workspaceAgents = append(q.workspaceAgents, agent) @@ -4294,3 +4295,20 @@ func (q *fakeQuerier) GetQuotaConsumedForUser(_ context.Context, userID uuid.UUI } return sum, nil } + +func (q *fakeQuerier) UpdateWorkspaceAgentLifecycleStateByID(_ context.Context, arg database.UpdateWorkspaceAgentLifecycleStateByIDParams) error { + if err := validateDatabaseType(arg); err != nil { + return err + } + + q.mutex.Lock() + defer q.mutex.Unlock() + for i, agent := range q.workspaceAgents { + if agent.ID == arg.ID { + agent.LifecycleState = arg.LifecycleState + q.workspaceAgents[i] = agent + return nil + } + } + return sql.ErrNoRows +} diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 5f32d13f904c7..76881e579022e 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -99,6 +99,14 @@ CREATE TYPE user_status AS ENUM ( 'suspended' ); +CREATE TYPE workspace_agent_lifecycle_state AS ENUM ( + 'created', + 'starting', + 'start_timeout', + 'start_error', + 'ready' +); + CREATE TYPE workspace_app_health AS ENUM ( 'disabled', 'initializing', @@ -448,7 +456,10 @@ CREATE TABLE workspace_agents ( last_connected_replica_id uuid, connection_timeout_seconds integer DEFAULT 0 NOT NULL, troubleshooting_url text DEFAULT ''::text NOT NULL, - motd_file text DEFAULT ''::text NOT NULL + motd_file text DEFAULT ''::text NOT NULL, + lifecycle_state workspace_agent_lifecycle_state DEFAULT 'created'::workspace_agent_lifecycle_state NOT NULL, + delay_login_until_ready boolean DEFAULT false NOT NULL, + startup_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.'; @@ -459,6 +470,12 @@ COMMENT ON COLUMN workspace_agents.troubleshooting_url IS 'URL for troubleshooti COMMENT ON COLUMN workspace_agents.motd_file IS 'Path to file inside workspace containing the message of the day (MOTD) to show to the user when logging in via SSH.'; +COMMENT ON COLUMN workspace_agents.lifecycle_state IS 'The current lifecycle state reported by the workspace agent.'; + +COMMENT ON COLUMN workspace_agents.delay_login_until_ready IS 'If true, the agent will delay logins until it is ready (e.g. executing startup script has ended).'; + +COMMENT ON COLUMN workspace_agents.startup_script_timeout_seconds IS 'The number of seconds to wait for the startup script to complete. If the script does not complete within this time, the agent lifecycle will be marked as start_timeout.'; + CREATE TABLE workspace_apps ( id uuid NOT NULL, created_at timestamp with time zone NOT NULL, diff --git a/coderd/database/migrations/000091_add_workspace_agent_state.down.sql b/coderd/database/migrations/000091_add_workspace_agent_state.down.sql new file mode 100644 index 0000000000000..ecd39183ad044 --- /dev/null +++ b/coderd/database/migrations/000091_add_workspace_agent_state.down.sql @@ -0,0 +1,6 @@ +ALTER TABLE workspace_agents DROP COLUMN startup_script_timeout_seconds; +ALTER TABLE workspace_agents DROP COLUMN delay_login_until_ready; + +ALTER TABLE workspace_agents DROP COLUMN lifecycle_state; + +DROP TYPE workspace_agent_lifecycle_state; diff --git a/coderd/database/migrations/000091_add_workspace_agent_state.up.sql b/coderd/database/migrations/000091_add_workspace_agent_state.up.sql new file mode 100644 index 0000000000000..c2c1f4da3fa80 --- /dev/null +++ b/coderd/database/migrations/000091_add_workspace_agent_state.up.sql @@ -0,0 +1,19 @@ +CREATE TYPE workspace_agent_lifecycle_state AS ENUM ('created', 'starting', 'start_timeout', 'start_error', 'ready'); + +-- Set all existing workspace agents to 'ready' so that only newly created agents will be in the 'created' state. +ALTER TABLE workspace_agents ADD COLUMN lifecycle_state workspace_agent_lifecycle_state NOT NULL DEFAULT 'ready'; +-- Change the default for newly created agents. +ALTER TABLE workspace_agents ALTER COLUMN lifecycle_state SET DEFAULT 'created'; + +COMMENT ON COLUMN workspace_agents.lifecycle_state IS 'The current lifecycle state reported by the workspace agent.'; + +-- Set default values that conform to current behavior. +-- Allow logins immediately after agent connect. +ALTER TABLE workspace_agents ADD COLUMN delay_login_until_ready boolean NOT NULL DEFAULT false; + +COMMENT ON COLUMN workspace_agents.delay_login_until_ready IS 'If true, the agent will delay logins until it is ready (e.g. executing startup script has ended).'; + +-- Disable startup script timeouts by default. +ALTER TABLE workspace_agents ADD COLUMN startup_script_timeout_seconds int4 NOT NULL DEFAULT 0; + +COMMENT ON COLUMN workspace_agents.startup_script_timeout_seconds IS 'The number of seconds to wait for the startup script to complete. If the script does not complete within this time, the agent lifecycle will be marked as start_timeout.'; diff --git a/coderd/database/models.go b/coderd/database/models.go index e2a9cf2c31845..9337610a23c4b 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1002,6 +1002,73 @@ func AllUserStatusValues() []UserStatus { } } +type WorkspaceAgentLifecycleState string + +const ( + WorkspaceAgentLifecycleStateCreated WorkspaceAgentLifecycleState = "created" + WorkspaceAgentLifecycleStateStarting WorkspaceAgentLifecycleState = "starting" + WorkspaceAgentLifecycleStateStartTimeout WorkspaceAgentLifecycleState = "start_timeout" + WorkspaceAgentLifecycleStateStartError WorkspaceAgentLifecycleState = "start_error" + WorkspaceAgentLifecycleStateReady WorkspaceAgentLifecycleState = "ready" +) + +func (e *WorkspaceAgentLifecycleState) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = WorkspaceAgentLifecycleState(s) + case string: + *e = WorkspaceAgentLifecycleState(s) + default: + return fmt.Errorf("unsupported scan type for WorkspaceAgentLifecycleState: %T", src) + } + return nil +} + +type NullWorkspaceAgentLifecycleState struct { + WorkspaceAgentLifecycleState WorkspaceAgentLifecycleState + Valid bool // Valid is true if WorkspaceAgentLifecycleState is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullWorkspaceAgentLifecycleState) Scan(value interface{}) error { + if value == nil { + ns.WorkspaceAgentLifecycleState, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.WorkspaceAgentLifecycleState.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullWorkspaceAgentLifecycleState) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return ns.WorkspaceAgentLifecycleState, nil +} + +func (e WorkspaceAgentLifecycleState) Valid() bool { + switch e { + case WorkspaceAgentLifecycleStateCreated, + WorkspaceAgentLifecycleStateStarting, + WorkspaceAgentLifecycleStateStartTimeout, + WorkspaceAgentLifecycleStateStartError, + WorkspaceAgentLifecycleStateReady: + return true + } + return false +} + +func AllWorkspaceAgentLifecycleStateValues() []WorkspaceAgentLifecycleState { + return []WorkspaceAgentLifecycleState{ + WorkspaceAgentLifecycleStateCreated, + WorkspaceAgentLifecycleStateStarting, + WorkspaceAgentLifecycleStateStartTimeout, + WorkspaceAgentLifecycleStateStartError, + WorkspaceAgentLifecycleStateReady, + } +} + type WorkspaceAppHealth string const ( @@ -1448,6 +1515,12 @@ type WorkspaceAgent struct { TroubleshootingURL string `db:"troubleshooting_url" json:"troubleshooting_url"` // Path to file inside workspace containing the message of the day (MOTD) to show to the user when logging in via SSH. MOTDFile string `db:"motd_file" json:"motd_file"` + // The current lifecycle state reported by the workspace agent. + LifecycleState WorkspaceAgentLifecycleState `db:"lifecycle_state" json:"lifecycle_state"` + // If true, the agent will delay logins until it is ready (e.g. executing startup script has ended). + DelayLoginUntilReady bool `db:"delay_login_until_ready" json:"delay_login_until_ready"` + // The number of seconds to wait for the startup script to complete. If the script does not complete within this time, the agent lifecycle will be marked as start_timeout. + StartupScriptTimeoutSeconds int32 `db:"startup_script_timeout_seconds" json:"startup_script_timeout_seconds"` } type WorkspaceApp struct { diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 6a11205739c87..7e9ddecea7c56 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -199,6 +199,7 @@ type sqlcQuerier interface { UpdateUserStatus(ctx context.Context, arg UpdateUserStatusParams) (User, error) UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams) (Workspace, error) UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg UpdateWorkspaceAgentConnectionByIDParams) error + UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, arg UpdateWorkspaceAgentLifecycleStateByIDParams) error UpdateWorkspaceAgentVersionByID(ctx context.Context, arg UpdateWorkspaceAgentVersionByIDParams) error UpdateWorkspaceAppHealthByID(ctx context.Context, arg UpdateWorkspaceAppHealthByIDParams) error UpdateWorkspaceAutostart(ctx context.Context, arg UpdateWorkspaceAutostartParams) error diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 198ed29bd15a6..4eff98612623b 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -4869,7 +4869,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 + 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, delay_login_until_ready, startup_script_timeout_seconds FROM workspace_agents WHERE @@ -4904,13 +4904,16 @@ func (q *sqlQuerier) GetWorkspaceAgentByAuthToken(ctx context.Context, authToken &i.ConnectionTimeoutSeconds, &i.TroubleshootingURL, &i.MOTDFile, + &i.LifecycleState, + &i.DelayLoginUntilReady, + &i.StartupScriptTimeoutSeconds, ) 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 + 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, delay_login_until_ready, startup_script_timeout_seconds FROM workspace_agents WHERE @@ -4943,13 +4946,16 @@ func (q *sqlQuerier) GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (W &i.ConnectionTimeoutSeconds, &i.TroubleshootingURL, &i.MOTDFile, + &i.LifecycleState, + &i.DelayLoginUntilReady, + &i.StartupScriptTimeoutSeconds, ) 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 + 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, delay_login_until_ready, startup_script_timeout_seconds FROM workspace_agents WHERE @@ -4984,13 +4990,16 @@ func (q *sqlQuerier) GetWorkspaceAgentByInstanceID(ctx context.Context, authInst &i.ConnectionTimeoutSeconds, &i.TroubleshootingURL, &i.MOTDFile, + &i.LifecycleState, + &i.DelayLoginUntilReady, + &i.StartupScriptTimeoutSeconds, ) 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 + 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, delay_login_until_ready, startup_script_timeout_seconds FROM workspace_agents WHERE @@ -5029,6 +5038,9 @@ func (q *sqlQuerier) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids [] &i.ConnectionTimeoutSeconds, &i.TroubleshootingURL, &i.MOTDFile, + &i.LifecycleState, + &i.DelayLoginUntilReady, + &i.StartupScriptTimeoutSeconds, ); err != nil { return nil, err } @@ -5044,7 +5056,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 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, delay_login_until_ready, startup_script_timeout_seconds FROM workspace_agents WHERE created_at > $1 ` func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceAgent, error) { @@ -5079,6 +5091,9 @@ func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, created &i.ConnectionTimeoutSeconds, &i.TroubleshootingURL, &i.MOTDFile, + &i.LifecycleState, + &i.DelayLoginUntilReady, + &i.StartupScriptTimeoutSeconds, ); err != nil { return nil, err } @@ -5112,30 +5127,34 @@ INSERT INTO resource_metadata, connection_timeout_seconds, troubleshooting_url, - motd_file + motd_file, + delay_login_until_ready, + startup_script_timeout_seconds ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) 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 + ($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, delay_login_until_ready, startup_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"` + 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"` + DelayLoginUntilReady bool `db:"delay_login_until_ready" json:"delay_login_until_ready"` + StartupScriptTimeoutSeconds int32 `db:"startup_script_timeout_seconds" json:"startup_script_timeout_seconds"` } func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspaceAgentParams) (WorkspaceAgent, error) { @@ -5157,6 +5176,8 @@ func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspa arg.ConnectionTimeoutSeconds, arg.TroubleshootingURL, arg.MOTDFile, + arg.DelayLoginUntilReady, + arg.StartupScriptTimeoutSeconds, ) var i WorkspaceAgent err := row.Scan( @@ -5182,6 +5203,9 @@ func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspa &i.ConnectionTimeoutSeconds, &i.TroubleshootingURL, &i.MOTDFile, + &i.LifecycleState, + &i.DelayLoginUntilReady, + &i.StartupScriptTimeoutSeconds, ) return i, err } @@ -5220,6 +5244,25 @@ func (q *sqlQuerier) UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg return err } +const updateWorkspaceAgentLifecycleStateByID = `-- name: UpdateWorkspaceAgentLifecycleStateByID :exec +UPDATE + workspace_agents +SET + lifecycle_state = $2 +WHERE + id = $1 +` + +type UpdateWorkspaceAgentLifecycleStateByIDParams struct { + ID uuid.UUID `db:"id" json:"id"` + LifecycleState WorkspaceAgentLifecycleState `db:"lifecycle_state" json:"lifecycle_state"` +} + +func (q *sqlQuerier) UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, arg UpdateWorkspaceAgentLifecycleStateByIDParams) error { + _, err := q.db.ExecContext(ctx, updateWorkspaceAgentLifecycleStateByID, arg.ID, arg.LifecycleState) + return err +} + const updateWorkspaceAgentVersionByID = `-- name: UpdateWorkspaceAgentVersionByID :exec UPDATE workspace_agents diff --git a/coderd/database/queries/workspaceagents.sql b/coderd/database/queries/workspaceagents.sql index 12193013cec8d..25e7de2863459 100644 --- a/coderd/database/queries/workspaceagents.sql +++ b/coderd/database/queries/workspaceagents.sql @@ -56,10 +56,12 @@ INSERT INTO resource_metadata, connection_timeout_seconds, troubleshooting_url, - motd_file + motd_file, + delay_login_until_ready, + startup_script_timeout_seconds ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) RETURNING *; + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) RETURNING *; -- name: UpdateWorkspaceAgentConnectionByID :exec UPDATE @@ -80,3 +82,11 @@ SET version = $2 WHERE id = $1; + +-- name: UpdateWorkspaceAgentLifecycleStateByID :exec +UPDATE + workspace_agents +SET + lifecycle_state = $2 +WHERE + id = $1; diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 9974338ffbeae..9b996271cbed7 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -949,9 +949,11 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. String: prAgent.StartupScript, Valid: prAgent.StartupScript != "", }, - ConnectionTimeoutSeconds: prAgent.GetConnectionTimeoutSeconds(), - TroubleshootingURL: prAgent.GetTroubleshootingUrl(), - MOTDFile: prAgent.GetMotdFile(), + ConnectionTimeoutSeconds: prAgent.GetConnectionTimeoutSeconds(), + TroubleshootingURL: prAgent.GetTroubleshootingUrl(), + MOTDFile: prAgent.GetMotdFile(), + DelayLoginUntilReady: prAgent.GetDelayLoginUntilReady(), + StartupScriptTimeoutSeconds: prAgent.GetStartupScriptTimeoutSeconds(), }) if err != nil { return xerrors.Errorf("insert agent: %w", err) diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 6c2f6f970c3cc..69d056f66da9c 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -150,6 +150,7 @@ func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) Directory: apiAgent.Directory, VSCodePortProxyURI: vscodeProxyURI, MOTDFile: workspaceAgent.MOTDFile, + StartupScriptTimeout: time.Duration(apiAgent.StartupScriptTimeoutSeconds) * time.Second, }) } @@ -739,21 +740,24 @@ 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, - Apps: apps, - ConnectionTimeoutSeconds: dbAgent.ConnectionTimeoutSeconds, - TroubleshootingURL: troubleshootingURL, + 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, + Apps: apps, + ConnectionTimeoutSeconds: dbAgent.ConnectionTimeoutSeconds, + TroubleshootingURL: troubleshootingURL, + LifecycleState: codersdk.WorkspaceAgentLifecycle(dbAgent.LifecycleState), + DelayLoginUntilReady: dbAgent.DelayLoginUntilReady, + StartupScriptTimeoutSeconds: dbAgent.StartupScriptTimeoutSeconds, } node := coordinator.Node(dbAgent.ID) if node != nil { @@ -900,6 +904,61 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques }) } +// @Summary Submit workspace agent lifecycle state +// @ID submit-workspace-agent-lifecycle-state +// @Security CoderSessionToken +// @Accept json +// @Tags Agents +// @Param request body codersdk.PostWorkspaceAgentLifecycleRequest true "Workspace agent lifecycle request" +// @Success 204 "Success" +// @Router /workspaceagents/me/report-lifecycle [post] +// @x-apidocgen {"skip": true} +func (api *API) workspaceAgentReportLifecycle(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + workspaceAgent := httpmw.WorkspaceAgent(r) + workspace, err := api.Database.GetWorkspaceByAgentID(ctx, workspaceAgent.ID) + if err != nil { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "Failed to get workspace.", + Detail: err.Error(), + }) + return + } + + var req codersdk.PostWorkspaceAgentLifecycleRequest + if !httpapi.Read(ctx, rw, r, &req) { + return + } + + api.Logger.Debug(ctx, "workspace agent state report", + slog.F("agent", workspaceAgent.ID), + slog.F("workspace", workspace.ID), + slog.F("payload", req), + ) + + lifecycleState := database.WorkspaceAgentLifecycleState(req.State) + if !lifecycleState.Valid() { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "Invalid lifecycle state.", + Detail: fmt.Sprintf("Invalid lifecycle state %q, must be be one of %q.", req.State, database.AllWorkspaceAgentLifecycleStateValues()), + }) + return + } + + err = api.Database.UpdateWorkspaceAgentLifecycleStateByID(ctx, database.UpdateWorkspaceAgentLifecycleStateByIDParams{ + ID: workspaceAgent.ID, + LifecycleState: lifecycleState, + }) + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + api.publishWorkspaceUpdate(ctx, workspace.ID) + + httpapi.Write(ctx, rw, http.StatusNoContent, nil) +} + // @Summary Submit workspace agent application health // @ID submit-workspace-agent-application-health // @Security CoderSessionToken diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 15927730bd915..b5974b6f5d35f 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1220,3 +1220,88 @@ func gitAuthCallback(t *testing.T, id string, client *codersdk.Client) *http.Res }) return res } + +func TestWorkspaceAgent_LifecycleState(t *testing.T) { + t.Parallel() + + t.Run("Set", func(t *testing.T) { + t.Parallel() + + client := coderdtest.New(t, &coderdtest.Options{ + IncludeProvisionerDaemon: true, + }) + user := coderdtest.CreateFirstUser(t, client) + authToken := uuid.NewString() + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionPlan: echo.ProvisionComplete, + ProvisionApply: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Resources: []*proto.Resource{{ + Name: "example", + Type: "aws_instance", + Agents: []*proto.Agent{{ + Id: uuid.NewString(), + Auth: &proto.Agent_Token{ + Token: authToken, + }, + }}, + }}, + }, + }, + }}, + }) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) + + for _, res := range workspace.LatestBuild.Resources { + for _, a := range res.Agents { + require.Equal(t, codersdk.WorkspaceAgentLifecycleCreated, a.LifecycleState) + } + } + + agentClient := codersdk.New(client.URL) + agentClient.SetSessionToken(authToken) + + tests := []struct { + state codersdk.WorkspaceAgentLifecycle + wantErr bool + }{ + {codersdk.WorkspaceAgentLifecycleCreated, false}, + {codersdk.WorkspaceAgentLifecycleStarting, false}, + {codersdk.WorkspaceAgentLifecycleStartTimeout, false}, + {codersdk.WorkspaceAgentLifecycleStartError, false}, + {codersdk.WorkspaceAgentLifecycleReady, false}, + {codersdk.WorkspaceAgentLifecycle("nonexistent_state"), true}, + {codersdk.WorkspaceAgentLifecycle(""), true}, + } + //nolint:paralleltest // No race between setting the state and getting the workspace. + for _, tt := range tests { + tt := tt + t.Run(string(tt.state), func(t *testing.T) { + ctx, _ := testutil.Context(t) + + err := agentClient.PostWorkspaceAgentLifecycle(ctx, codersdk.PostWorkspaceAgentLifecycleRequest{ + State: tt.state, + }) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err, "post lifecycle state %q", tt.state) + + workspace, err = client.Workspace(ctx, workspace.ID) + require.NoError(t, err, "get workspace") + + for _, res := range workspace.LatestBuild.Resources { + for _, agent := range res.Agents { + require.Equal(t, tt.state, agent.LifecycleState) + } + } + }) + } + }) +} diff --git a/coderd/wsconncache/wsconncache_test.go b/coderd/wsconncache/wsconncache_test.go index edb58891f1178..a73e95e98947c 100644 --- a/coderd/wsconncache/wsconncache_test.go +++ b/coderd/wsconncache/wsconncache_test.go @@ -217,6 +217,10 @@ func (*client) AgentReportStats(_ context.Context, _ slog.Logger, _ func() *code return io.NopCloser(strings.NewReader("")), nil } +func (*client) PostWorkspaceAgentLifecycle(_ context.Context, _ codersdk.PostWorkspaceAgentLifecycleRequest) error { + return nil +} + func (*client) PostWorkspaceAgentAppHealth(_ context.Context, _ codersdk.PostWorkspaceAppHealthsRequest) error { return nil } diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 5f033bddb367c..125bd2a5ef7c7 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -34,28 +34,53 @@ const ( WorkspaceAgentTimeout WorkspaceAgentStatus = "timeout" ) +// WorkspaceAgentLifecycle represents the lifecycle state of a workspace agent. +// +// The agent lifecycle starts in the "created" state, and transitions to +// "starting" when the agent reports it has begun preparing (e.g. started +// executing the startup script). +// +// Note that states are not guaranteed to be reported, for instance the agent +// may go from "created" to "ready" without reporting "starting", if it had +// trouble connecting on startup. +type WorkspaceAgentLifecycle string + +// WorkspaceAgentLifecycle enums. +const ( + WorkspaceAgentLifecycleCreated WorkspaceAgentLifecycle = "created" + WorkspaceAgentLifecycleStarting WorkspaceAgentLifecycle = "starting" + WorkspaceAgentLifecycleStartTimeout WorkspaceAgentLifecycle = "start_timeout" + WorkspaceAgentLifecycleStartError WorkspaceAgentLifecycle = "start_error" + WorkspaceAgentLifecycleReady WorkspaceAgentLifecycle = "ready" +) + type WorkspaceAgent struct { - ID uuid.UUID `json:"id" format:"uuid"` - CreatedAt time.Time `json:"created_at" format:"date-time"` - UpdatedAt time.Time `json:"updated_at" format:"date-time"` - FirstConnectedAt *time.Time `json:"first_connected_at,omitempty" format:"date-time"` - LastConnectedAt *time.Time `json:"last_connected_at,omitempty" format:"date-time"` - DisconnectedAt *time.Time `json:"disconnected_at,omitempty" format:"date-time"` - Status WorkspaceAgentStatus `json:"status" enums:"connecting,connected,disconnected,timeout"` - Name string `json:"name"` - ResourceID uuid.UUID `json:"resource_id" format:"uuid"` - InstanceID string `json:"instance_id,omitempty"` - Architecture string `json:"architecture"` - EnvironmentVariables map[string]string `json:"environment_variables"` - OperatingSystem string `json:"operating_system"` - StartupScript string `json:"startup_script,omitempty"` - Directory string `json:"directory,omitempty"` - Version string `json:"version"` - Apps []WorkspaceApp `json:"apps"` + ID uuid.UUID `json:"id" format:"uuid"` + CreatedAt time.Time `json:"created_at" format:"date-time"` + UpdatedAt time.Time `json:"updated_at" format:"date-time"` + FirstConnectedAt *time.Time `json:"first_connected_at,omitempty" format:"date-time"` + LastConnectedAt *time.Time `json:"last_connected_at,omitempty" format:"date-time"` + DisconnectedAt *time.Time `json:"disconnected_at,omitempty" format:"date-time"` + Status WorkspaceAgentStatus `json:"status"` + LifecycleState WorkspaceAgentLifecycle `json:"lifecycle_state"` + Name string `json:"name"` + ResourceID uuid.UUID `json:"resource_id" format:"uuid"` + InstanceID string `json:"instance_id,omitempty"` + Architecture string `json:"architecture"` + EnvironmentVariables map[string]string `json:"environment_variables"` + OperatingSystem string `json:"operating_system"` + StartupScript string `json:"startup_script,omitempty"` + Directory string `json:"directory,omitempty"` + Version string `json:"version"` + Apps []WorkspaceApp `json:"apps"` // DERPLatency is mapped by region name (e.g. "New York City", "Seattle"). DERPLatency map[string]DERPRegion `json:"latency,omitempty"` ConnectionTimeoutSeconds int32 `json:"connection_timeout_seconds"` TroubleshootingURL string `json:"troubleshooting_url"` + // DelayLoginUntilReady if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). + DelayLoginUntilReady bool `db:"delay_login_until_ready" json:"delay_login_until_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"` } type WorkspaceAgentResourceMetadata struct { @@ -131,6 +156,7 @@ type WorkspaceAgentMetadata struct { 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"` } @@ -717,3 +743,21 @@ func (c *Client) WorkspaceAgentGitAuth(ctx context.Context, gitURL string, liste var authResp WorkspaceAgentGitAuthResponse return authResp, json.NewDecoder(res.Body).Decode(&authResp) } + +// @typescript-ignore PostWorkspaceAgentLifecycleRequest +type PostWorkspaceAgentLifecycleRequest struct { + State WorkspaceAgentLifecycle `json:"state"` +} + +func (c *Client) PostWorkspaceAgentLifecycle(ctx context.Context, req PostWorkspaceAgentLifecycleRequest) error { + res, err := c.Request(ctx, http.MethodPost, "/api/v2/workspaceagents/me/report-lifecycle", req) + if err != nil { + return xerrors.Errorf("agent state post request: %w", err) + } + defer res.Body.Close() + if res.StatusCode != http.StatusNoContent { + return readBodyAsError(res) + } + + return nil +} diff --git a/docs/api/agents.md b/docs/api/agents.md index b43626f5c94bd..3359c66e32628 100644 --- a/docs/api/agents.md +++ b/docs/api/agents.md @@ -370,6 +370,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/metadata \ "git_auth_configs": 0, "motd_file": "string", "startup_script": "string", + "startup_script_timeout": 0, "vscode_port_proxy_uri": "string" } ``` @@ -483,6 +484,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent} \ "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -503,10 +505,12 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent} \ "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", diff --git a/docs/api/builds.md b/docs/api/builds.md index 89b30d46d6d48..ee5d3392b5122 100644 --- a/docs/api/builds.md +++ b/docs/api/builds.md @@ -76,6 +76,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -96,10 +97,12 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -218,6 +221,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \ "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -238,10 +242,12 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \ "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -503,6 +509,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/res "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -523,10 +530,12 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/res "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -563,61 +572,64 @@ 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 | | | -| `»» 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 | | | -| `»» name` | string | false | | | -| `»» operating_system` | string | false | | | -| `»» resource_id` | string(uuid) | false | | | -| `»» startup_script` | string | false | | | -| `»» 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 | | | +| `»» delay_login_until_ready` | boolean | false | | »delay login until ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). | +| `»» directory` | string | false | | | +| `»» disconnected_at` | string(date-time) | false | | | +| `»» environment_variables` | object | false | | | +| `»»» [any property]` | 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 | | | +| `»» 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 | | | #### Enumerated Values @@ -630,6 +642,11 @@ Status Code **200** | `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` | @@ -714,6 +731,7 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -734,10 +752,12 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -861,6 +881,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -881,10 +902,12 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -932,91 +955,94 @@ 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 | | | -| `»»» 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 | | | -| `»»» name` | string | false | | | -| `»»» operating_system` | string | false | | | -| `»»» resource_id` | string(uuid) | false | | | -| `»»» startup_script` | string | false | | | -| `»»» 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 | | | +| `»»» delay_login_until_ready` | boolean | false | | »»delay login until ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). | +| `»»» directory` | string | false | | | +| `»»» disconnected_at` | string(date-time) | false | | | +| `»»» environment_variables` | object | false | | | +| `»»»» [any property]` | 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 | | | +| `»»» 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 | | | #### Enumerated Values @@ -1038,6 +1064,11 @@ Status Code **200** | `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` | @@ -1164,6 +1195,7 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -1184,10 +1216,12 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 8b86ca483f088..4da28ea65c779 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -3327,6 +3327,20 @@ Parameter represents a set value for the scope. | `none` | | `data` | +## codersdk.PostWorkspaceAgentLifecycleRequest + +```json +{ + "state": "created" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------- | -------------------------------------------------------------------- | -------- | ------------ | ----------- | +| `state` | [codersdk.WorkspaceAgentLifecycle](#codersdkworkspaceagentlifecycle) | false | | | + ## codersdk.PostWorkspaceAppHealthsRequest ```json @@ -4566,6 +4580,7 @@ Parameter represents a set value for the scope. "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -4586,10 +4601,12 @@ Parameter represents a set value for the scope. "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -4685,6 +4702,7 @@ Parameter represents a set value for the scope. "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -4705,10 +4723,12 @@ Parameter represents a set value for the scope. "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -4718,39 +4738,33 @@ 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 | | | -| `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 | | | -| `name` | string | false | | | -| `operating_system` | string | false | | | -| `resource_id` | string | false | | | -| `startup_script` | string | false | | | -| `status` | [codersdk.WorkspaceAgentStatus](#codersdkworkspaceagentstatus) | false | | | -| `troubleshooting_url` | string | false | | | -| `updated_at` | string | false | | | -| `version` | string | false | | | - -#### Enumerated Values - -| Property | Value | -| -------- | -------------- | -| `status` | `connecting` | -| `status` | `connected` | -| `status` | `disconnected` | -| `status` | `timeout` | +| Name | Type | Required | Restrictions | Description | +| -------------------------------- | -------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `apps` | array of [codersdk.WorkspaceApp](#codersdkworkspaceapp) | false | | | +| `architecture` | string | false | | | +| `connection_timeout_seconds` | integer | false | | | +| `created_at` | string | false | | | +| `delay_login_until_ready` | boolean | false | | Delay login until ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). | +| `directory` | string | false | | | +| `disconnected_at` | string | false | | | +| `environment_variables` | object | false | | | +| » `[any property]` | 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 | | | +| `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 | | | ## codersdk.WorkspaceAgentAuthenticateResponse @@ -4848,6 +4862,24 @@ Parameter represents a set value for the scope. | `url` | string | false | | | | `username` | string | false | | | +## codersdk.WorkspaceAgentLifecycle + +```json +"created" +``` + +### Properties + +#### Enumerated Values + +| Value | +| --------------- | +| `created` | +| `starting` | +| `start_timeout` | +| `start_error` | +| `ready` | + ## codersdk.WorkspaceAgentMetadata ```json @@ -4930,23 +4962,25 @@ Parameter represents a set value for the scope. "git_auth_configs": 0, "motd_file": "string", "startup_script": "string", + "startup_script_timeout": 0, "vscode_port_proxy_uri": "string" } ``` ### 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 | | | -| `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 | | | +| `startup_script` | string | false | | | +| `startup_script_timeout` | integer | false | | | +| `vscode_port_proxy_uri` | string | false | | | ## codersdk.WorkspaceAgentStatus @@ -5097,6 +5131,7 @@ Parameter represents a set value for the scope. "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -5117,10 +5152,12 @@ Parameter represents a set value for the scope. "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -5262,6 +5299,7 @@ Parameter represents a set value for the scope. "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -5282,10 +5320,12 @@ Parameter represents a set value for the scope. "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -5449,6 +5489,7 @@ Parameter represents a set value for the scope. "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -5469,10 +5510,12 @@ Parameter represents a set value for the scope. "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", diff --git a/docs/api/templates.md b/docs/api/templates.md index 670ef9f14d931..68176dc6d5d25 100644 --- a/docs/api/templates.md +++ b/docs/api/templates.md @@ -1564,6 +1564,7 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/d "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -1584,10 +1585,12 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/d "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -1624,61 +1627,64 @@ 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 | | | -| `»» 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 | | | -| `»» name` | string | false | | | -| `»» operating_system` | string | false | | | -| `»» resource_id` | string(uuid) | false | | | -| `»» startup_script` | string | false | | | -| `»» 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 | | | +| `»» delay_login_until_ready` | boolean | false | | »delay login until ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). | +| `»» directory` | string | false | | | +| `»» disconnected_at` | string(date-time) | false | | | +| `»» environment_variables` | object | false | | | +| `»»» [any property]` | 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 | | | +| `»» 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 | | | #### Enumerated Values @@ -1691,6 +1697,11 @@ Status Code **200** | `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` | @@ -1905,6 +1916,7 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/r "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -1925,10 +1937,12 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/r "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -1965,61 +1979,64 @@ 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 | | | -| `»» 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 | | | -| `»» name` | string | false | | | -| `»» operating_system` | string | false | | | -| `»» resource_id` | string(uuid) | false | | | -| `»» startup_script` | string | false | | | -| `»» 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 | | | +| `»» delay_login_until_ready` | boolean | false | | »delay login until ready if true, the agent will delay logins until it is ready (e.g. executing startup script has ended). | +| `»» directory` | string | false | | | +| `»» disconnected_at` | string(date-time) | false | | | +| `»» environment_variables` | object | false | | | +| `»»» [any property]` | 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 | | | +| `»» 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 | | | #### Enumerated Values @@ -2032,6 +2049,11 @@ Status Code **200** | `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` | diff --git a/docs/api/workspaces.md b/docs/api/workspaces.md index e552bc6b4236c..f51d33d76dbd8 100644 --- a/docs/api/workspaces.md +++ b/docs/api/workspaces.md @@ -80,6 +80,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -100,10 +101,12 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -241,6 +244,7 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -261,10 +265,12 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -421,6 +427,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \ "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -441,10 +448,12 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \ "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", @@ -583,6 +592,7 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \ "architecture": "string", "connection_timeout_seconds": 0, "created_at": "2019-08-24T14:15:22Z", + "delay_login_until_ready": true, "directory": "string", "disconnected_at": "2019-08-24T14:15:22Z", "environment_variables": { @@ -603,10 +613,12 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \ "preferred": true } }, + "lifecycle_state": "created", "name": "string", "operating_system": "string", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "startup_script": "string", + "startup_script_timeout_seconds": 0, "status": "connecting", "troubleshooting_url": "string", "updated_at": "2019-08-24T14:15:22Z", diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go index 7e701cc82749e..385ae2cc9be34 100644 --- a/provisioner/terraform/resources.go +++ b/provisioner/terraform/resources.go @@ -16,17 +16,19 @@ 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"` + 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"` + DelayLoginUntilReady bool `mapstructure:"delay_login_until_ready"` + StartupScriptTimeoutSeconds int32 `mapstructure:"startup_script_timeout"` } // A mapping of attributes on the "coder_app" resource. @@ -122,16 +124,18 @@ func ConvertResourcesAndParameters(modules []*tfjson.StateModule, rawGraph strin agentNames[tfResource.Name] = struct{}{} 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, + 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, + DelayLoginUntilReady: attrs.DelayLoginUntilReady, + StartupScriptTimeoutSeconds: attrs.StartupScriptTimeoutSeconds, } switch attrs.Auth { case "token": diff --git a/provisioner/terraform/resources_test.go b/provisioner/terraform/resources_test.go index f299e64a8ed3b..95d81b7540317 100644 --- a/provisioner/terraform/resources_test.go +++ b/provisioner/terraform/resources_test.go @@ -100,25 +100,31 @@ 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, + Name: "dev1", + OperatingSystem: "linux", + Architecture: "amd64", + Auth: &proto.Agent_Token{}, + ConnectionTimeoutSeconds: 120, + DelayLoginUntilReady: false, + StartupScriptTimeoutSeconds: 300, }, { - Name: "dev2", - OperatingSystem: "darwin", - Architecture: "amd64", - Auth: &proto.Agent_Token{}, - ConnectionTimeoutSeconds: 1, - MotdFile: "/etc/motd", + Name: "dev2", + OperatingSystem: "darwin", + Architecture: "amd64", + Auth: &proto.Agent_Token{}, + ConnectionTimeoutSeconds: 1, + MotdFile: "/etc/motd", + DelayLoginUntilReady: false, + StartupScriptTimeoutSeconds: 30, }, { - Name: "dev3", - OperatingSystem: "windows", - Architecture: "arm64", - Auth: &proto.Agent_Token{}, - ConnectionTimeoutSeconds: 120, - TroubleshootingUrl: "https://coder.com/troubleshoot", + Name: "dev3", + OperatingSystem: "windows", + Architecture: "arm64", + Auth: &proto.Agent_Token{}, + ConnectionTimeoutSeconds: 120, + TroubleshootingUrl: "https://coder.com/troubleshoot", + DelayLoginUntilReady: true, + StartupScriptTimeoutSeconds: 300, }}, }}, }, @@ -231,7 +237,8 @@ func TestConvertResources(t *testing.T) { ConnectionTimeoutSeconds: 120, }}, }, - }}, + }, + }, "rich-parameters": { resources: []*proto.Resource{{ Name: "dev", diff --git a/provisioner/terraform/testdata/generate.sh b/provisioner/terraform/testdata/generate.sh index 9e6b7ccdc0587..29e26a310c522 100755 --- a/provisioner/terraform/testdata/generate.sh +++ b/provisioner/terraform/testdata/generate.sh @@ -9,6 +9,7 @@ for d in */; do # This needs care to update correctly. if [[ $name == "kubernetes-metadata" ]]; then + popd continue fi diff --git a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tf b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tf index ca40d81479532..3db1113f2dd3b 100644 --- a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tf +++ b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tf @@ -2,7 +2,7 @@ terraform { required_providers { coder = { source = "coder/coder" - version = "0.6.4" + version = "0.6.7" } } } @@ -13,16 +13,19 @@ resource "coder_agent" "dev1" { } resource "coder_agent" "dev2" { - os = "darwin" - arch = "amd64" - connection_timeout = 1 - motd_file = "/etc/motd" + os = "darwin" + arch = "amd64" + connection_timeout = 1 + motd_file = "/etc/motd" + startup_script_timeout = 30 + delay_login_until_ready = false } resource "coder_agent" "dev3" { - os = "windows" - arch = "arm64" - troubleshooting_url = "https://coder.com/troubleshoot" + os = "windows" + arch = "arm64" + troubleshooting_url = "https://coder.com/troubleshoot" + delay_login_until_ready = true } resource "null_resource" "dev" { diff --git a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfplan.json b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfplan.json index 66ec6c3d868b2..ef9f6e331b9a6 100644 --- a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfplan.json +++ b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfplan.json @@ -1,6 +1,6 @@ { "format_version": "1.1", - "terraform_version": "1.3.3", + "terraform_version": "1.3.7", "planned_values": { "root_module": { "resources": [ @@ -15,11 +15,14 @@ "arch": "amd64", "auth": "token", "connection_timeout": 120, + "delay_login_until_ready": false, "dir": null, "env": null, "motd_file": null, "os": "linux", + "shutdown_script": null, "startup_script": null, + "startup_script_timeout": 300, "troubleshooting_url": null }, "sensitive_values": {} @@ -35,11 +38,14 @@ "arch": "amd64", "auth": "token", "connection_timeout": 1, + "delay_login_until_ready": false, "dir": null, "env": null, "motd_file": "/etc/motd", "os": "darwin", + "shutdown_script": null, "startup_script": null, + "startup_script_timeout": 30, "troubleshooting_url": null }, "sensitive_values": {} @@ -55,11 +61,14 @@ "arch": "arm64", "auth": "token", "connection_timeout": 120, + "delay_login_until_ready": true, "dir": null, "env": null, "motd_file": null, "os": "windows", + "shutdown_script": null, "startup_script": null, + "startup_script_timeout": 300, "troubleshooting_url": "https://coder.com/troubleshoot" }, "sensitive_values": {} @@ -95,11 +104,14 @@ "arch": "amd64", "auth": "token", "connection_timeout": 120, + "delay_login_until_ready": false, "dir": null, "env": null, "motd_file": null, "os": "linux", + "shutdown_script": null, "startup_script": null, + "startup_script_timeout": 300, "troubleshooting_url": null }, "after_unknown": { @@ -128,11 +140,14 @@ "arch": "amd64", "auth": "token", "connection_timeout": 1, + "delay_login_until_ready": false, "dir": null, "env": null, "motd_file": "/etc/motd", "os": "darwin", + "shutdown_script": null, "startup_script": null, + "startup_script_timeout": 30, "troubleshooting_url": null }, "after_unknown": { @@ -161,11 +176,14 @@ "arch": "arm64", "auth": "token", "connection_timeout": 120, + "delay_login_until_ready": true, "dir": null, "env": null, "motd_file": null, "os": "windows", + "shutdown_script": null, "startup_script": null, + "startup_script_timeout": 300, "troubleshooting_url": "https://coder.com/troubleshoot" }, "after_unknown": { @@ -206,7 +224,7 @@ "coder": { "name": "coder", "full_name": "registry.terraform.io/coder/coder", - "version_constraint": "0.6.4" + "version_constraint": "0.6.7" }, "null": { "name": "null", @@ -244,11 +262,17 @@ "connection_timeout": { "constant_value": 1 }, + "delay_login_until_ready": { + "constant_value": false + }, "motd_file": { "constant_value": "/etc/motd" }, "os": { "constant_value": "darwin" + }, + "startup_script_timeout": { + "constant_value": 30 } }, "schema_version": 0 @@ -263,6 +287,9 @@ "arch": { "constant_value": "arm64" }, + "delay_login_until_ready": { + "constant_value": true + }, "os": { "constant_value": "windows" }, diff --git a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json index 68db0a20abf8d..b4f0d1594bdf5 100644 --- a/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json +++ b/provisioner/terraform/testdata/multiple-agents/multiple-agents.tfstate.json @@ -1,6 +1,6 @@ { "format_version": "1.0", - "terraform_version": "1.3.3", + "terraform_version": "1.3.7", "values": { "root_module": { "resources": [ @@ -15,14 +15,17 @@ "arch": "amd64", "auth": "token", "connection_timeout": 120, + "delay_login_until_ready": false, "dir": null, "env": null, - "id": "e545d734-f852-4fda-ac8f-39e3ff094e58", + "id": "8ba856b4-4aa9-4fbe-aa04-9af53dc93f08", "init_script": "", "motd_file": null, "os": "linux", + "shutdown_script": null, "startup_script": null, - "token": "c2c47266-af7a-467c-9ffc-30c3270ffecb", + "startup_script_timeout": 300, + "token": "c7f72bfa-c84c-4b52-898c-1c056a42141e", "troubleshooting_url": null }, "sensitive_values": {} @@ -38,14 +41,17 @@ "arch": "amd64", "auth": "token", "connection_timeout": 1, + "delay_login_until_ready": false, "dir": null, "env": null, - "id": "b5e18556-d202-478f-80d9-76f34a4cb105", + "id": "3e79acd5-88f4-4de6-8145-a8e0e2b1129b", "init_script": "", "motd_file": "/etc/motd", "os": "darwin", + "shutdown_script": null, "startup_script": null, - "token": "795082f9-642a-4647-a595-6539edaa74a3", + "startup_script_timeout": 30, + "token": "0e2fa4e7-df44-41c4-a6e4-9e46c6f5e441", "troubleshooting_url": null }, "sensitive_values": {} @@ -61,14 +67,17 @@ "arch": "arm64", "auth": "token", "connection_timeout": 120, + "delay_login_until_ready": true, "dir": null, "env": null, - "id": "27e1114a-bc92-4e35-ab57-1680f3b7658f", + "id": "c9df6531-41f4-4005-b1a7-b4c4712b117f", "init_script": "", "motd_file": null, "os": "windows", + "shutdown_script": null, "startup_script": null, - "token": "c4fc1679-eb42-4d9f-bca8-fcf9641a7256", + "startup_script_timeout": 300, + "token": "68f0e144-22ce-455f-be00-edef913f34a6", "troubleshooting_url": "https://coder.com/troubleshoot" }, "sensitive_values": {} @@ -81,7 +90,7 @@ "provider_name": "registry.terraform.io/hashicorp/null", "schema_version": 0, "values": { - "id": "5577006791947779410", + "id": "5895060021397476995", "triggers": null }, "sensitive_values": {}, diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index 1355451e5c57e..ce29977d21542 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -1013,10 +1013,12 @@ 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"` + 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"` + DelayLoginUntilReady bool `protobuf:"varint,14,opt,name=delay_login_until_ready,json=delayLoginUntilReady,proto3" json:"delay_login_until_ready,omitempty"` + StartupScriptTimeoutSeconds int32 `protobuf:"varint,15,opt,name=startup_script_timeout_seconds,json=startupScriptTimeoutSeconds,proto3" json:"startup_script_timeout_seconds,omitempty"` } func (x *Agent) Reset() { @@ -1149,6 +1151,20 @@ func (x *Agent) GetMotdFile() string { return "" } +func (x *Agent) GetDelayLoginUntilReady() bool { + if x != nil { + return x.DelayLoginUntilReady + } + return false +} + +func (x *Agent) GetStartupScriptTimeoutSeconds() int32 { + if x != nil { + return x.StartupScriptTimeoutSeconds + } + return 0 +} + type isAgent_Auth interface { isAgent_Auth() } @@ -2459,7 +2475,7 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x0a, 0x14, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x41, 0x75, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x22, 0x9b, 0x04, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, + 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x22, 0x97, 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, @@ -2489,181 +2505,188 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x74, 0x72, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x73, 0x68, 0x6f, 0x6f, 0x74, 0x69, 0x6e, 0x67, 0x55, 0x72, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x6f, 0x74, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x6f, 0x74, - 0x64, 0x46, 0x69, 0x6c, 0x65, 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, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, - 0x04, 0x61, 0x75, 0x74, 0x68, 0x22, 0xb5, 0x02, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, - 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, - 0x67, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, - 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, - 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, - 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, - 0x69, 0x6e, 0x12, 0x3a, 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, - 0x6b, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, - 0x6b, 0x52, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x41, - 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, - 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, - 0x76, 0x65, 0x6c, 0x52, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, - 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x22, 0x59, 0x0a, - 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, - 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, - 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, - 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, - 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x22, 0xf1, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, - 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x04, 0x68, 0x69, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, - 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, - 0x0d, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73, - 0x74, 0x1a, 0x69, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x10, 0x0a, + 0x64, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x35, 0x0a, 0x17, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x6c, + 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x79, + 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x4c, 0x6f, 0x67, + 0x69, 0x6e, 0x55, 0x6e, 0x74, 0x69, 0x6c, 0x52, 0x65, 0x61, 0x64, 0x79, 0x12, 0x43, 0x0a, 0x1e, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 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, 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, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, - 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, - 0x69, 0x76, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x4e, 0x75, 0x6c, 0x6c, 0x22, 0xfc, 0x01, 0x0a, - 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x1a, 0x27, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x1a, - 0x55, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x49, 0x0a, 0x11, 0x70, - 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, - 0x68, 0x65, 0x6d, 0x61, 0x52, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, - 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x1a, 0x73, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, - 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, - 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x43, - 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, - 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x82, 0x0a, 0x0a, 0x09, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0xd1, 0x02, 0x0a, 0x08, 0x4d, 0x65, - 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, - 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x64, 0x65, 0x72, - 0x55, 0x72, 0x6c, 0x12, 0x53, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, - 0x27, 0x0a, 0x0f, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, - 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, - 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, - 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77, - 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, - 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, - 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, - 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, - 0x69, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x1a, 0x79, 0x0a, - 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, - 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, - 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a, 0x08, 0x6d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x06, 0x0a, 0x04, 0x61, 0x75, 0x74, + 0x68, 0x22, 0xb5, 0x02, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6c, 0x75, + 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, 0x12, 0x21, 0x0a, + 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, + 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x12, 0x0a, 0x04, + 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, + 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x75, 0x62, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x3a, + 0x0a, 0x0b, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x0b, 0x68, + 0x65, 0x61, 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x41, 0x0a, 0x0d, 0x73, 0x68, + 0x61, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, + 0x0c, 0x73, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1a, 0x0a, + 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x08, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x22, 0x59, 0x0a, 0x0b, 0x48, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, 0x68, + 0x6f, 0x6c, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74, 0x68, 0x72, 0x65, 0x73, + 0x68, 0x6f, 0x6c, 0x64, 0x22, 0xf1, 0x02, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x06, 0x61, 0x67, 0x65, + 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, + 0x67, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x3a, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, + 0x61, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x69, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x04, 0x68, 0x69, 0x64, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x73, + 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x5f, 0x63, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x09, 0x64, 0x61, 0x69, 0x6c, 0x79, 0x43, 0x6f, 0x73, 0x74, 0x1a, 0x69, 0x0a, + 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 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, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x12, + 0x17, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x6e, 0x75, 0x6c, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x06, 0x69, 0x73, 0x4e, 0x75, 0x6c, 0x6c, 0x22, 0xfc, 0x01, 0x0a, 0x05, 0x50, 0x61, 0x72, + 0x73, 0x65, 0x1a, 0x27, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, + 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x1a, 0x55, 0x0a, 0x08, 0x43, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x49, 0x0a, 0x11, 0x70, 0x61, 0x72, 0x61, 0x6d, + 0x65, 0x74, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, + 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x52, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, + 0x61, 0x73, 0x1a, 0x73, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x24, + 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, 0x52, + 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x39, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, + 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, + 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x82, 0x0a, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x1a, 0xd1, 0x02, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x72, 0x6c, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x55, 0x72, 0x6c, 0x12, + 0x53, 0x0a, 0x14, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x61, + 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, + 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x57, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x77, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x77, + 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, + 0x77, 0x6e, 0x65, 0x72, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x77, 0x6f, 0x72, 0x6b, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x10, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, + 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, + 0x77, 0x6e, 0x65, 0x72, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x1a, 0x79, 0x0a, 0x06, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x79, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x1a, 0xda, 0x01, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x35, 0x0a, + 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x1a, 0xda, 0x01, 0x0a, 0x04, 0x50, 0x6c, 0x61, - 0x6e, 0x12, 0x35, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, - 0x0f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, - 0x12, 0x53, 0x0a, 0x15, 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, - 0x65, 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x46, 0x0a, 0x10, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, + 0x72, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0f, 0x70, 0x61, 0x72, + 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x53, 0x0a, 0x15, + 0x72, 0x69, 0x63, 0x68, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x5f, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x52, 0x13, 0x72, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x73, 0x1a, 0x52, 0x0a, 0x05, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x35, - 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, - 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x08, 0x0a, 0x06, 0x43, 0x61, 0x6e, - 0x63, 0x65, 0x6c, 0x1a, 0xb3, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x31, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x73, 0x1a, 0x52, 0x0a, 0x05, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x35, 0x0a, 0x06, 0x63, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x08, 0x0a, 0x06, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x1a, + 0xb3, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x04, 0x70, + 0x6c, 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x34, + 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, - 0x61, 0x6e, 0x12, 0x34, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x48, - 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, - 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x2e, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, - 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x1a, 0xbb, 0x01, 0x0a, 0x08, 0x43, 0x6f, - 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, - 0x6f, 0x72, 0x12, 0x33, 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, - 0x65, 0x74, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, - 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, - 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x1a, 0x77, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, - 0x6f, 0x67, 0x48, 0x00, 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x3d, 0x0a, 0x08, 0x63, 0x6f, 0x6d, - 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, - 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, - 0x2a, 0x3f, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, - 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, - 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, - 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, - 0x04, 0x2a, 0x3b, 0x0a, 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, - 0x11, 0x0a, 0x0d, 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, - 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x2a, 0x37, - 0x0a, 0x13, 0x57, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, - 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, - 0x53, 0x54, 0x52, 0x4f, 0x59, 0x10, 0x02, 0x32, 0xa3, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, - 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, - 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x48, 0x00, 0x52, 0x05, 0x61, + 0x70, 0x70, 0x6c, 0x79, 0x12, 0x37, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x6e, + 0x63, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x1a, 0xbb, 0x01, 0x0a, 0x08, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, + 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x33, + 0x0a, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x12, 0x3a, 0x0a, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x69, 0x63, 0x68, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, + 0x74, 0x65, 0x72, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x12, + 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x70, + 0x6c, 0x61, 0x6e, 0x1a, 0x77, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x24, 0x0a, 0x03, 0x6c, 0x6f, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4c, 0x6f, 0x67, 0x48, 0x00, + 0x52, 0x03, 0x6c, 0x6f, 0x67, 0x12, 0x3d, 0x0a, 0x08, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, + 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6d, 0x70, + 0x6c, 0x65, 0x74, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x2a, 0x3f, 0x0a, 0x08, + 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x09, 0x0a, 0x05, 0x54, 0x52, 0x41, 0x43, + 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x08, + 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x57, 0x41, 0x52, 0x4e, + 0x10, 0x03, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x2a, 0x3b, 0x0a, + 0x0f, 0x41, 0x70, 0x70, 0x53, 0x68, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, + 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x10, 0x00, 0x12, 0x11, 0x0a, 0x0d, 0x41, + 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, + 0x0a, 0x06, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, 0x10, 0x02, 0x2a, 0x37, 0x0a, 0x13, 0x57, 0x6f, + 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, + 0x53, 0x54, 0x4f, 0x50, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x53, 0x54, 0x52, 0x4f, + 0x59, 0x10, 0x02, 0x32, 0xa3, 0x01, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x05, 0x50, 0x61, 0x72, 0x73, 0x65, 0x12, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, - 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x50, 0x0a, 0x09, 0x50, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x2d, 0x5a, - 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, - 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x65, 0x72, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x2e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x50, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x72, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, + 0x64, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x73, + 0x64, 0x6b, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index c1dcb09fcee8e..dd8a49a6a60bd 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -116,6 +116,8 @@ message Agent { int32 connection_timeout_seconds = 11; string troubleshooting_url = 12; string motd_file = 13; + bool delay_login_until_ready = 14; + int32 startup_script_timeout_seconds = 15; } enum AppSharingLevel { diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 6fd81556aae37..a6c18ebdff770 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -919,6 +919,7 @@ export interface WorkspaceAgent { readonly last_connected_at?: string readonly disconnected_at?: string readonly status: WorkspaceAgentStatus + readonly lifecycle_state: WorkspaceAgentLifecycle readonly name: string readonly resource_id: string readonly instance_id?: string @@ -932,6 +933,8 @@ export interface WorkspaceAgent { readonly latency?: Record readonly connection_timeout_seconds: number readonly troubleshooting_url: string + readonly delay_login_until_ready: boolean + readonly startup_script_timeout_seconds: number } // From codersdk/workspaceagents.go @@ -1226,6 +1229,21 @@ export const TemplateRoles: TemplateRole[] = ["", "admin", "use"] export type UserStatus = "active" | "suspended" export const UserStatuses: UserStatus[] = ["active", "suspended"] +// From codersdk/workspaceagents.go +export type WorkspaceAgentLifecycle = + | "created" + | "ready" + | "start_error" + | "start_timeout" + | "starting" +export const WorkspaceAgentLifecycles: WorkspaceAgentLifecycle[] = [ + "created", + "ready", + "start_error", + "start_timeout", + "starting", +] + // From codersdk/workspaceagents.go export type WorkspaceAgentStatus = | "connected" diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index b588d51314a05..027357a4653f9 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -301,6 +301,9 @@ export const MockWorkspaceAgent: TypesGen.WorkspaceAgent = { }, connection_timeout_seconds: 120, troubleshooting_url: "https://coder.com/troubleshoot", + lifecycle_state: "starting", + delay_login_until_ready: true, + startup_script_timeout_seconds: 120, } export const MockWorkspaceAgentDisconnected: TypesGen.WorkspaceAgent = { @@ -310,6 +313,7 @@ export const MockWorkspaceAgentDisconnected: TypesGen.WorkspaceAgent = { status: "disconnected", version: "", latency: {}, + lifecycle_state: "ready", } export const MockWorkspaceAgentOutdated: TypesGen.WorkspaceAgent = { @@ -333,6 +337,7 @@ export const MockWorkspaceAgentOutdated: TypesGen.WorkspaceAgent = { latency_ms: 221.66, }, }, + lifecycle_state: "ready", } export const MockWorkspaceAgentConnecting: TypesGen.WorkspaceAgent = { @@ -342,6 +347,7 @@ export const MockWorkspaceAgentConnecting: TypesGen.WorkspaceAgent = { status: "connecting", version: "", latency: {}, + lifecycle_state: "created", } export const MockWorkspaceAgentTimeout: TypesGen.WorkspaceAgent = { @@ -351,6 +357,28 @@ export const MockWorkspaceAgentTimeout: TypesGen.WorkspaceAgent = { status: "timeout", version: "", latency: {}, + lifecycle_state: "created", +} + +export const MockWorkspaceAgentStarting: TypesGen.WorkspaceAgent = { + ...MockWorkspaceAgent, + id: "test-workspace-agent-starting", + name: "a-starting-workspace-agent", + lifecycle_state: "starting", +} + +export const MockWorkspaceAgentStartTimeout: TypesGen.WorkspaceAgent = { + ...MockWorkspaceAgent, + id: "test-workspace-agent-start-timeout", + name: "a-workspace-agent-timed-out-while-running-startup-script", + lifecycle_state: "start_timeout", +} + +export const MockWorkspaceAgentStartError: TypesGen.WorkspaceAgent = { + ...MockWorkspaceAgent, + id: "test-workspace-agent-start-error", + name: "a-workspace-agent-errored-while-running-startup-script", + lifecycle_state: "start_error", } export const MockWorkspaceResource: TypesGen.WorkspaceResource = {