diff --git a/agent/agent.go b/agent/agent.go index cdab56d935ce3..d5045f499b228 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -12,7 +12,6 @@ import ( "net/http" "net/netip" "os" - "os/exec" "os/user" "path/filepath" "runtime" @@ -37,6 +36,7 @@ import ( "cdr.dev/slog" "github.com/coder/coder/v2/agent/agentproc" + "github.com/coder/coder/v2/agent/agentscripts" "github.com/coder/coder/v2/agent/agentssh" "github.com/coder/coder/v2/agent/reconnectingpty" "github.com/coder/coder/v2/buildinfo" @@ -196,6 +196,7 @@ type agent struct { manifest atomic.Pointer[agentsdk.Manifest] // manifest is atomic because values can change after reconnection. reportMetadataInterval time.Duration + scriptRunner *agentscripts.Runner serviceBanner atomic.Pointer[codersdk.ServiceBannerConfig] // serviceBanner is atomic because it is periodically updated. serviceBannerRefreshInterval time.Duration sessionToken atomic.Pointer[string] @@ -238,7 +239,13 @@ func (a *agent) init(ctx context.Context) { sshSrv.Manifest = &a.manifest sshSrv.ServiceBanner = &a.serviceBanner a.sshServer = sshSrv - + a.scriptRunner = agentscripts.New(agentscripts.Options{ + LogDir: a.logDir, + Logger: a.logger, + SSHServer: sshSrv, + Filesystem: a.filesystem, + PatchLogs: a.client.PatchLogs, + }) go a.runLoop(ctx) } @@ -657,41 +664,29 @@ func (a *agent) run(ctx context.Context) error { } } - lifecycleState := codersdk.WorkspaceAgentLifecycleReady - scriptDone := make(chan error, 1) - err = a.trackConnGoroutine(func() { - defer close(scriptDone) - scriptDone <- a.runStartupScript(ctx, manifest.StartupScript) - }) + err = a.scriptRunner.Init(manifest.Scripts) if err != nil { - return xerrors.Errorf("track startup script: %w", err) + return xerrors.Errorf("init script runner: %w", err) } - go func() { - 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 manifest.StartupScriptTimeout > 0 { - t := time.NewTimer(manifest.StartupScriptTimeout) - defer t.Stop() - timeout = t.C - } - - var err error - select { - case err = <-scriptDone: - case <-timeout: - a.logger.Warn(ctx, "script timed out", slog.F("lifecycle", "startup"), slog.F("timeout", manifest.StartupScriptTimeout)) - a.setLifecycle(ctx, codersdk.WorkspaceAgentLifecycleStartTimeout) - err = <-scriptDone // The script can still complete after a timeout. - } + err = a.trackConnGoroutine(func() { + err := a.scriptRunner.Execute(ctx, func(script codersdk.WorkspaceAgentScript) bool { + return script.RunOnStart + }) if err != nil { - if errors.Is(err, context.Canceled) { - return + a.logger.Warn(ctx, "startup script failed", slog.Error(err)) + if errors.Is(err, agentscripts.ErrTimeout) { + a.setLifecycle(ctx, codersdk.WorkspaceAgentLifecycleStartTimeout) + } else { + a.setLifecycle(ctx, codersdk.WorkspaceAgentLifecycleStartError) } - lifecycleState = codersdk.WorkspaceAgentLifecycleStartError + } else { + a.setLifecycle(ctx, codersdk.WorkspaceAgentLifecycleReady) } - a.setLifecycle(ctx, lifecycleState) - }() + a.scriptRunner.StartCron() + }) + if err != nil { + return xerrors.Errorf("track conn goroutine: %w", err) + } } // This automatically closes when the context ends! @@ -1006,93 +1001,6 @@ func (a *agent) runDERPMapSubscriber(ctx context.Context, network *tailnet.Conn) } } -func (a *agent) runStartupScript(ctx context.Context, script string) error { - return a.runScript(ctx, "startup", script) -} - -func (a *agent) runShutdownScript(ctx context.Context, script string) error { - return a.runScript(ctx, "shutdown", script) -} - -func (a *agent) runScript(ctx context.Context, lifecycle, script string) (err error) { - if script == "" { - return nil - } - - logger := a.logger.With(slog.F("lifecycle", lifecycle)) - - logger.Info(ctx, fmt.Sprintf("running %s script", lifecycle), slog.F("script", script)) - fileWriter, err := a.filesystem.OpenFile(filepath.Join(a.logDir, fmt.Sprintf("coder-%s-script.log", lifecycle)), os.O_CREATE|os.O_RDWR, 0o600) - if err != nil { - return xerrors.Errorf("open %s script log file: %w", lifecycle, err) - } - defer func() { - err := fileWriter.Close() - if err != nil { - logger.Warn(ctx, fmt.Sprintf("close %s script log file", lifecycle), slog.Error(err)) - } - }() - - cmdPty, err := a.sshServer.CreateCommand(ctx, script, nil) - if err != nil { - return xerrors.Errorf("%s script: create command: %w", lifecycle, err) - } - cmd := cmdPty.AsExec() - - var stdout, stderr io.Writer = fileWriter, fileWriter - if lifecycle == "startup" { - send, flushAndClose := agentsdk.LogsSender(a.client.PatchLogs, logger) - // If ctx is canceled here (or in a writer below), we may be - // discarding logs, but that's okay because we're shutting down - // anyway. We could consider creating a new context here if we - // want better control over flush during shutdown. - defer func() { - if err := flushAndClose(ctx); err != nil { - logger.Warn(ctx, "flush startup logs failed", slog.Error(err)) - } - }() - - infoW := agentsdk.StartupLogsWriter(ctx, send, codersdk.WorkspaceAgentLogSourceStartupScript, codersdk.LogLevelInfo) - defer infoW.Close() - errW := agentsdk.StartupLogsWriter(ctx, send, codersdk.WorkspaceAgentLogSourceStartupScript, codersdk.LogLevelError) - defer errW.Close() - - stdout = io.MultiWriter(fileWriter, infoW) - stderr = io.MultiWriter(fileWriter, errW) - } - - cmd.Stdout = stdout - cmd.Stderr = stderr - - start := time.Now() - defer func() { - end := time.Now() - execTime := end.Sub(start) - exitCode := 0 - if err != nil { - exitCode = 255 // Unknown status. - var exitError *exec.ExitError - if xerrors.As(err, &exitError) { - exitCode = exitError.ExitCode() - } - logger.Warn(ctx, fmt.Sprintf("%s script failed", lifecycle), slog.F("execution_time", execTime), slog.F("exit_code", exitCode), slog.Error(err)) - } else { - logger.Info(ctx, fmt.Sprintf("%s script completed", lifecycle), slog.F("execution_time", execTime), slog.F("exit_code", exitCode)) - } - }() - - err = cmd.Run() - if err != nil { - // cmd.Run does not return a context canceled error, it returns "signal: killed". - if ctx.Err() != nil { - return ctx.Err() - } - - return xerrors.Errorf("%s script: run: %w", lifecycle, err) - } - return nil -} - func (a *agent) handleReconnectingPTY(ctx context.Context, logger slog.Logger, msg codersdk.WorkspaceAgentReconnectingPTYInit, conn net.Conn) (retErr error) { defer conn.Close() a.metrics.connectionsTotal.Add(1) @@ -1475,39 +1383,23 @@ func (a *agent) Close() error { } lifecycleState := codersdk.WorkspaceAgentLifecycleOff - if manifest := a.manifest.Load(); manifest != nil && manifest.ShutdownScript != "" { - scriptDone := make(chan error, 1) - go func() { - defer close(scriptDone) - scriptDone <- a.runShutdownScript(ctx, manifest.ShutdownScript) - }() - - var timeout <-chan time.Time - // If timeout is zero, an older version of the coder - // provider was used. Otherwise a timeout is always > 0. - if manifest.ShutdownScriptTimeout > 0 { - t := time.NewTimer(manifest.ShutdownScriptTimeout) - defer t.Stop() - timeout = t.C - } - - var err error - select { - case err = <-scriptDone: - case <-timeout: - a.logger.Warn(ctx, "script timed out", slog.F("lifecycle", "shutdown"), slog.F("timeout", manifest.ShutdownScriptTimeout)) - a.setLifecycle(ctx, codersdk.WorkspaceAgentLifecycleShutdownTimeout) - err = <-scriptDone // The script can still complete after a timeout. - } - if err != nil { + err = a.scriptRunner.Execute(ctx, func(script codersdk.WorkspaceAgentScript) bool { + return script.RunOnStop + }) + if err != nil { + if errors.Is(err, agentscripts.ErrTimeout) { + lifecycleState = codersdk.WorkspaceAgentLifecycleShutdownTimeout + } else { lifecycleState = codersdk.WorkspaceAgentLifecycleShutdownError } } - - // Set final state and wait for it to be reported because context - // cancellation will stop the report loop. a.setLifecycle(ctx, lifecycleState) + err = a.scriptRunner.Close() + if err != nil { + a.logger.Error(ctx, "script runner close", slog.Error(err)) + } + // Wait for the lifecycle to be reported, but don't wait forever so // that we don't break user expectations. ctx, cancel := context.WithTimeout(ctx, 5*time.Second) diff --git a/agent/agent_test.go b/agent/agent_test.go index 57cd14fd5da76..fb801a93e53de 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -50,7 +50,6 @@ import ( "github.com/coder/coder/v2/agent/agentproc/agentproctest" "github.com/coder/coder/v2/agent/agentssh" "github.com/coder/coder/v2/agent/agenttest" - "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/agentsdk" "github.com/coder/coder/v2/pty" @@ -1060,84 +1059,6 @@ func TestAgent_SSHConnectionEnvVars(t *testing.T) { } } -func TestAgent_StartupScript(t *testing.T) { - t.Parallel() - output := "something" - command := "sh -c 'echo " + output + "'" - if runtime.GOOS == "windows" { - command = "cmd.exe /c echo " + output - } - t.Run("Success", func(t *testing.T) { - t.Parallel() - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) - client := agenttest.NewClient(t, - logger, - uuid.New(), - agentsdk.Manifest{ - StartupScript: command, - DERPMap: &tailcfg.DERPMap{}, - }, - make(chan *agentsdk.Stats), - tailnet.NewCoordinator(logger), - ) - closer := agent.New(agent.Options{ - Client: client, - Filesystem: afero.NewMemMapFs(), - Logger: logger.Named("agent"), - ReconnectingPTYTimeout: 0, - }) - t.Cleanup(func() { - _ = closer.Close() - }) - assert.Eventually(t, func() bool { - got := client.GetLifecycleStates() - return len(got) > 0 && got[len(got)-1] == codersdk.WorkspaceAgentLifecycleReady - }, testutil.WaitShort, testutil.IntervalMedium) - - require.Len(t, client.GetStartupLogs(), 1) - require.Equal(t, output, client.GetStartupLogs()[0].Output) - }) - // This ensures that even when coderd sends back that the startup - // script has written too many lines it will still succeed! - t.Run("OverflowsAndSkips", func(t *testing.T) { - t.Parallel() - logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug) - client := agenttest.NewClient(t, - logger, - uuid.New(), - agentsdk.Manifest{ - StartupScript: command, - DERPMap: &tailcfg.DERPMap{}, - }, - make(chan *agentsdk.Stats, 50), - tailnet.NewCoordinator(logger), - ) - client.PatchWorkspaceLogs = func() error { - resp := httptest.NewRecorder() - httpapi.Write(context.Background(), resp, http.StatusRequestEntityTooLarge, codersdk.Response{ - Message: "Too many lines!", - }) - res := resp.Result() - defer res.Body.Close() - return codersdk.ReadBodyAsError(res) - } - closer := agent.New(agent.Options{ - Client: client, - Filesystem: afero.NewMemMapFs(), - Logger: logger.Named("agent"), - ReconnectingPTYTimeout: 0, - }) - t.Cleanup(func() { - _ = closer.Close() - }) - assert.Eventually(t, func() bool { - got := client.GetLifecycleStates() - return len(got) > 0 && got[len(got)-1] == codersdk.WorkspaceAgentLifecycleReady - }, testutil.WaitShort, testutil.IntervalMedium) - require.Len(t, client.GetStartupLogs(), 0) - }) -} - func TestAgent_Metadata(t *testing.T) { t.Parallel() @@ -1292,8 +1213,11 @@ func TestAgent_Lifecycle(t *testing.T) { t.Parallel() _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ - StartupScript: "sleep 3", - StartupScriptTimeout: time.Nanosecond, + Scripts: []codersdk.WorkspaceAgentScript{{ + Script: "sleep 3", + Timeout: time.Millisecond, + RunOnStart: true, + }}, }, 0) want := []codersdk.WorkspaceAgentLifecycle{ @@ -1314,8 +1238,11 @@ func TestAgent_Lifecycle(t *testing.T) { t.Parallel() _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ - StartupScript: "false", - StartupScriptTimeout: 30 * time.Second, + Scripts: []codersdk.WorkspaceAgentScript{{ + Script: "false", + Timeout: 30 * time.Second, + RunOnStart: true, + }}, }, 0) want := []codersdk.WorkspaceAgentLifecycle{ @@ -1336,8 +1263,11 @@ func TestAgent_Lifecycle(t *testing.T) { t.Parallel() _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ - StartupScript: "true", - StartupScriptTimeout: 30 * time.Second, + Scripts: []codersdk.WorkspaceAgentScript{{ + Script: "true", + Timeout: 30 * time.Second, + RunOnStart: true, + }}, }, 0) want := []codersdk.WorkspaceAgentLifecycle{ @@ -1358,8 +1288,11 @@ func TestAgent_Lifecycle(t *testing.T) { t.Parallel() _, client, _, _, closer := setupAgent(t, agentsdk.Manifest{ - ShutdownScript: "sleep 3", - StartupScriptTimeout: 30 * time.Second, + Scripts: []codersdk.WorkspaceAgentScript{{ + Script: "sleep 3", + Timeout: 30 * time.Second, + RunOnStop: true, + }}, }, 0) assert.Eventually(t, func() bool { @@ -1396,8 +1329,11 @@ func TestAgent_Lifecycle(t *testing.T) { t.Parallel() _, client, _, _, closer := setupAgent(t, agentsdk.Manifest{ - ShutdownScript: "sleep 3", - ShutdownScriptTimeout: time.Nanosecond, + Scripts: []codersdk.WorkspaceAgentScript{{ + Script: "sleep 3", + Timeout: time.Millisecond, + RunOnStop: true, + }}, }, 0) assert.Eventually(t, func() bool { @@ -1435,8 +1371,11 @@ func TestAgent_Lifecycle(t *testing.T) { t.Parallel() _, client, _, _, closer := setupAgent(t, agentsdk.Manifest{ - ShutdownScript: "false", - ShutdownScriptTimeout: 30 * time.Second, + Scripts: []codersdk.WorkspaceAgentScript{{ + Script: "false", + Timeout: 30 * time.Second, + RunOnStop: true, + }}, }, 0) assert.Eventually(t, func() bool { @@ -1480,9 +1419,16 @@ func TestAgent_Lifecycle(t *testing.T) { logger, uuid.New(), agentsdk.Manifest{ - DERPMap: derpMap, - StartupScript: "echo 1", - ShutdownScript: "echo " + expected, + DERPMap: derpMap, + Scripts: []codersdk.WorkspaceAgentScript{{ + LogPath: "coder-startup-script.log", + Script: "echo 1", + RunOnStart: true, + }, { + LogPath: "coder-shutdown-script.log", + Script: "echo " + expected, + RunOnStop: true, + }}, }, make(chan *agentsdk.Stats, 50), tailnet.NewCoordinator(logger), @@ -1533,9 +1479,7 @@ func TestAgent_Startup(t *testing.T) { t.Parallel() _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ - StartupScript: "true", - StartupScriptTimeout: 30 * time.Second, - Directory: "", + Directory: "", }, 0) assert.Eventually(t, func() bool { return client.GetStartup().Version != "" @@ -1547,9 +1491,7 @@ func TestAgent_Startup(t *testing.T) { t.Parallel() _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ - StartupScript: "true", - StartupScriptTimeout: 30 * time.Second, - Directory: "~", + Directory: "~", }, 0) assert.Eventually(t, func() bool { return client.GetStartup().Version != "" @@ -1563,9 +1505,7 @@ func TestAgent_Startup(t *testing.T) { t.Parallel() _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ - StartupScript: "true", - StartupScriptTimeout: 30 * time.Second, - Directory: "coder/coder", + Directory: "coder/coder", }, 0) assert.Eventually(t, func() bool { return client.GetStartup().Version != "" @@ -1579,9 +1519,7 @@ func TestAgent_Startup(t *testing.T) { t.Parallel() _, client, _, _, _ := setupAgent(t, agentsdk.Manifest{ - StartupScript: "true", - StartupScriptTimeout: 30 * time.Second, - Directory: "$HOME", + Directory: "$HOME", }, 0) assert.Eventually(t, func() bool { return client.GetStartup().Version != "" diff --git a/agent/agentscripts/agentscripts.go b/agent/agentscripts/agentscripts.go new file mode 100644 index 0000000000000..970b8004e9a60 --- /dev/null +++ b/agent/agentscripts/agentscripts.go @@ -0,0 +1,269 @@ +package agentscripts + +import ( + "context" + "errors" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "sync" + "sync/atomic" + "time" + + "github.com/robfig/cron/v3" + "github.com/spf13/afero" + "golang.org/x/sync/errgroup" + "golang.org/x/xerrors" + + "cdr.dev/slog" + "github.com/coder/coder/v2/agent/agentssh" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/agentsdk" +) + +var ( + // ErrTimeout is returned when a script times out. + ErrTimeout = xerrors.New("script timed out") + + parser = cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.DowOptional) +) + +// Options are a set of options for the runner. +type Options struct { + LogDir string + Logger slog.Logger + SSHServer *agentssh.Server + Filesystem afero.Fs + PatchLogs func(ctx context.Context, req agentsdk.PatchLogs) error +} + +// New creates a runner for the provided scripts. +func New(opts Options) *Runner { + cronCtx, cronCtxCancel := context.WithCancel(context.Background()) + return &Runner{ + Options: opts, + cronCtx: cronCtx, + cronCtxCancel: cronCtxCancel, + cron: cron.New(cron.WithParser(parser)), + closed: make(chan struct{}), + } +} + +type Runner struct { + Options + + cronCtx context.Context + cronCtxCancel context.CancelFunc + cmdCloseWait sync.WaitGroup + closed chan struct{} + closeMutex sync.Mutex + cron *cron.Cron + initialized atomic.Bool + scripts []codersdk.WorkspaceAgentScript +} + +// Init initializes the runner with the provided scripts. +// It also schedules any scripts that have a schedule. +// This function must be called before Execute. +func (r *Runner) Init(scripts []codersdk.WorkspaceAgentScript) error { + if r.initialized.Load() { + return xerrors.New("init: already initialized") + } + r.initialized.Store(true) + r.scripts = scripts + r.Logger.Info(r.cronCtx, "initializing agent scripts", slog.F("script_count", len(scripts)), slog.F("log_dir", r.LogDir)) + + for _, script := range scripts { + if script.Cron == "" { + continue + } + script := script + _, err := r.cron.AddFunc(script.Cron, func() { + err := r.run(r.cronCtx, script) + if err != nil { + r.Logger.Warn(context.Background(), "run agent script on schedule", slog.Error(err)) + } + }) + if err != nil { + return xerrors.Errorf("add schedule: %w", err) + } + } + return nil +} + +// StartCron starts the cron scheduler. +// This is done async to allow for the caller to execute scripts prior. +func (r *Runner) StartCron() { + r.cron.Start() +} + +// Execute runs a set of scripts according to a filter. +func (r *Runner) Execute(ctx context.Context, filter func(script codersdk.WorkspaceAgentScript) bool) error { + if filter == nil { + // Execute em' all! + filter = func(script codersdk.WorkspaceAgentScript) bool { + return true + } + } + var eg errgroup.Group + for _, script := range r.scripts { + if !filter(script) { + continue + } + script := script + eg.Go(func() error { + err := r.run(ctx, script) + if err != nil { + return xerrors.Errorf("run agent script %q: %w", script.LogSourceID, err) + } + return nil + }) + } + return eg.Wait() +} + +// run executes the provided script with the timeout. +// If the timeout is exceeded, the process is sent an interrupt signal. +// If the process does not exit after a few seconds, it is forcefully killed. +// This function immediately returns after a timeout, and does not wait for the process to exit. +func (r *Runner) run(ctx context.Context, script codersdk.WorkspaceAgentScript) error { + logPath := script.LogPath + if logPath == "" { + logPath = fmt.Sprintf("coder-script-%s.log", script.LogSourceID) + } + if !filepath.IsAbs(logPath) { + logPath = filepath.Join(r.LogDir, logPath) + } + logger := r.Logger.With(slog.F("log_path", logPath)) + logger.Info(ctx, "running agent script", slog.F("script", script.Script)) + + fileWriter, err := r.Filesystem.OpenFile(logPath, os.O_CREATE|os.O_RDWR, 0o600) + if err != nil { + return xerrors.Errorf("open %s script log file: %w", logPath, err) + } + defer func() { + err := fileWriter.Close() + if err != nil { + logger.Warn(ctx, fmt.Sprintf("close %s script log file", logPath), slog.Error(err)) + } + }() + + var cmd *exec.Cmd + cmdCtx := ctx + if script.Timeout > 0 { + var ctxCancel context.CancelFunc + cmdCtx, ctxCancel = context.WithTimeout(ctx, script.Timeout) + defer ctxCancel() + } + cmdPty, err := r.SSHServer.CreateCommand(cmdCtx, script.Script, nil) + if err != nil { + return xerrors.Errorf("%s script: create command: %w", logPath, err) + } + cmd = cmdPty.AsExec() + cmd.SysProcAttr = cmdSysProcAttr() + cmd.WaitDelay = 10 * time.Second + cmd.Cancel = cmdCancel(cmd) + + send, flushAndClose := agentsdk.LogsSender(script.LogSourceID, r.PatchLogs, logger) + // If ctx is canceled here (or in a writer below), we may be + // discarding logs, but that's okay because we're shutting down + // anyway. We could consider creating a new context here if we + // want better control over flush during shutdown. + defer func() { + if err := flushAndClose(ctx); err != nil { + logger.Warn(ctx, "flush startup logs failed", slog.Error(err)) + } + }() + + infoW := agentsdk.LogsWriter(ctx, send, script.LogSourceID, codersdk.LogLevelInfo) + defer infoW.Close() + errW := agentsdk.LogsWriter(ctx, send, script.LogSourceID, codersdk.LogLevelError) + defer errW.Close() + cmd.Stdout = io.MultiWriter(fileWriter, infoW) + cmd.Stderr = io.MultiWriter(fileWriter, errW) + + start := time.Now() + defer func() { + end := time.Now() + execTime := end.Sub(start) + exitCode := 0 + if err != nil { + exitCode = 255 // Unknown status. + var exitError *exec.ExitError + if xerrors.As(err, &exitError) { + exitCode = exitError.ExitCode() + } + logger.Warn(ctx, fmt.Sprintf("%s script failed", logPath), slog.F("execution_time", execTime), slog.F("exit_code", exitCode), slog.Error(err)) + } else { + logger.Info(ctx, fmt.Sprintf("%s script completed", logPath), slog.F("execution_time", execTime), slog.F("exit_code", exitCode)) + } + }() + + err = cmd.Start() + if err != nil { + if errors.Is(err, context.DeadlineExceeded) { + return ErrTimeout + } + return xerrors.Errorf("%s script: start command: %w", logPath, err) + } + + cmdDone := make(chan error, 1) + err = r.trackCommandGoroutine(func() { + cmdDone <- cmd.Wait() + }) + if err != nil { + return xerrors.Errorf("%s script: track command goroutine: %w", logPath, err) + } + select { + case <-cmdCtx.Done(): + // Wait for the command to drain! + select { + case <-cmdDone: + case <-time.After(10 * time.Second): + } + err = cmdCtx.Err() + case err = <-cmdDone: + } + if errors.Is(err, context.DeadlineExceeded) { + err = ErrTimeout + } + return err +} + +func (r *Runner) Close() error { + r.closeMutex.Lock() + defer r.closeMutex.Unlock() + if r.isClosed() { + return nil + } + close(r.closed) + r.cronCtxCancel() + r.cron.Stop() + r.cmdCloseWait.Wait() + return nil +} + +func (r *Runner) trackCommandGoroutine(fn func()) error { + r.closeMutex.Lock() + defer r.closeMutex.Unlock() + if r.isClosed() { + return xerrors.New("track command goroutine: closed") + } + r.cmdCloseWait.Add(1) + go func() { + defer r.cmdCloseWait.Done() + fn() + }() + return nil +} + +func (r *Runner) isClosed() bool { + select { + case <-r.closed: + return true + default: + return false + } +} diff --git a/agent/agentscripts/agentscripts_other.go b/agent/agentscripts/agentscripts_other.go new file mode 100644 index 0000000000000..a7ab83276e67d --- /dev/null +++ b/agent/agentscripts/agentscripts_other.go @@ -0,0 +1,20 @@ +//go:build !windows + +package agentscripts + +import ( + "os/exec" + "syscall" +) + +func cmdSysProcAttr() *syscall.SysProcAttr { + return &syscall.SysProcAttr{ + Setsid: true, + } +} + +func cmdCancel(cmd *exec.Cmd) func() error { + return func() error { + return syscall.Kill(-cmd.Process.Pid, syscall.SIGHUP) + } +} diff --git a/agent/agentscripts/agentscripts_test.go b/agent/agentscripts/agentscripts_test.go new file mode 100644 index 0000000000000..1570e35d59b31 --- /dev/null +++ b/agent/agentscripts/agentscripts_test.go @@ -0,0 +1,80 @@ +package agentscripts_test + +import ( + "context" + "testing" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/spf13/afero" + "github.com/stretchr/testify/require" + "go.uber.org/atomic" + "go.uber.org/goleak" + + "cdr.dev/slog/sloggers/slogtest" + "github.com/coder/coder/v2/agent/agentscripts" + "github.com/coder/coder/v2/agent/agentssh" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/codersdk/agentsdk" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} + +func TestExecuteBasic(t *testing.T) { + t.Parallel() + logs := make(chan agentsdk.PatchLogs, 1) + runner := setup(t, func(ctx context.Context, req agentsdk.PatchLogs) error { + logs <- req + return nil + }) + defer runner.Close() + err := runner.Init([]codersdk.WorkspaceAgentScript{{ + Script: "echo hello", + }}) + require.NoError(t, err) + require.NoError(t, runner.Execute(context.Background(), func(script codersdk.WorkspaceAgentScript) bool { + return true + })) + log := <-logs + require.Equal(t, "hello", log.Logs[0].Output) +} + +func TestTimeout(t *testing.T) { + t.Parallel() + runner := setup(t, nil) + defer runner.Close() + err := runner.Init([]codersdk.WorkspaceAgentScript{{ + Script: "sleep infinity", + Timeout: time.Millisecond, + }}) + require.NoError(t, err) + require.ErrorIs(t, runner.Execute(context.Background(), nil), agentscripts.ErrTimeout) +} + +func setup(t *testing.T, patchLogs func(ctx context.Context, req agentsdk.PatchLogs) error) *agentscripts.Runner { + t.Helper() + if patchLogs == nil { + // noop + patchLogs = func(ctx context.Context, req agentsdk.PatchLogs) error { + return nil + } + } + fs := afero.NewMemMapFs() + logger := slogtest.Make(t, nil) + s, err := agentssh.NewServer(context.Background(), logger, prometheus.NewRegistry(), fs, 0, "") + require.NoError(t, err) + s.AgentToken = func() string { return "" } + s.Manifest = atomic.NewPointer(&agentsdk.Manifest{}) + t.Cleanup(func() { + _ = s.Close() + }) + return agentscripts.New(agentscripts.Options{ + LogDir: t.TempDir(), + Logger: logger, + SSHServer: s, + Filesystem: fs, + PatchLogs: patchLogs, + }) +} diff --git a/agent/agentscripts/agentscripts_windows.go b/agent/agentscripts/agentscripts_windows.go new file mode 100644 index 0000000000000..cda1b3fcc39e1 --- /dev/null +++ b/agent/agentscripts/agentscripts_windows.go @@ -0,0 +1,17 @@ +package agentscripts + +import ( + "os" + "os/exec" + "syscall" +) + +func cmdSysProcAttr() *syscall.SysProcAttr { + return &syscall.SysProcAttr{} +} + +func cmdCancel(cmd *exec.Cmd) func() error { + return func() error { + return cmd.Process.Signal(os.Interrupt) + } +} diff --git a/cli/cliui/agent.go b/cli/cliui/agent.go index c6cc9f413fe54..42a42f82de983 100644 --- a/cli/cliui/agent.go +++ b/cli/cliui/agent.go @@ -80,6 +80,10 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO if err != nil { return xerrors.Errorf("fetch: %w", err) } + logSources := map[uuid.UUID]codersdk.WorkspaceAgentLogSource{} + for _, source := range agent.LogSources { + logSources[source.ID] = source + } sw := &stageWriter{w: writer} @@ -123,7 +127,7 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO return nil } - stage := "Running workspace agent startup script" + stage := "Running workspace agent startup scripts" follow := opts.Wait if !follow { stage += " (non-blocking)" @@ -173,7 +177,12 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO return nil } for _, log := range logs { - sw.Log(log.CreatedAt, log.Level, log.Output) + source, hasSource := logSources[log.SourceID] + output := log.Output + if hasSource && source.DisplayName != "" { + output = source.DisplayName + ": " + output + } + sw.Log(log.CreatedAt, log.Level, output) lastLog = log } } @@ -192,16 +201,19 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO switch agent.LifecycleState { case codersdk.WorkspaceAgentLifecycleReady: sw.Complete(stage, agent.ReadyAt.Sub(*agent.StartedAt)) + case codersdk.WorkspaceAgentLifecycleStartTimeout: + sw.Fail(stage, 0) + sw.Log(time.Time{}, codersdk.LogLevelWarn, "Warning: A startup script timed out and your workspace may be incomplete.") case codersdk.WorkspaceAgentLifecycleStartError: sw.Fail(stage, agent.ReadyAt.Sub(*agent.StartedAt)) // Use zero time (omitted) to separate these from the startup logs. - sw.Log(time.Time{}, codersdk.LogLevelWarn, "Warning: The startup script exited with an error and your workspace may be incomplete.") + sw.Log(time.Time{}, codersdk.LogLevelWarn, "Warning: A startup script exited with an error and your workspace may be incomplete.") sw.Log(time.Time{}, codersdk.LogLevelWarn, troubleshootingMessage(agent, "https://coder.com/docs/v2/latest/templates#startup-script-exited-with-an-error")) default: switch { case agent.LifecycleState.Starting(): // Use zero time (omitted) to separate these from the startup logs. - sw.Log(time.Time{}, codersdk.LogLevelWarn, "Notice: The startup script is still running and your workspace may be incomplete.") + sw.Log(time.Time{}, codersdk.LogLevelWarn, "Notice: The startup scripts are still running and your workspace may be incomplete.") sw.Log(time.Time{}, codersdk.LogLevelWarn, troubleshootingMessage(agent, "https://coder.com/docs/v2/latest/templates#your-workspace-may-be-incomplete")) // Note: We don't complete or fail the stage here, it's // intentionally left open to indicate this stage didn't diff --git a/cli/cliui/agent_test.go b/cli/cliui/agent_test.go index 2181c353113dd..2c474e84c22f5 100644 --- a/cli/cliui/agent_test.go +++ b/cli/cliui/agent_test.go @@ -52,11 +52,36 @@ func TestAgent(t *testing.T) { want: []string{ "⧗ Waiting for the workspace agent to connect", "✔ Waiting for the workspace agent to connect", - "⧗ Running workspace agent startup script (non-blocking)", - "Notice: The startup script is still running and your workspace may be incomplete.", + "⧗ Running workspace agent startup scripts (non-blocking)", + "Notice: The startup scripts are still running and your workspace may be incomplete.", "For more information and troubleshooting, see", }, }, + { + name: "Start timeout", + opts: cliui.AgentOptions{ + FetchInterval: time.Millisecond, + }, + iter: []func(context.Context, *codersdk.WorkspaceAgent, chan []codersdk.WorkspaceAgentLog) error{ + func(_ context.Context, agent *codersdk.WorkspaceAgent, _ chan []codersdk.WorkspaceAgentLog) error { + agent.Status = codersdk.WorkspaceAgentConnecting + return nil + }, + func(_ context.Context, agent *codersdk.WorkspaceAgent, logs chan []codersdk.WorkspaceAgentLog) error { + agent.Status = codersdk.WorkspaceAgentConnected + agent.LifecycleState = codersdk.WorkspaceAgentLifecycleStartTimeout + agent.FirstConnectedAt = ptr.Ref(time.Now()) + return nil + }, + }, + want: []string{ + "⧗ Waiting for the workspace agent to connect", + "✔ Waiting for the workspace agent to connect", + "⧗ Running workspace agent startup scripts (non-blocking)", + "✘ Running workspace agent startup scripts (non-blocking)", + "Warning: A startup script timed out and your workspace may be incomplete.", + }, + }, { name: "Initial connection timeout", opts: cliui.AgentOptions{ @@ -86,8 +111,8 @@ func TestAgent(t *testing.T) { "The workspace agent is having trouble connecting, wait for it to connect or restart your workspace.", "For more information and troubleshooting, see", "✔ Waiting for the workspace agent to connect", - "⧗ Running workspace agent startup script (non-blocking)", - "✔ Running workspace agent startup script (non-blocking)", + "⧗ Running workspace agent startup scripts (non-blocking)", + "✔ Running workspace agent startup scripts (non-blocking)", }, }, { @@ -120,7 +145,7 @@ func TestAgent(t *testing.T) { }, }, { - name: "Startup script logs", + name: "Startup Logs", opts: cliui.AgentOptions{ FetchInterval: time.Millisecond, Wait: true, @@ -131,10 +156,15 @@ func TestAgent(t *testing.T) { agent.FirstConnectedAt = ptr.Ref(time.Now()) agent.LifecycleState = codersdk.WorkspaceAgentLifecycleStarting agent.StartedAt = ptr.Ref(time.Now()) + agent.LogSources = []codersdk.WorkspaceAgentLogSource{{ + ID: uuid.Nil, + DisplayName: "testing", + }} logs <- []codersdk.WorkspaceAgentLog{ { CreatedAt: time.Now(), Output: "Hello world", + SourceID: uuid.Nil, }, } return nil @@ -152,10 +182,10 @@ func TestAgent(t *testing.T) { }, }, want: []string{ - "⧗ Running workspace agent startup script", - "Hello world", + "⧗ Running workspace agent startup scripts", + "testing: Hello world", "Bye now", - "✔ Running workspace agent startup script", + "✔ Running workspace agent startup scripts", }, }, { @@ -181,10 +211,10 @@ func TestAgent(t *testing.T) { }, }, want: []string{ - "⧗ Running workspace agent startup script", + "⧗ Running workspace agent startup scripts", "Hello world", - "✘ Running workspace agent startup script", - "Warning: The startup script exited with an error and your workspace may be incomplete.", + "✘ Running workspace agent startup scripts", + "Warning: A startup script exited with an error and your workspace may be incomplete.", "For more information and troubleshooting, see", }, }, @@ -229,9 +259,9 @@ func TestAgent(t *testing.T) { }, }, want: []string{ - "⧗ Running workspace agent startup script", + "⧗ Running workspace agent startup scripts", "Hello world", - "✔ Running workspace agent startup script", + "✔ Running workspace agent startup scripts", }, wantErr: true, }, @@ -288,11 +318,10 @@ func TestAgent(t *testing.T) { var buf bytes.Buffer agent := codersdk.WorkspaceAgent{ - ID: uuid.New(), - Status: codersdk.WorkspaceAgentConnecting, - StartupScriptBehavior: codersdk.WorkspaceAgentStartupScriptBehaviorNonBlocking, - CreatedAt: time.Now(), - LifecycleState: codersdk.WorkspaceAgentLifecycleCreated, + ID: uuid.New(), + Status: codersdk.WorkspaceAgentConnecting, + CreatedAt: time.Now(), + LifecycleState: codersdk.WorkspaceAgentLifecycleCreated, } logs := make(chan []codersdk.WorkspaceAgentLog, 1) @@ -340,6 +369,9 @@ func TestAgent(t *testing.T) { line := s.Text() t.Log(line) if len(tc.want) == 0 { + for i := 0; i < 5; i++ { + t.Log(line) + } require.Fail(t, "unexpected line", line) } require.Contains(t, line, tc.want[0]) diff --git a/cli/ssh.go b/cli/ssh.go index 4455b8987cc5f..dbff0ea52017e 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -143,13 +143,11 @@ func (r *RootCmd) ssh() *clibase.Cmd { case "no": wait = false case "auto": - switch workspaceAgent.StartupScriptBehavior { - case codersdk.WorkspaceAgentStartupScriptBehaviorBlocking: - wait = true - case codersdk.WorkspaceAgentStartupScriptBehaviorNonBlocking: - wait = false - default: - return xerrors.Errorf("unknown startup script behavior %q", workspaceAgent.StartupScriptBehavior) + for _, script := range workspaceAgent.Scripts { + if script.StartBlocksLogin { + wait = true + break + } } default: return xerrors.Errorf("unknown wait value %q", waitEnum) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index c849cd2ef81e3..96be09c86c324 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -6398,9 +6398,6 @@ const docTemplate = `{ }, "output": { "type": "string" - }, - "source": { - "$ref": "#/definitions/codersdk.WorkspaceAgentLogSource" } } }, @@ -6447,17 +6444,11 @@ const docTemplate = `{ "motd_file": { "type": "string" }, - "shutdown_script": { - "type": "string" - }, - "shutdown_script_timeout": { - "type": "integer" - }, - "startup_script": { - "type": "string" - }, - "startup_script_timeout": { - "type": "integer" + "scripts": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.WorkspaceAgentScript" + } }, "vscode_port_proxy_uri": { "type": "string" @@ -6467,6 +6458,9 @@ const docTemplate = `{ "agentsdk.PatchLogs": { "type": "object", "properties": { + "log_source_id": { + "type": "string" + }, "logs": { "type": "array", "items": { @@ -10694,9 +10688,11 @@ const docTemplate = `{ "lifecycle_state": { "$ref": "#/definitions/codersdk.WorkspaceAgentLifecycle" }, - "login_before_ready": { - "description": "Deprecated: Use StartupScriptBehavior instead.", - "type": "boolean" + "log_sources": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.WorkspaceAgentLogSource" + } }, "logs_length": { "type": "integer" @@ -10718,25 +10714,23 @@ const docTemplate = `{ "type": "string", "format": "uuid" }, - "shutdown_script": { - "type": "string" - }, - "shutdown_script_timeout_seconds": { - "type": "integer" + "scripts": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.WorkspaceAgentScript" + } }, "started_at": { "type": "string", "format": "date-time" }, - "startup_script": { - "type": "string" - }, "startup_script_behavior": { - "$ref": "#/definitions/codersdk.WorkspaceAgentStartupScriptBehavior" - }, - "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" + "description": "StartupScriptBehavior is a legacy field that is deprecated in favor\nof the ` + "`" + `coder_script` + "`" + ` resource. It's only referenced by old clients.\nDeprecated: Remove in the future!", + "allOf": [ + { + "$ref": "#/definitions/codersdk.WorkspaceAgentStartupScriptBehavior" + } + ] }, "status": { "$ref": "#/definitions/codersdk.WorkspaceAgentStatus" @@ -10856,27 +10850,35 @@ const docTemplate = `{ }, "output": { "type": "string" + }, + "source_id": { + "type": "string", + "format": "uuid" } } }, "codersdk.WorkspaceAgentLogSource": { - "type": "string", - "enum": [ - "startup_script", - "shutdown_script", - "kubernetes", - "envbox", - "envbuilder", - "external" - ], - "x-enum-varnames": [ - "WorkspaceAgentLogSourceStartupScript", - "WorkspaceAgentLogSourceShutdownScript", - "WorkspaceAgentLogSourceKubernetes", - "WorkspaceAgentLogSourceEnvbox", - "WorkspaceAgentLogSourceEnvbuilder", - "WorkspaceAgentLogSourceExternal" - ] + "type": "object", + "properties": { + "created_at": { + "type": "string", + "format": "date-time" + }, + "display_name": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "id": { + "type": "string", + "format": "uuid" + }, + "workspace_agent_id": { + "type": "string", + "format": "uuid" + } + } }, "codersdk.WorkspaceAgentMetadataDescription": { "type": "object", @@ -10898,6 +10900,36 @@ const docTemplate = `{ } } }, + "codersdk.WorkspaceAgentScript": { + "type": "object", + "properties": { + "cron": { + "type": "string" + }, + "log_path": { + "type": "string" + }, + "log_source_id": { + "type": "string", + "format": "uuid" + }, + "run_on_start": { + "type": "boolean" + }, + "run_on_stop": { + "type": "boolean" + }, + "script": { + "type": "string" + }, + "start_blocks_login": { + "type": "boolean" + }, + "timeout": { + "type": "integer" + } + } + }, "codersdk.WorkspaceAgentStartupScriptBehavior": { "type": "string", "enum": [ diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 843a09d51c464..1d881c3868a3e 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -5638,9 +5638,6 @@ }, "output": { "type": "string" - }, - "source": { - "$ref": "#/definitions/codersdk.WorkspaceAgentLogSource" } } }, @@ -5687,17 +5684,11 @@ "motd_file": { "type": "string" }, - "shutdown_script": { - "type": "string" - }, - "shutdown_script_timeout": { - "type": "integer" - }, - "startup_script": { - "type": "string" - }, - "startup_script_timeout": { - "type": "integer" + "scripts": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.WorkspaceAgentScript" + } }, "vscode_port_proxy_uri": { "type": "string" @@ -5707,6 +5698,9 @@ "agentsdk.PatchLogs": { "type": "object", "properties": { + "log_source_id": { + "type": "string" + }, "logs": { "type": "array", "items": { @@ -9698,9 +9692,11 @@ "lifecycle_state": { "$ref": "#/definitions/codersdk.WorkspaceAgentLifecycle" }, - "login_before_ready": { - "description": "Deprecated: Use StartupScriptBehavior instead.", - "type": "boolean" + "log_sources": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.WorkspaceAgentLogSource" + } }, "logs_length": { "type": "integer" @@ -9722,25 +9718,23 @@ "type": "string", "format": "uuid" }, - "shutdown_script": { - "type": "string" - }, - "shutdown_script_timeout_seconds": { - "type": "integer" + "scripts": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.WorkspaceAgentScript" + } }, "started_at": { "type": "string", "format": "date-time" }, - "startup_script": { - "type": "string" - }, "startup_script_behavior": { - "$ref": "#/definitions/codersdk.WorkspaceAgentStartupScriptBehavior" - }, - "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" + "description": "StartupScriptBehavior is a legacy field that is deprecated in favor\nof the `coder_script` resource. It's only referenced by old clients.\nDeprecated: Remove in the future!", + "allOf": [ + { + "$ref": "#/definitions/codersdk.WorkspaceAgentStartupScriptBehavior" + } + ] }, "status": { "$ref": "#/definitions/codersdk.WorkspaceAgentStatus" @@ -9860,27 +9854,35 @@ }, "output": { "type": "string" + }, + "source_id": { + "type": "string", + "format": "uuid" } } }, "codersdk.WorkspaceAgentLogSource": { - "type": "string", - "enum": [ - "startup_script", - "shutdown_script", - "kubernetes", - "envbox", - "envbuilder", - "external" - ], - "x-enum-varnames": [ - "WorkspaceAgentLogSourceStartupScript", - "WorkspaceAgentLogSourceShutdownScript", - "WorkspaceAgentLogSourceKubernetes", - "WorkspaceAgentLogSourceEnvbox", - "WorkspaceAgentLogSourceEnvbuilder", - "WorkspaceAgentLogSourceExternal" - ] + "type": "object", + "properties": { + "created_at": { + "type": "string", + "format": "date-time" + }, + "display_name": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "id": { + "type": "string", + "format": "uuid" + }, + "workspace_agent_id": { + "type": "string", + "format": "uuid" + } + } }, "codersdk.WorkspaceAgentMetadataDescription": { "type": "object", @@ -9902,6 +9904,36 @@ } } }, + "codersdk.WorkspaceAgentScript": { + "type": "object", + "properties": { + "cron": { + "type": "string" + }, + "log_path": { + "type": "string" + }, + "log_source_id": { + "type": "string", + "format": "uuid" + }, + "run_on_start": { + "type": "boolean" + }, + "run_on_stop": { + "type": "boolean" + }, + "script": { + "type": "string" + }, + "start_blocks_login": { + "type": "boolean" + }, + "timeout": { + "type": "integer" + } + } + }, "codersdk.WorkspaceAgentStartupScriptBehavior": { "type": "string", "enum": ["blocking", "non-blocking"], diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index a877a92c3cb36..9e0d875c55f7a 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -1593,6 +1593,13 @@ func (q *querier) GetWorkspaceAgentLifecycleStateByID(ctx context.Context, id uu return q.db.GetWorkspaceAgentLifecycleStateByID(ctx, id) } +func (q *querier) GetWorkspaceAgentLogSourcesByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]database.WorkspaceAgentLogSource, error) { + if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil { + return nil, err + } + return q.db.GetWorkspaceAgentLogSourcesByAgentIDs(ctx, ids) +} + func (q *querier) GetWorkspaceAgentLogsAfter(ctx context.Context, arg database.GetWorkspaceAgentLogsAfterParams) ([]database.WorkspaceAgentLog, error) { _, err := q.GetWorkspaceAgentByID(ctx, arg.AgentID) if err != nil { @@ -1615,6 +1622,13 @@ func (q *querier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAgentI return q.db.GetWorkspaceAgentMetadata(ctx, workspaceAgentID) } +func (q *querier) GetWorkspaceAgentScriptsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]database.WorkspaceAgentScript, error) { + if err := q.authorizeContext(ctx, rbac.ActionRead, rbac.ResourceSystem); err != nil { + return nil, err + } + return q.db.GetWorkspaceAgentScriptsByAgentIDs(ctx, ids) +} + func (q *querier) GetWorkspaceAgentStats(ctx context.Context, createdAfter time.Time) ([]database.GetWorkspaceAgentStatsRow, error) { return q.db.GetWorkspaceAgentStats(ctx, createdAfter) } @@ -2080,8 +2094,6 @@ func (q *querier) InsertWorkspace(ctx context.Context, arg database.InsertWorksp return insert(q.log, q.auth, obj, q.db.InsertWorkspace)(ctx, arg) } -// Provisionerd server functions - func (q *querier) InsertWorkspaceAgent(ctx context.Context, arg database.InsertWorkspaceAgentParams) (database.WorkspaceAgent, error) { if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil { return database.WorkspaceAgent{}, err @@ -2089,6 +2101,10 @@ func (q *querier) InsertWorkspaceAgent(ctx context.Context, arg database.InsertW return q.db.InsertWorkspaceAgent(ctx, arg) } +func (q *querier) InsertWorkspaceAgentLogSources(ctx context.Context, arg database.InsertWorkspaceAgentLogSourcesParams) ([]database.WorkspaceAgentLogSource, error) { + return q.db.InsertWorkspaceAgentLogSources(ctx, arg) +} + func (q *querier) InsertWorkspaceAgentLogs(ctx context.Context, arg database.InsertWorkspaceAgentLogsParams) ([]database.WorkspaceAgentLog, error) { return q.db.InsertWorkspaceAgentLogs(ctx, arg) } @@ -2103,6 +2119,13 @@ func (q *querier) InsertWorkspaceAgentMetadata(ctx context.Context, arg database return q.db.InsertWorkspaceAgentMetadata(ctx, arg) } +func (q *querier) InsertWorkspaceAgentScripts(ctx context.Context, arg database.InsertWorkspaceAgentScriptsParams) ([]database.WorkspaceAgentScript, error) { + if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceSystem); err != nil { + return []database.WorkspaceAgentScript{}, err + } + return q.db.InsertWorkspaceAgentScripts(ctx, arg) +} + func (q *querier) InsertWorkspaceAgentStat(ctx context.Context, arg database.InsertWorkspaceAgentStatParams) (database.WorkspaceAgentStat, error) { // TODO: This is a workspace agent operation. Should users be able to query this? // Not really sure what this is for. diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index fa4e7ac8d2100..eb9be63f87cac 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -1494,8 +1494,7 @@ func (s *MethodTestSuite) TestSystemFunctions() { })) s.Run("InsertWorkspaceAgent", s.Subtest(func(db database.Store, check *expects) { check.Args(database.InsertWorkspaceAgentParams{ - ID: uuid.New(), - StartupScriptBehavior: database.StartupScriptBehaviorNonBlocking, + ID: uuid.New(), }).Asserts(rbac.ResourceSystem, rbac.ActionCreate) })) s.Run("InsertWorkspaceApp", s.Subtest(func(db database.Store, check *expects) { diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index 15c8b867473c5..3800395b2db23 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -142,6 +142,8 @@ type data struct { workspaceAgents []database.WorkspaceAgent workspaceAgentMetadata []database.WorkspaceAgentMetadatum workspaceAgentLogs []database.WorkspaceAgentLog + workspaceAgentLogSources []database.WorkspaceAgentLogSource + workspaceAgentScripts []database.WorkspaceAgentScript workspaceApps []database.WorkspaceApp workspaceAppStatsLastInsertID int64 workspaceAppStats []database.WorkspaceAppStat @@ -3213,6 +3215,22 @@ func (q *FakeQuerier) GetWorkspaceAgentLifecycleStateByID(ctx context.Context, i }, nil } +func (q *FakeQuerier) GetWorkspaceAgentLogSourcesByAgentIDs(_ context.Context, ids []uuid.UUID) ([]database.WorkspaceAgentLogSource, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + logSources := make([]database.WorkspaceAgentLogSource, 0) + for _, logSource := range q.workspaceAgentLogSources { + for _, id := range ids { + if logSource.WorkspaceAgentID == id { + logSources = append(logSources, logSource) + break + } + } + } + return logSources, nil +} + func (q *FakeQuerier) GetWorkspaceAgentLogsAfter(_ context.Context, arg database.GetWorkspaceAgentLogsAfterParams) ([]database.WorkspaceAgentLog, error) { if err := validateDatabaseType(arg); err != nil { return nil, err @@ -3247,6 +3265,22 @@ func (q *FakeQuerier) GetWorkspaceAgentMetadata(_ context.Context, workspaceAgen return metadata, nil } +func (q *FakeQuerier) GetWorkspaceAgentScriptsByAgentIDs(_ context.Context, ids []uuid.UUID) ([]database.WorkspaceAgentScript, error) { + q.mutex.RLock() + defer q.mutex.RUnlock() + + scripts := make([]database.WorkspaceAgentScript, 0) + for _, script := range q.workspaceAgentScripts { + for _, id := range ids { + if script.WorkspaceAgentID == id { + scripts = append(scripts, script) + break + } + } + } + return scripts, nil +} + func (q *FakeQuerier) GetWorkspaceAgentStats(_ context.Context, createdAfter time.Time) ([]database.GetWorkspaceAgentStatsRow, error) { q.mutex.RLock() defer q.mutex.RUnlock() @@ -3468,9 +3502,6 @@ func (q *FakeQuerier) GetWorkspaceAppsByAgentID(_ context.Context, id uuid.UUID) apps = append(apps, app) } } - if len(apps) == 0 { - return nil, sql.ErrNoRows - } return apps, nil } @@ -4588,15 +4619,12 @@ func (q *FakeQuerier) InsertWorkspaceAgent(_ context.Context, arg database.Inser Architecture: arg.Architecture, OperatingSystem: arg.OperatingSystem, Directory: arg.Directory, - StartupScriptBehavior: arg.StartupScriptBehavior, - StartupScript: arg.StartupScript, InstanceMetadata: arg.InstanceMetadata, ResourceMetadata: arg.ResourceMetadata, ConnectionTimeoutSeconds: arg.ConnectionTimeoutSeconds, TroubleshootingURL: arg.TroubleshootingURL, MOTDFile: arg.MOTDFile, LifecycleState: database.WorkspaceAgentLifecycleStateCreated, - ShutdownScript: arg.ShutdownScript, DisplayApps: arg.DisplayApps, } @@ -4604,6 +4632,30 @@ func (q *FakeQuerier) InsertWorkspaceAgent(_ context.Context, arg database.Inser return agent, nil } +func (q *FakeQuerier) InsertWorkspaceAgentLogSources(_ context.Context, arg database.InsertWorkspaceAgentLogSourcesParams) ([]database.WorkspaceAgentLogSource, error) { + err := validateDatabaseType(arg) + if err != nil { + return nil, err + } + + q.mutex.Lock() + defer q.mutex.Unlock() + + logSources := make([]database.WorkspaceAgentLogSource, 0) + for index, source := range arg.ID { + logSource := database.WorkspaceAgentLogSource{ + ID: source, + WorkspaceAgentID: arg.WorkspaceAgentID, + CreatedAt: arg.CreatedAt, + DisplayName: arg.DisplayName[index], + Icon: arg.Icon[index], + } + logSources = append(logSources, logSource) + } + q.workspaceAgentLogSources = append(q.workspaceAgentLogSources, logSources...) + return logSources, nil +} + func (q *FakeQuerier) InsertWorkspaceAgentLogs(_ context.Context, arg database.InsertWorkspaceAgentLogsParams) ([]database.WorkspaceAgentLog, error) { if err := validateDatabaseType(arg); err != nil { return nil, err @@ -4621,12 +4673,12 @@ func (q *FakeQuerier) InsertWorkspaceAgentLogs(_ context.Context, arg database.I for index, output := range arg.Output { id++ logs = append(logs, database.WorkspaceAgentLog{ - ID: id, - AgentID: arg.AgentID, - CreatedAt: arg.CreatedAt[index], - Level: arg.Level[index], - Source: arg.Source[index], - Output: output, + ID: id, + AgentID: arg.AgentID, + CreatedAt: arg.CreatedAt, + Level: arg.Level[index], + LogSourceID: arg.LogSourceID, + Output: output, }) outputLength += int32(len(output)) } @@ -4667,6 +4719,35 @@ func (q *FakeQuerier) InsertWorkspaceAgentMetadata(_ context.Context, arg databa return nil } +func (q *FakeQuerier) InsertWorkspaceAgentScripts(_ context.Context, arg database.InsertWorkspaceAgentScriptsParams) ([]database.WorkspaceAgentScript, error) { + err := validateDatabaseType(arg) + if err != nil { + return nil, err + } + + q.mutex.Lock() + defer q.mutex.Unlock() + + scripts := make([]database.WorkspaceAgentScript, 0) + for index, source := range arg.LogSourceID { + script := database.WorkspaceAgentScript{ + LogSourceID: source, + WorkspaceAgentID: arg.WorkspaceAgentID, + LogPath: arg.LogPath[index], + Script: arg.Script[index], + Cron: arg.Cron[index], + StartBlocksLogin: arg.StartBlocksLogin[index], + RunOnStart: arg.RunOnStart[index], + RunOnStop: arg.RunOnStop[index], + TimeoutSeconds: arg.TimeoutSeconds[index], + CreatedAt: arg.CreatedAt, + } + scripts = append(scripts, script) + } + q.workspaceAgentScripts = append(q.workspaceAgentScripts, scripts...) + return scripts, nil +} + func (q *FakeQuerier) InsertWorkspaceAgentStat(_ context.Context, p database.InsertWorkspaceAgentStatParams) (database.WorkspaceAgentStat, error) { if err := validateDatabaseType(p); err != nil { return database.WorkspaceAgentStat{}, err diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index cb2b2187bd747..61cae7bd5a201 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -142,11 +142,7 @@ func WorkspaceAgent(t testing.TB, db database.Store, orig database.WorkspaceAgen Valid: takeFirst(orig.EnvironmentVariables.Valid, false), }, OperatingSystem: takeFirst(orig.OperatingSystem, "linux"), - StartupScript: sql.NullString{ - String: takeFirst(orig.StartupScript.String, ""), - Valid: takeFirst(orig.StartupScript.Valid, false), - }, - Directory: takeFirst(orig.Directory, ""), + Directory: takeFirst(orig.Directory, ""), InstanceMetadata: pqtype.NullRawMessage{ RawMessage: takeFirstSlice(orig.ResourceMetadata.RawMessage, []byte("{}")), Valid: takeFirst(orig.ResourceMetadata.Valid, false), @@ -155,11 +151,9 @@ func WorkspaceAgent(t testing.TB, db database.Store, orig database.WorkspaceAgen RawMessage: takeFirstSlice(orig.ResourceMetadata.RawMessage, []byte("{}")), Valid: takeFirst(orig.ResourceMetadata.Valid, false), }, - ConnectionTimeoutSeconds: takeFirst(orig.ConnectionTimeoutSeconds, 3600), - TroubleshootingURL: takeFirst(orig.TroubleshootingURL, "https://example.com"), - MOTDFile: takeFirst(orig.TroubleshootingURL, ""), - StartupScriptBehavior: takeFirst(orig.StartupScriptBehavior, "non-blocking"), - StartupScriptTimeoutSeconds: takeFirst(orig.StartupScriptTimeoutSeconds, 3600), + ConnectionTimeoutSeconds: takeFirst(orig.ConnectionTimeoutSeconds, 3600), + TroubleshootingURL: takeFirst(orig.TroubleshootingURL, "https://example.com"), + MOTDFile: takeFirst(orig.TroubleshootingURL, ""), }) require.NoError(t, err, "insert workspace agent") return workspace @@ -182,6 +176,18 @@ func Workspace(t testing.TB, db database.Store, orig database.Workspace) databas return workspace } +func WorkspaceAgentLogSource(t testing.TB, db database.Store, orig database.WorkspaceAgentLogSource) database.WorkspaceAgentLogSource { + sources, err := db.InsertWorkspaceAgentLogSources(genCtx, database.InsertWorkspaceAgentLogSourcesParams{ + WorkspaceAgentID: takeFirst(orig.WorkspaceAgentID, uuid.New()), + ID: []uuid.UUID{takeFirst(orig.ID, uuid.New())}, + CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()), + DisplayName: []string{takeFirst(orig.DisplayName, namesgenerator.GetRandomName(1))}, + Icon: []string{takeFirst(orig.Icon, namesgenerator.GetRandomName(1))}, + }) + require.NoError(t, err, "insert workspace agent log source") + return sources[0] +} + func WorkspaceBuild(t testing.TB, db database.Store, orig database.WorkspaceBuild) database.WorkspaceBuild { buildID := takeFirst(orig.ID, uuid.New()) var build database.WorkspaceBuild diff --git a/coderd/database/dbmetrics/dbmetrics.go b/coderd/database/dbmetrics/dbmetrics.go index 6cd65bda99faf..5972b6e5c4db1 100644 --- a/coderd/database/dbmetrics/dbmetrics.go +++ b/coderd/database/dbmetrics/dbmetrics.go @@ -865,6 +865,13 @@ func (m metricsStore) GetWorkspaceAgentLifecycleStateByID(ctx context.Context, i return r0, r1 } +func (m metricsStore) GetWorkspaceAgentLogSourcesByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]database.WorkspaceAgentLogSource, error) { + start := time.Now() + r0, r1 := m.s.GetWorkspaceAgentLogSourcesByAgentIDs(ctx, ids) + m.queryLatencies.WithLabelValues("GetWorkspaceAgentLogSourcesByAgentIDs").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) GetWorkspaceAgentLogsAfter(ctx context.Context, arg database.GetWorkspaceAgentLogsAfterParams) ([]database.WorkspaceAgentLog, error) { start := time.Now() r0, r1 := m.s.GetWorkspaceAgentLogsAfter(ctx, arg) @@ -879,6 +886,13 @@ func (m metricsStore) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAg return metadata, err } +func (m metricsStore) GetWorkspaceAgentScriptsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]database.WorkspaceAgentScript, error) { + start := time.Now() + r0, r1 := m.s.GetWorkspaceAgentScriptsByAgentIDs(ctx, ids) + m.queryLatencies.WithLabelValues("GetWorkspaceAgentScriptsByAgentIDs").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) GetWorkspaceAgentStats(ctx context.Context, createdAt time.Time) ([]database.GetWorkspaceAgentStatsRow, error) { start := time.Now() stats, err := m.s.GetWorkspaceAgentStats(ctx, createdAt) @@ -1292,6 +1306,13 @@ func (m metricsStore) InsertWorkspaceAgent(ctx context.Context, arg database.Ins return agent, err } +func (m metricsStore) InsertWorkspaceAgentLogSources(ctx context.Context, arg database.InsertWorkspaceAgentLogSourcesParams) ([]database.WorkspaceAgentLogSource, error) { + start := time.Now() + r0, r1 := m.s.InsertWorkspaceAgentLogSources(ctx, arg) + m.queryLatencies.WithLabelValues("InsertWorkspaceAgentLogSources").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) InsertWorkspaceAgentLogs(ctx context.Context, arg database.InsertWorkspaceAgentLogsParams) ([]database.WorkspaceAgentLog, error) { start := time.Now() r0, r1 := m.s.InsertWorkspaceAgentLogs(ctx, arg) @@ -1306,6 +1327,13 @@ func (m metricsStore) InsertWorkspaceAgentMetadata(ctx context.Context, arg data return err } +func (m metricsStore) InsertWorkspaceAgentScripts(ctx context.Context, arg database.InsertWorkspaceAgentScriptsParams) ([]database.WorkspaceAgentScript, error) { + start := time.Now() + r0, r1 := m.s.InsertWorkspaceAgentScripts(ctx, arg) + m.queryLatencies.WithLabelValues("InsertWorkspaceAgentScripts").Observe(time.Since(start).Seconds()) + return r0, r1 +} + func (m metricsStore) InsertWorkspaceAgentStat(ctx context.Context, arg database.InsertWorkspaceAgentStatParams) (database.WorkspaceAgentStat, error) { start := time.Now() stat, err := m.s.InsertWorkspaceAgentStat(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 6c4ece4b1438f..e2a515793f22d 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -1793,6 +1793,21 @@ func (mr *MockStoreMockRecorder) GetWorkspaceAgentLifecycleStateByID(arg0, arg1 return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceAgentLifecycleStateByID", reflect.TypeOf((*MockStore)(nil).GetWorkspaceAgentLifecycleStateByID), arg0, arg1) } +// GetWorkspaceAgentLogSourcesByAgentIDs mocks base method. +func (m *MockStore) GetWorkspaceAgentLogSourcesByAgentIDs(arg0 context.Context, arg1 []uuid.UUID) ([]database.WorkspaceAgentLogSource, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetWorkspaceAgentLogSourcesByAgentIDs", arg0, arg1) + ret0, _ := ret[0].([]database.WorkspaceAgentLogSource) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetWorkspaceAgentLogSourcesByAgentIDs indicates an expected call of GetWorkspaceAgentLogSourcesByAgentIDs. +func (mr *MockStoreMockRecorder) GetWorkspaceAgentLogSourcesByAgentIDs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceAgentLogSourcesByAgentIDs", reflect.TypeOf((*MockStore)(nil).GetWorkspaceAgentLogSourcesByAgentIDs), arg0, arg1) +} + // GetWorkspaceAgentLogsAfter mocks base method. func (m *MockStore) GetWorkspaceAgentLogsAfter(arg0 context.Context, arg1 database.GetWorkspaceAgentLogsAfterParams) ([]database.WorkspaceAgentLog, error) { m.ctrl.T.Helper() @@ -1823,6 +1838,21 @@ func (mr *MockStoreMockRecorder) GetWorkspaceAgentMetadata(arg0, arg1 interface{ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceAgentMetadata", reflect.TypeOf((*MockStore)(nil).GetWorkspaceAgentMetadata), arg0, arg1) } +// GetWorkspaceAgentScriptsByAgentIDs mocks base method. +func (m *MockStore) GetWorkspaceAgentScriptsByAgentIDs(arg0 context.Context, arg1 []uuid.UUID) ([]database.WorkspaceAgentScript, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetWorkspaceAgentScriptsByAgentIDs", arg0, arg1) + ret0, _ := ret[0].([]database.WorkspaceAgentScript) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetWorkspaceAgentScriptsByAgentIDs indicates an expected call of GetWorkspaceAgentScriptsByAgentIDs. +func (mr *MockStoreMockRecorder) GetWorkspaceAgentScriptsByAgentIDs(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceAgentScriptsByAgentIDs", reflect.TypeOf((*MockStore)(nil).GetWorkspaceAgentScriptsByAgentIDs), arg0, arg1) +} + // GetWorkspaceAgentStats mocks base method. func (m *MockStore) GetWorkspaceAgentStats(arg0 context.Context, arg1 time.Time) ([]database.GetWorkspaceAgentStatsRow, error) { m.ctrl.T.Helper() @@ -2715,6 +2745,21 @@ func (mr *MockStoreMockRecorder) InsertWorkspaceAgent(arg0, arg1 interface{}) *g return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertWorkspaceAgent", reflect.TypeOf((*MockStore)(nil).InsertWorkspaceAgent), arg0, arg1) } +// InsertWorkspaceAgentLogSources mocks base method. +func (m *MockStore) InsertWorkspaceAgentLogSources(arg0 context.Context, arg1 database.InsertWorkspaceAgentLogSourcesParams) ([]database.WorkspaceAgentLogSource, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InsertWorkspaceAgentLogSources", arg0, arg1) + ret0, _ := ret[0].([]database.WorkspaceAgentLogSource) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// InsertWorkspaceAgentLogSources indicates an expected call of InsertWorkspaceAgentLogSources. +func (mr *MockStoreMockRecorder) InsertWorkspaceAgentLogSources(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertWorkspaceAgentLogSources", reflect.TypeOf((*MockStore)(nil).InsertWorkspaceAgentLogSources), arg0, arg1) +} + // InsertWorkspaceAgentLogs mocks base method. func (m *MockStore) InsertWorkspaceAgentLogs(arg0 context.Context, arg1 database.InsertWorkspaceAgentLogsParams) ([]database.WorkspaceAgentLog, error) { m.ctrl.T.Helper() @@ -2744,6 +2789,21 @@ func (mr *MockStoreMockRecorder) InsertWorkspaceAgentMetadata(arg0, arg1 interfa return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertWorkspaceAgentMetadata", reflect.TypeOf((*MockStore)(nil).InsertWorkspaceAgentMetadata), arg0, arg1) } +// InsertWorkspaceAgentScripts mocks base method. +func (m *MockStore) InsertWorkspaceAgentScripts(arg0 context.Context, arg1 database.InsertWorkspaceAgentScriptsParams) ([]database.WorkspaceAgentScript, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InsertWorkspaceAgentScripts", arg0, arg1) + ret0, _ := ret[0].([]database.WorkspaceAgentScript) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// InsertWorkspaceAgentScripts indicates an expected call of InsertWorkspaceAgentScripts. +func (mr *MockStoreMockRecorder) InsertWorkspaceAgentScripts(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertWorkspaceAgentScripts", reflect.TypeOf((*MockStore)(nil).InsertWorkspaceAgentScripts), arg0, arg1) +} + // InsertWorkspaceAgentStat mocks base method. func (m *MockStore) InsertWorkspaceAgentStat(arg0 context.Context, arg1 database.InsertWorkspaceAgentStatParams) (database.WorkspaceAgentStat, error) { m.ctrl.T.Helper() diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 644349bb58cae..fd8529d78af46 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -144,15 +144,6 @@ CREATE TYPE workspace_agent_lifecycle_state AS ENUM ( 'off' ); -CREATE TYPE workspace_agent_log_source AS ENUM ( - 'startup_script', - 'shutdown_script', - 'kubernetes_logs', - 'envbox', - 'envbuilder', - 'external' -); - CREATE TYPE workspace_agent_subsystem AS ENUM ( 'envbuilder', 'envbox', @@ -789,13 +780,21 @@ COMMENT ON COLUMN user_links.oauth_access_token_key_id IS 'The ID of the key use COMMENT ON COLUMN user_links.oauth_refresh_token_key_id IS 'The ID of the key used to encrypt the OAuth refresh token. If this is NULL, the refresh token is not encrypted'; -CREATE TABLE workspace_agent_logs ( +CREATE TABLE workspace_agent_log_sources ( + workspace_agent_id uuid NOT NULL, + id uuid NOT NULL, + created_at timestamp with time zone NOT NULL, + display_name character varying(127) NOT NULL, + icon text NOT NULL +); + +CREATE UNLOGGED TABLE workspace_agent_logs ( agent_id uuid NOT NULL, created_at timestamp with time zone NOT NULL, output character varying(1024) NOT NULL, id bigint NOT NULL, level log_level DEFAULT 'info'::log_level NOT NULL, - source workspace_agent_log_source DEFAULT 'startup_script'::workspace_agent_log_source NOT NULL + log_source_id uuid DEFAULT '00000000-0000-0000-0000-000000000000'::uuid NOT NULL ); CREATE UNLOGGED TABLE workspace_agent_metadata ( @@ -810,6 +809,19 @@ CREATE UNLOGGED TABLE workspace_agent_metadata ( collected_at timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL ); +CREATE TABLE workspace_agent_scripts ( + workspace_agent_id uuid NOT NULL, + log_source_id uuid NOT NULL, + log_path text NOT NULL, + created_at timestamp with time zone NOT NULL, + script text NOT NULL, + cron text NOT NULL, + start_blocks_login boolean NOT NULL, + run_on_start boolean NOT NULL, + run_on_stop boolean NOT NULL, + timeout_seconds integer NOT NULL +); + CREATE SEQUENCE workspace_agent_startup_logs_id_seq START WITH 1 INCREMENT BY 1 @@ -853,7 +865,6 @@ CREATE TABLE workspace_agents ( architecture character varying(64) NOT NULL, environment_variables jsonb, operating_system character varying(64) NOT NULL, - startup_script character varying(65534), instance_metadata jsonb, resource_metadata jsonb, directory character varying(4096) DEFAULT ''::character varying NOT NULL, @@ -863,13 +874,9 @@ CREATE TABLE workspace_agents ( troubleshooting_url 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, - startup_script_timeout_seconds integer DEFAULT 0 NOT NULL, expanded_directory character varying(4096) DEFAULT ''::character varying NOT NULL, - shutdown_script character varying(65534), - shutdown_script_timeout_seconds integer DEFAULT 0 NOT NULL, logs_length integer DEFAULT 0 NOT NULL, logs_overflowed boolean DEFAULT false NOT NULL, - startup_script_behavior startup_script_behavior DEFAULT 'non-blocking'::startup_script_behavior NOT NULL, started_at timestamp with time zone, ready_at timestamp with time zone, subsystems workspace_agent_subsystem[] DEFAULT '{}'::workspace_agent_subsystem[], @@ -888,20 +895,12 @@ COMMENT ON COLUMN workspace_agents.motd_file IS 'Path to file inside workspace c COMMENT ON COLUMN workspace_agents.lifecycle_state IS 'The current lifecycle state reported by the workspace agent.'; -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.'; - COMMENT ON COLUMN workspace_agents.expanded_directory IS 'The resolved path of a user-specified directory. e.g. ~/coder -> /home/coder/coder'; -COMMENT ON COLUMN workspace_agents.shutdown_script IS 'Script that is executed before the agent is stopped.'; - -COMMENT ON COLUMN workspace_agents.shutdown_script_timeout_seconds IS 'The number of seconds to wait for the shutdown script to complete. If the script does not complete within this time, the agent lifecycle will be marked as shutdown_timeout.'; - COMMENT ON COLUMN workspace_agents.logs_length IS 'Total length of startup logs'; COMMENT ON COLUMN workspace_agents.logs_overflowed IS 'Whether the startup logs overflowed in length'; -COMMENT ON COLUMN workspace_agents.startup_script_behavior IS 'When startup script behavior is non-blocking, the workspace will be ready and accessible upon agent connection, when it is blocking, workspace will wait for the startup script to complete before becoming ready and accessible.'; - COMMENT ON COLUMN workspace_agents.started_at IS 'The time the agent entered the starting lifecycle state'; COMMENT ON COLUMN workspace_agents.ready_at IS 'The time the agent entered the ready or start_error lifecycle state'; @@ -1224,6 +1223,9 @@ ALTER TABLE ONLY user_links ALTER TABLE ONLY users ADD CONSTRAINT users_pkey PRIMARY KEY (id); +ALTER TABLE ONLY workspace_agent_log_sources + ADD CONSTRAINT workspace_agent_log_sources_pkey PRIMARY KEY (workspace_agent_id, id); + ALTER TABLE ONLY workspace_agent_metadata ADD CONSTRAINT workspace_agent_metadata_pkey PRIMARY KEY (workspace_agent_id, key); @@ -1418,9 +1420,15 @@ ALTER TABLE ONLY user_links ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; +ALTER TABLE ONLY workspace_agent_log_sources + ADD CONSTRAINT workspace_agent_log_sources_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; + ALTER TABLE ONLY workspace_agent_metadata ADD CONSTRAINT workspace_agent_metadata_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; +ALTER TABLE ONLY workspace_agent_scripts + ADD CONSTRAINT workspace_agent_scripts_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; + ALTER TABLE ONLY workspace_agent_logs ADD CONSTRAINT workspace_agent_startup_logs_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; diff --git a/coderd/database/foreign_key_constraint.go b/coderd/database/foreign_key_constraint.go index 5f3b9aa5c32b3..9bab83c9d04e2 100644 --- a/coderd/database/foreign_key_constraint.go +++ b/coderd/database/foreign_key_constraint.go @@ -31,7 +31,9 @@ const ( ForeignKeyUserLinksOauthAccessTokenKeyID ForeignKeyConstraint = "user_links_oauth_access_token_key_id_fkey" // ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_oauth_access_token_key_id_fkey FOREIGN KEY (oauth_access_token_key_id) REFERENCES dbcrypt_keys(active_key_digest); ForeignKeyUserLinksOauthRefreshTokenKeyID ForeignKeyConstraint = "user_links_oauth_refresh_token_key_id_fkey" // ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_oauth_refresh_token_key_id_fkey FOREIGN KEY (oauth_refresh_token_key_id) REFERENCES dbcrypt_keys(active_key_digest); ForeignKeyUserLinksUserID ForeignKeyConstraint = "user_links_user_id_fkey" // ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; + ForeignKeyWorkspaceAgentLogSourcesWorkspaceAgentID ForeignKeyConstraint = "workspace_agent_log_sources_workspace_agent_id_fkey" // ALTER TABLE ONLY workspace_agent_log_sources ADD CONSTRAINT workspace_agent_log_sources_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; ForeignKeyWorkspaceAgentMetadataWorkspaceAgentID ForeignKeyConstraint = "workspace_agent_metadata_workspace_agent_id_fkey" // ALTER TABLE ONLY workspace_agent_metadata ADD CONSTRAINT workspace_agent_metadata_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; + ForeignKeyWorkspaceAgentScriptsWorkspaceAgentID ForeignKeyConstraint = "workspace_agent_scripts_workspace_agent_id_fkey" // ALTER TABLE ONLY workspace_agent_scripts ADD CONSTRAINT workspace_agent_scripts_workspace_agent_id_fkey FOREIGN KEY (workspace_agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; ForeignKeyWorkspaceAgentStartupLogsAgentID ForeignKeyConstraint = "workspace_agent_startup_logs_agent_id_fkey" // ALTER TABLE ONLY workspace_agent_logs ADD CONSTRAINT workspace_agent_startup_logs_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id) ON DELETE CASCADE; ForeignKeyWorkspaceAgentsResourceID ForeignKeyConstraint = "workspace_agents_resource_id_fkey" // ALTER TABLE ONLY workspace_agents ADD CONSTRAINT workspace_agents_resource_id_fkey FOREIGN KEY (resource_id) REFERENCES workspace_resources(id) ON DELETE CASCADE; ForeignKeyWorkspaceAppStatsAgentID ForeignKeyConstraint = "workspace_app_stats_agent_id_fkey" // ALTER TABLE ONLY workspace_app_stats ADD CONSTRAINT workspace_app_stats_agent_id_fkey FOREIGN KEY (agent_id) REFERENCES workspace_agents(id); diff --git a/coderd/database/migrations/000157_workspace_agent_script.down.sql b/coderd/database/migrations/000157_workspace_agent_script.down.sql new file mode 100644 index 0000000000000..013c1097dda3e --- /dev/null +++ b/coderd/database/migrations/000157_workspace_agent_script.down.sql @@ -0,0 +1,23 @@ +BEGIN; + +ALTER TABLE workspace_agent_logs SET LOGGED; + +-- Revert the workspace_agents table to its former state +ALTER TABLE workspace_agents ADD COLUMN startup_script text; +ALTER TABLE workspace_agents ADD COLUMN startup_script_behavior text; +ALTER TABLE workspace_agents ADD COLUMN shutdown_script_timeout_seconds integer; +ALTER TABLE workspace_agents ADD COLUMN shutdown_script text; +ALTER TABLE workspace_agents ADD COLUMN startup_script_timeout_seconds integer; + +-- Reinstate the dropped type +CREATE TYPE workspace_agent_log_source AS ENUM ('startup_script', 'shutdown_script', 'kubernetes_logs', 'envbox', 'envbuilder', 'external'); + +-- Add old source column back with enum type and drop log_source_id +ALTER TABLE workspace_agent_logs ADD COLUMN source workspace_agent_log_source; +ALTER TABLE workspace_agent_logs DROP COLUMN log_source_id; + +-- Drop the newly created tables +DROP TABLE workspace_agent_scripts; +DROP TABLE workspace_agent_log_sources; + +COMMIT; diff --git a/coderd/database/migrations/000157_workspace_agent_script.up.sql b/coderd/database/migrations/000157_workspace_agent_script.up.sql new file mode 100644 index 0000000000000..c073318c5b804 --- /dev/null +++ b/coderd/database/migrations/000157_workspace_agent_script.up.sql @@ -0,0 +1,36 @@ +BEGIN; +CREATE TABLE workspace_agent_log_sources ( + workspace_agent_id uuid NOT NULL REFERENCES workspace_agents(id) ON DELETE CASCADE, + id uuid NOT NULL, + created_at timestamptz NOT NULL, + display_name varchar(127) NOT NULL, + icon text NOT NULL, + PRIMARY KEY (workspace_agent_id, id) +); + +CREATE TABLE workspace_agent_scripts ( + workspace_agent_id uuid NOT NULL REFERENCES workspace_agents(id) ON DELETE CASCADE, + log_source_id uuid NOT NULL, + log_path text NOT NULL, + created_at timestamptz NOT NULL, + script text NOT NULL, + cron text NOT NULL, + start_blocks_login boolean NOT NULL, + run_on_start boolean NOT NULL, + run_on_stop boolean NOT NULL, + timeout_seconds integer NOT NULL +); + +ALTER TABLE workspace_agent_logs ADD COLUMN log_source_id uuid NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'::uuid; +ALTER TABLE workspace_agent_logs DROP COLUMN source; +DROP TYPE workspace_agent_log_source; + +ALTER TABLE workspace_agents DROP COLUMN startup_script_timeout_seconds; +ALTER TABLE workspace_agents DROP COLUMN shutdown_script; +ALTER TABLE workspace_agents DROP COLUMN shutdown_script_timeout_seconds; +ALTER TABLE workspace_agents DROP COLUMN startup_script_behavior; +ALTER TABLE workspace_agents DROP COLUMN startup_script; + +-- Set the table to unlogged to speed up the inserts +ALTER TABLE workspace_agent_logs SET UNLOGGED; +COMMIT; diff --git a/coderd/database/migrations/testdata/fixtures/000157_workspace_agent_script.up.sql b/coderd/database/migrations/testdata/fixtures/000157_workspace_agent_script.up.sql new file mode 100644 index 0000000000000..7523cd4714285 --- /dev/null +++ b/coderd/database/migrations/testdata/fixtures/000157_workspace_agent_script.up.sql @@ -0,0 +1,39 @@ +-- INSERT INTO workspace_agents VALUES ('45e89705-e09d-4850-bcec-f9a937f5d78d', '2022-11-02 13:03:45.046432+02', '2022-11-02 13:03:45.046432+02', 'main', NULL, NULL, NULL, '0ff953c0-92a6-4fe6-a415-eb0139a36ad1', 'ffc107ef-7ded-4d80-b1a9-0c1d0bf7ccbf', NULL, 'amd64', '{"GIT_AUTHOR_NAME": "default", "GIT_AUTHOR_EMAIL": "", "GIT_COMMITTER_NAME": "default", "GIT_COMMITTER_EMAIL": ""}', 'linux', 'code-server --auth none', NULL, NULL, '', '') ON CONFLICT DO NOTHING; + +INSERT INTO workspace_agent_log_sources ( + workspace_agent_id, + id, + created_at, + display_name, + icon +) VALUES ( + '45e89705-e09d-4850-bcec-f9a937f5d78d', + '0ff953c0-92a6-4fe6-a415-eb0139a36ad1', + '2022-11-02 13:03:45.046432+02', + 'main', + 'something.png' +) ON CONFLICT DO NOTHING; + +INSERT INTO workspace_agent_scripts ( + workspace_agent_id, + created_at, + log_source_id, + log_path, + script, + cron, + start_blocks_login, + run_on_start, + run_on_stop, + timeout_seconds +) VALUES ( + '45e89705-e09d-4850-bcec-f9a937f5d78d', + '2022-11-02 13:03:45.046432+02', + '0ff953c0-92a6-4fe6-a415-eb0139a36ad1', + '/tmp', + 'echo "hello world"', + '@daily', + TRUE, + TRUE, + TRUE, + 60 +) ON CONFLICT DO NOTHING; diff --git a/coderd/database/models.go b/coderd/database/models.go index aaa141bca68ff..41615f8af2a09 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1298,76 +1298,6 @@ func AllWorkspaceAgentLifecycleStateValues() []WorkspaceAgentLifecycleState { } } -type WorkspaceAgentLogSource string - -const ( - WorkspaceAgentLogSourceStartupScript WorkspaceAgentLogSource = "startup_script" - WorkspaceAgentLogSourceShutdownScript WorkspaceAgentLogSource = "shutdown_script" - WorkspaceAgentLogSourceKubernetesLogs WorkspaceAgentLogSource = "kubernetes_logs" - WorkspaceAgentLogSourceEnvbox WorkspaceAgentLogSource = "envbox" - WorkspaceAgentLogSourceEnvbuilder WorkspaceAgentLogSource = "envbuilder" - WorkspaceAgentLogSourceExternal WorkspaceAgentLogSource = "external" -) - -func (e *WorkspaceAgentLogSource) Scan(src interface{}) error { - switch s := src.(type) { - case []byte: - *e = WorkspaceAgentLogSource(s) - case string: - *e = WorkspaceAgentLogSource(s) - default: - return fmt.Errorf("unsupported scan type for WorkspaceAgentLogSource: %T", src) - } - return nil -} - -type NullWorkspaceAgentLogSource struct { - WorkspaceAgentLogSource WorkspaceAgentLogSource `json:"workspace_agent_log_source"` - Valid bool `json:"valid"` // Valid is true if WorkspaceAgentLogSource is not NULL -} - -// Scan implements the Scanner interface. -func (ns *NullWorkspaceAgentLogSource) Scan(value interface{}) error { - if value == nil { - ns.WorkspaceAgentLogSource, ns.Valid = "", false - return nil - } - ns.Valid = true - return ns.WorkspaceAgentLogSource.Scan(value) -} - -// Value implements the driver Valuer interface. -func (ns NullWorkspaceAgentLogSource) Value() (driver.Value, error) { - if !ns.Valid { - return nil, nil - } - return string(ns.WorkspaceAgentLogSource), nil -} - -func (e WorkspaceAgentLogSource) Valid() bool { - switch e { - case WorkspaceAgentLogSourceStartupScript, - WorkspaceAgentLogSourceShutdownScript, - WorkspaceAgentLogSourceKubernetesLogs, - WorkspaceAgentLogSourceEnvbox, - WorkspaceAgentLogSourceEnvbuilder, - WorkspaceAgentLogSourceExternal: - return true - } - return false -} - -func AllWorkspaceAgentLogSourceValues() []WorkspaceAgentLogSource { - return []WorkspaceAgentLogSource{ - WorkspaceAgentLogSourceStartupScript, - WorkspaceAgentLogSourceShutdownScript, - WorkspaceAgentLogSourceKubernetesLogs, - WorkspaceAgentLogSourceEnvbox, - WorkspaceAgentLogSourceEnvbuilder, - WorkspaceAgentLogSourceExternal, - } -} - type WorkspaceAgentSubsystem string const ( @@ -2018,7 +1948,6 @@ type WorkspaceAgent struct { Architecture string `db:"architecture" json:"architecture"` EnvironmentVariables pqtype.NullRawMessage `db:"environment_variables" json:"environment_variables"` OperatingSystem string `db:"operating_system" json:"operating_system"` - StartupScript sql.NullString `db:"startup_script" json:"startup_script"` InstanceMetadata pqtype.NullRawMessage `db:"instance_metadata" json:"instance_metadata"` ResourceMetadata pqtype.NullRawMessage `db:"resource_metadata" json:"resource_metadata"` Directory string `db:"directory" json:"directory"` @@ -2033,20 +1962,12 @@ type WorkspaceAgent struct { MOTDFile string `db:"motd_file" json:"motd_file"` // The current lifecycle state reported by the workspace agent. LifecycleState WorkspaceAgentLifecycleState `db:"lifecycle_state" json:"lifecycle_state"` - // The number of seconds to wait for the startup script to complete. If the script does not complete within this time, the agent lifecycle will be marked as start_timeout. - StartupScriptTimeoutSeconds int32 `db:"startup_script_timeout_seconds" json:"startup_script_timeout_seconds"` // The resolved path of a user-specified directory. e.g. ~/coder -> /home/coder/coder ExpandedDirectory string `db:"expanded_directory" json:"expanded_directory"` - // Script that is executed before the agent is stopped. - ShutdownScript sql.NullString `db:"shutdown_script" json:"shutdown_script"` - // The number of seconds to wait for the shutdown script to complete. If the script does not complete within this time, the agent lifecycle will be marked as shutdown_timeout. - ShutdownScriptTimeoutSeconds int32 `db:"shutdown_script_timeout_seconds" json:"shutdown_script_timeout_seconds"` // Total length of startup logs LogsLength int32 `db:"logs_length" json:"logs_length"` // Whether the startup logs overflowed in length LogsOverflowed bool `db:"logs_overflowed" json:"logs_overflowed"` - // When startup script behavior is non-blocking, the workspace will be ready and accessible upon agent connection, when it is blocking, workspace will wait for the startup script to complete before becoming ready and accessible. - StartupScriptBehavior StartupScriptBehavior `db:"startup_script_behavior" json:"startup_script_behavior"` // The time the agent entered the starting lifecycle state StartedAt sql.NullTime `db:"started_at" json:"started_at"` // The time the agent entered the ready or start_error lifecycle state @@ -2056,12 +1977,20 @@ type WorkspaceAgent struct { } type WorkspaceAgentLog struct { - AgentID uuid.UUID `db:"agent_id" json:"agent_id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - Output string `db:"output" json:"output"` - ID int64 `db:"id" json:"id"` - Level LogLevel `db:"level" json:"level"` - Source WorkspaceAgentLogSource `db:"source" json:"source"` + AgentID uuid.UUID `db:"agent_id" json:"agent_id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + Output string `db:"output" json:"output"` + ID int64 `db:"id" json:"id"` + Level LogLevel `db:"level" json:"level"` + LogSourceID uuid.UUID `db:"log_source_id" json:"log_source_id"` +} + +type WorkspaceAgentLogSource struct { + WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + DisplayName string `db:"display_name" json:"display_name"` + Icon string `db:"icon" json:"icon"` } type WorkspaceAgentMetadatum struct { @@ -2076,6 +2005,19 @@ type WorkspaceAgentMetadatum struct { CollectedAt time.Time `db:"collected_at" json:"collected_at"` } +type WorkspaceAgentScript struct { + WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` + LogSourceID uuid.UUID `db:"log_source_id" json:"log_source_id"` + LogPath string `db:"log_path" json:"log_path"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + Script string `db:"script" json:"script"` + Cron string `db:"cron" json:"cron"` + StartBlocksLogin bool `db:"start_blocks_login" json:"start_blocks_login"` + RunOnStart bool `db:"run_on_start" json:"run_on_start"` + RunOnStop bool `db:"run_on_stop" json:"run_on_stop"` + TimeoutSeconds int32 `db:"timeout_seconds" json:"timeout_seconds"` +} + type WorkspaceAgentStat struct { ID uuid.UUID `db:"id" json:"id"` CreatedAt time.Time `db:"created_at" json:"created_at"` diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 2298819af54d6..2a9ac3149385d 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -174,8 +174,10 @@ type sqlcQuerier interface { GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (WorkspaceAgent, error) GetWorkspaceAgentByInstanceID(ctx context.Context, authInstanceID string) (WorkspaceAgent, error) GetWorkspaceAgentLifecycleStateByID(ctx context.Context, id uuid.UUID) (GetWorkspaceAgentLifecycleStateByIDRow, error) + GetWorkspaceAgentLogSourcesByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgentLogSource, error) GetWorkspaceAgentLogsAfter(ctx context.Context, arg GetWorkspaceAgentLogsAfterParams) ([]WorkspaceAgentLog, error) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAgentID uuid.UUID) ([]WorkspaceAgentMetadatum, error) + GetWorkspaceAgentScriptsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgentScript, error) GetWorkspaceAgentStats(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentStatsRow, error) GetWorkspaceAgentStatsAndLabels(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentStatsAndLabelsRow, error) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgent, error) @@ -250,8 +252,10 @@ type sqlcQuerier interface { InsertUserLink(ctx context.Context, arg InsertUserLinkParams) (UserLink, error) InsertWorkspace(ctx context.Context, arg InsertWorkspaceParams) (Workspace, error) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspaceAgentParams) (WorkspaceAgent, error) + InsertWorkspaceAgentLogSources(ctx context.Context, arg InsertWorkspaceAgentLogSourcesParams) ([]WorkspaceAgentLogSource, error) InsertWorkspaceAgentLogs(ctx context.Context, arg InsertWorkspaceAgentLogsParams) ([]WorkspaceAgentLog, error) InsertWorkspaceAgentMetadata(ctx context.Context, arg InsertWorkspaceAgentMetadataParams) error + InsertWorkspaceAgentScripts(ctx context.Context, arg InsertWorkspaceAgentScriptsParams) ([]WorkspaceAgentScript, error) InsertWorkspaceAgentStat(ctx context.Context, arg InsertWorkspaceAgentStatParams) (WorkspaceAgentStat, error) InsertWorkspaceAgentStats(ctx context.Context, arg InsertWorkspaceAgentStatsParams) error InsertWorkspaceApp(ctx context.Context, arg InsertWorkspaceAppParams) (WorkspaceApp, error) diff --git a/coderd/database/querier_test.go b/coderd/database/querier_test.go index 9184117aa2896..97e5068e3689c 100644 --- a/coderd/database/querier_test.go +++ b/coderd/database/querier_test.go @@ -112,12 +112,15 @@ func TestInsertWorkspaceAgentLogs(t *testing.T) { agent := dbgen.WorkspaceAgent(t, db, database.WorkspaceAgent{ ResourceID: resource.ID, }) + source := dbgen.WorkspaceAgentLogSource(t, db, database.WorkspaceAgentLogSource{ + WorkspaceAgentID: agent.ID, + }) logs, err := db.InsertWorkspaceAgentLogs(ctx, database.InsertWorkspaceAgentLogsParams{ - AgentID: agent.ID, - CreatedAt: []time.Time{dbtime.Now()}, - Output: []string{"first"}, - Level: []database.LogLevel{database.LogLevelInfo}, - Source: []database.WorkspaceAgentLogSource{database.WorkspaceAgentLogSourceExternal}, + AgentID: agent.ID, + CreatedAt: dbtime.Now(), + Output: []string{"first"}, + Level: []database.LogLevel{database.LogLevelInfo}, + LogSourceID: source.ID, // 1 MB is the max OutputLength: 1 << 20, }) @@ -126,10 +129,10 @@ func TestInsertWorkspaceAgentLogs(t *testing.T) { _, err = db.InsertWorkspaceAgentLogs(ctx, database.InsertWorkspaceAgentLogsParams{ AgentID: agent.ID, - CreatedAt: []time.Time{dbtime.Now()}, + CreatedAt: dbtime.Now(), Output: []string{"second"}, Level: []database.LogLevel{database.LogLevelInfo}, - Source: []database.WorkspaceAgentLogSource{database.WorkspaceAgentLogSourceExternal}, + LogSourceID: source.ID, OutputLength: 1, }) require.True(t, database.IsWorkspaceAgentLogsLimitError(err)) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 02ac5b735cc51..0ee232273737c 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -6687,7 +6687,7 @@ func (q *sqlQuerier) DeleteOldWorkspaceAgentLogs(ctx context.Context) error { const getWorkspaceAgentAndOwnerByAuthToken = `-- name: GetWorkspaceAgentAndOwnerByAuthToken :one SELECT - workspace_agents.id, workspace_agents.created_at, workspace_agents.updated_at, workspace_agents.name, workspace_agents.first_connected_at, workspace_agents.last_connected_at, workspace_agents.disconnected_at, workspace_agents.resource_id, workspace_agents.auth_token, workspace_agents.auth_instance_id, workspace_agents.architecture, workspace_agents.environment_variables, workspace_agents.operating_system, workspace_agents.startup_script, workspace_agents.instance_metadata, workspace_agents.resource_metadata, workspace_agents.directory, workspace_agents.version, workspace_agents.last_connected_replica_id, workspace_agents.connection_timeout_seconds, workspace_agents.troubleshooting_url, workspace_agents.motd_file, workspace_agents.lifecycle_state, workspace_agents.startup_script_timeout_seconds, workspace_agents.expanded_directory, workspace_agents.shutdown_script, workspace_agents.shutdown_script_timeout_seconds, workspace_agents.logs_length, workspace_agents.logs_overflowed, workspace_agents.startup_script_behavior, workspace_agents.started_at, workspace_agents.ready_at, workspace_agents.subsystems, workspace_agents.display_apps, + workspace_agents.id, workspace_agents.created_at, workspace_agents.updated_at, workspace_agents.name, workspace_agents.first_connected_at, workspace_agents.last_connected_at, workspace_agents.disconnected_at, workspace_agents.resource_id, workspace_agents.auth_token, workspace_agents.auth_instance_id, workspace_agents.architecture, workspace_agents.environment_variables, workspace_agents.operating_system, workspace_agents.instance_metadata, workspace_agents.resource_metadata, workspace_agents.directory, workspace_agents.version, workspace_agents.last_connected_replica_id, workspace_agents.connection_timeout_seconds, workspace_agents.troubleshooting_url, workspace_agents.motd_file, workspace_agents.lifecycle_state, workspace_agents.expanded_directory, workspace_agents.logs_length, workspace_agents.logs_overflowed, workspace_agents.started_at, workspace_agents.ready_at, workspace_agents.subsystems, workspace_agents.display_apps, workspaces.id AS workspace_id, users.id AS owner_id, users.username AS owner_name, @@ -6766,7 +6766,6 @@ func (q *sqlQuerier) GetWorkspaceAgentAndOwnerByAuthToken(ctx context.Context, a &i.WorkspaceAgent.Architecture, &i.WorkspaceAgent.EnvironmentVariables, &i.WorkspaceAgent.OperatingSystem, - &i.WorkspaceAgent.StartupScript, &i.WorkspaceAgent.InstanceMetadata, &i.WorkspaceAgent.ResourceMetadata, &i.WorkspaceAgent.Directory, @@ -6776,13 +6775,9 @@ func (q *sqlQuerier) GetWorkspaceAgentAndOwnerByAuthToken(ctx context.Context, a &i.WorkspaceAgent.TroubleshootingURL, &i.WorkspaceAgent.MOTDFile, &i.WorkspaceAgent.LifecycleState, - &i.WorkspaceAgent.StartupScriptTimeoutSeconds, &i.WorkspaceAgent.ExpandedDirectory, - &i.WorkspaceAgent.ShutdownScript, - &i.WorkspaceAgent.ShutdownScriptTimeoutSeconds, &i.WorkspaceAgent.LogsLength, &i.WorkspaceAgent.LogsOverflowed, - &i.WorkspaceAgent.StartupScriptBehavior, &i.WorkspaceAgent.StartedAt, &i.WorkspaceAgent.ReadyAt, pq.Array(&i.WorkspaceAgent.Subsystems), @@ -6799,7 +6794,7 @@ func (q *sqlQuerier) GetWorkspaceAgentAndOwnerByAuthToken(ctx context.Context, a const getWorkspaceAgentByID = `-- name: GetWorkspaceAgentByID :one SELECT - id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, logs_length, logs_overflowed, startup_script_behavior, started_at, ready_at, subsystems, display_apps + 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, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps FROM workspace_agents WHERE @@ -6823,7 +6818,6 @@ func (q *sqlQuerier) GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (W &i.Architecture, &i.EnvironmentVariables, &i.OperatingSystem, - &i.StartupScript, &i.InstanceMetadata, &i.ResourceMetadata, &i.Directory, @@ -6833,13 +6827,9 @@ func (q *sqlQuerier) GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (W &i.TroubleshootingURL, &i.MOTDFile, &i.LifecycleState, - &i.StartupScriptTimeoutSeconds, &i.ExpandedDirectory, - &i.ShutdownScript, - &i.ShutdownScriptTimeoutSeconds, &i.LogsLength, &i.LogsOverflowed, - &i.StartupScriptBehavior, &i.StartedAt, &i.ReadyAt, pq.Array(&i.Subsystems), @@ -6850,7 +6840,7 @@ func (q *sqlQuerier) GetWorkspaceAgentByID(ctx context.Context, id uuid.UUID) (W const getWorkspaceAgentByInstanceID = `-- name: GetWorkspaceAgentByInstanceID :one SELECT - id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, logs_length, logs_overflowed, startup_script_behavior, started_at, ready_at, subsystems, display_apps + 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, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps FROM workspace_agents WHERE @@ -6876,7 +6866,6 @@ func (q *sqlQuerier) GetWorkspaceAgentByInstanceID(ctx context.Context, authInst &i.Architecture, &i.EnvironmentVariables, &i.OperatingSystem, - &i.StartupScript, &i.InstanceMetadata, &i.ResourceMetadata, &i.Directory, @@ -6886,13 +6875,9 @@ func (q *sqlQuerier) GetWorkspaceAgentByInstanceID(ctx context.Context, authInst &i.TroubleshootingURL, &i.MOTDFile, &i.LifecycleState, - &i.StartupScriptTimeoutSeconds, &i.ExpandedDirectory, - &i.ShutdownScript, - &i.ShutdownScriptTimeoutSeconds, &i.LogsLength, &i.LogsOverflowed, - &i.StartupScriptBehavior, &i.StartedAt, &i.ReadyAt, pq.Array(&i.Subsystems), @@ -6925,9 +6910,42 @@ func (q *sqlQuerier) GetWorkspaceAgentLifecycleStateByID(ctx context.Context, id return i, err } +const getWorkspaceAgentLogSourcesByAgentIDs = `-- name: GetWorkspaceAgentLogSourcesByAgentIDs :many +SELECT workspace_agent_id, id, created_at, display_name, icon FROM workspace_agent_log_sources WHERE workspace_agent_id = ANY($1 :: uuid [ ]) +` + +func (q *sqlQuerier) GetWorkspaceAgentLogSourcesByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgentLogSource, error) { + rows, err := q.db.QueryContext(ctx, getWorkspaceAgentLogSourcesByAgentIDs, pq.Array(ids)) + if err != nil { + return nil, err + } + defer rows.Close() + var items []WorkspaceAgentLogSource + for rows.Next() { + var i WorkspaceAgentLogSource + if err := rows.Scan( + &i.WorkspaceAgentID, + &i.ID, + &i.CreatedAt, + &i.DisplayName, + &i.Icon, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getWorkspaceAgentLogsAfter = `-- name: GetWorkspaceAgentLogsAfter :many SELECT - agent_id, created_at, output, id, level, source + agent_id, created_at, output, id, level, log_source_id FROM workspace_agent_logs WHERE @@ -6957,7 +6975,7 @@ func (q *sqlQuerier) GetWorkspaceAgentLogsAfter(ctx context.Context, arg GetWork &i.Output, &i.ID, &i.Level, - &i.Source, + &i.LogSourceID, ); err != nil { return nil, err } @@ -7016,7 +7034,7 @@ func (q *sqlQuerier) GetWorkspaceAgentMetadata(ctx context.Context, workspaceAge const getWorkspaceAgentsByResourceIDs = `-- name: GetWorkspaceAgentsByResourceIDs :many SELECT - id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, logs_length, logs_overflowed, startup_script_behavior, started_at, ready_at, subsystems, display_apps + 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, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps FROM workspace_agents WHERE @@ -7046,7 +7064,6 @@ func (q *sqlQuerier) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids [] &i.Architecture, &i.EnvironmentVariables, &i.OperatingSystem, - &i.StartupScript, &i.InstanceMetadata, &i.ResourceMetadata, &i.Directory, @@ -7056,13 +7073,9 @@ func (q *sqlQuerier) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids [] &i.TroubleshootingURL, &i.MOTDFile, &i.LifecycleState, - &i.StartupScriptTimeoutSeconds, &i.ExpandedDirectory, - &i.ShutdownScript, - &i.ShutdownScriptTimeoutSeconds, &i.LogsLength, &i.LogsOverflowed, - &i.StartupScriptBehavior, &i.StartedAt, &i.ReadyAt, pq.Array(&i.Subsystems), @@ -7082,7 +7095,7 @@ func (q *sqlQuerier) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids [] } const getWorkspaceAgentsCreatedAfter = `-- name: GetWorkspaceAgentsCreatedAfter :many -SELECT id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, startup_script, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, logs_length, logs_overflowed, startup_script_behavior, started_at, ready_at, subsystems, display_apps 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, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps FROM workspace_agents WHERE created_at > $1 ` func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceAgent, error) { @@ -7108,7 +7121,6 @@ func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, created &i.Architecture, &i.EnvironmentVariables, &i.OperatingSystem, - &i.StartupScript, &i.InstanceMetadata, &i.ResourceMetadata, &i.Directory, @@ -7118,13 +7130,9 @@ func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, created &i.TroubleshootingURL, &i.MOTDFile, &i.LifecycleState, - &i.StartupScriptTimeoutSeconds, &i.ExpandedDirectory, - &i.ShutdownScript, - &i.ShutdownScriptTimeoutSeconds, &i.LogsLength, &i.LogsOverflowed, - &i.StartupScriptBehavior, &i.StartedAt, &i.ReadyAt, pq.Array(&i.Subsystems), @@ -7145,7 +7153,7 @@ func (q *sqlQuerier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, created const getWorkspaceAgentsInLatestBuildByWorkspaceID = `-- name: GetWorkspaceAgentsInLatestBuildByWorkspaceID :many SELECT - workspace_agents.id, workspace_agents.created_at, workspace_agents.updated_at, workspace_agents.name, workspace_agents.first_connected_at, workspace_agents.last_connected_at, workspace_agents.disconnected_at, workspace_agents.resource_id, workspace_agents.auth_token, workspace_agents.auth_instance_id, workspace_agents.architecture, workspace_agents.environment_variables, workspace_agents.operating_system, workspace_agents.startup_script, workspace_agents.instance_metadata, workspace_agents.resource_metadata, workspace_agents.directory, workspace_agents.version, workspace_agents.last_connected_replica_id, workspace_agents.connection_timeout_seconds, workspace_agents.troubleshooting_url, workspace_agents.motd_file, workspace_agents.lifecycle_state, workspace_agents.startup_script_timeout_seconds, workspace_agents.expanded_directory, workspace_agents.shutdown_script, workspace_agents.shutdown_script_timeout_seconds, workspace_agents.logs_length, workspace_agents.logs_overflowed, workspace_agents.startup_script_behavior, workspace_agents.started_at, workspace_agents.ready_at, workspace_agents.subsystems, workspace_agents.display_apps + workspace_agents.id, workspace_agents.created_at, workspace_agents.updated_at, workspace_agents.name, workspace_agents.first_connected_at, workspace_agents.last_connected_at, workspace_agents.disconnected_at, workspace_agents.resource_id, workspace_agents.auth_token, workspace_agents.auth_instance_id, workspace_agents.architecture, workspace_agents.environment_variables, workspace_agents.operating_system, workspace_agents.instance_metadata, workspace_agents.resource_metadata, workspace_agents.directory, workspace_agents.version, workspace_agents.last_connected_replica_id, workspace_agents.connection_timeout_seconds, workspace_agents.troubleshooting_url, workspace_agents.motd_file, workspace_agents.lifecycle_state, workspace_agents.expanded_directory, workspace_agents.logs_length, workspace_agents.logs_overflowed, workspace_agents.started_at, workspace_agents.ready_at, workspace_agents.subsystems, workspace_agents.display_apps FROM workspace_agents JOIN @@ -7187,7 +7195,6 @@ func (q *sqlQuerier) GetWorkspaceAgentsInLatestBuildByWorkspaceID(ctx context.Co &i.Architecture, &i.EnvironmentVariables, &i.OperatingSystem, - &i.StartupScript, &i.InstanceMetadata, &i.ResourceMetadata, &i.Directory, @@ -7197,13 +7204,9 @@ func (q *sqlQuerier) GetWorkspaceAgentsInLatestBuildByWorkspaceID(ctx context.Co &i.TroubleshootingURL, &i.MOTDFile, &i.LifecycleState, - &i.StartupScriptTimeoutSeconds, &i.ExpandedDirectory, - &i.ShutdownScript, - &i.ShutdownScriptTimeoutSeconds, &i.LogsLength, &i.LogsOverflowed, - &i.StartupScriptBehavior, &i.StartedAt, &i.ReadyAt, pq.Array(&i.Subsystems), @@ -7235,46 +7238,36 @@ INSERT INTO architecture, environment_variables, operating_system, - startup_script, directory, instance_metadata, resource_metadata, connection_timeout_seconds, troubleshooting_url, motd_file, - startup_script_behavior, - startup_script_timeout_seconds, - shutdown_script, - shutdown_script_timeout_seconds, display_apps ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22) 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, startup_script_timeout_seconds, expanded_directory, shutdown_script, shutdown_script_timeout_seconds, logs_length, logs_overflowed, startup_script_behavior, started_at, ready_at, subsystems, display_apps + ($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, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps ` 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"` - StartupScriptBehavior StartupScriptBehavior `db:"startup_script_behavior" json:"startup_script_behavior"` - StartupScriptTimeoutSeconds int32 `db:"startup_script_timeout_seconds" json:"startup_script_timeout_seconds"` - ShutdownScript sql.NullString `db:"shutdown_script" json:"shutdown_script"` - ShutdownScriptTimeoutSeconds int32 `db:"shutdown_script_timeout_seconds" json:"shutdown_script_timeout_seconds"` - DisplayApps []DisplayApp `db:"display_apps" json:"display_apps"` + 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"` + 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"` + DisplayApps []DisplayApp `db:"display_apps" json:"display_apps"` } func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspaceAgentParams) (WorkspaceAgent, error) { @@ -7289,17 +7282,12 @@ func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspa arg.Architecture, arg.EnvironmentVariables, arg.OperatingSystem, - arg.StartupScript, arg.Directory, arg.InstanceMetadata, arg.ResourceMetadata, arg.ConnectionTimeoutSeconds, arg.TroubleshootingURL, arg.MOTDFile, - arg.StartupScriptBehavior, - arg.StartupScriptTimeoutSeconds, - arg.ShutdownScript, - arg.ShutdownScriptTimeoutSeconds, pq.Array(arg.DisplayApps), ) var i WorkspaceAgent @@ -7317,7 +7305,6 @@ func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspa &i.Architecture, &i.EnvironmentVariables, &i.OperatingSystem, - &i.StartupScript, &i.InstanceMetadata, &i.ResourceMetadata, &i.Directory, @@ -7327,13 +7314,9 @@ func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspa &i.TroubleshootingURL, &i.MOTDFile, &i.LifecycleState, - &i.StartupScriptTimeoutSeconds, &i.ExpandedDirectory, - &i.ShutdownScript, - &i.ShutdownScriptTimeoutSeconds, &i.LogsLength, &i.LogsOverflowed, - &i.StartupScriptBehavior, &i.StartedAt, &i.ReadyAt, pq.Array(&i.Subsystems), @@ -7342,38 +7325,93 @@ func (q *sqlQuerier) InsertWorkspaceAgent(ctx context.Context, arg InsertWorkspa return i, err } +const insertWorkspaceAgentLogSources = `-- name: InsertWorkspaceAgentLogSources :many +INSERT INTO + workspace_agent_log_sources (workspace_agent_id, created_at, id, display_name, icon) + SELECT + $1 :: uuid AS workspace_agent_id, + $2 :: timestamptz AS created_at, + unnest($3 :: uuid [ ]) AS id, + unnest($4 :: VARCHAR(127) [ ]) AS display_name, + unnest($5 :: text [ ]) AS icon + RETURNING workspace_agent_log_sources.workspace_agent_id, workspace_agent_log_sources.id, workspace_agent_log_sources.created_at, workspace_agent_log_sources.display_name, workspace_agent_log_sources.icon +` + +type InsertWorkspaceAgentLogSourcesParams struct { + WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + ID []uuid.UUID `db:"id" json:"id"` + DisplayName []string `db:"display_name" json:"display_name"` + Icon []string `db:"icon" json:"icon"` +} + +func (q *sqlQuerier) InsertWorkspaceAgentLogSources(ctx context.Context, arg InsertWorkspaceAgentLogSourcesParams) ([]WorkspaceAgentLogSource, error) { + rows, err := q.db.QueryContext(ctx, insertWorkspaceAgentLogSources, + arg.WorkspaceAgentID, + arg.CreatedAt, + pq.Array(arg.ID), + pq.Array(arg.DisplayName), + pq.Array(arg.Icon), + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []WorkspaceAgentLogSource + for rows.Next() { + var i WorkspaceAgentLogSource + if err := rows.Scan( + &i.WorkspaceAgentID, + &i.ID, + &i.CreatedAt, + &i.DisplayName, + &i.Icon, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const insertWorkspaceAgentLogs = `-- name: InsertWorkspaceAgentLogs :many WITH new_length AS ( UPDATE workspace_agents SET logs_length = logs_length + $6 WHERE workspace_agents.id = $1 ) INSERT INTO - workspace_agent_logs (agent_id, created_at, output, level, source) + workspace_agent_logs (agent_id, created_at, output, level, log_source_id) SELECT $1 :: uuid AS agent_id, - unnest($2 :: timestamptz [ ]) AS created_at, + $2 :: timestamptz AS created_at, unnest($3 :: VARCHAR(1024) [ ]) AS output, unnest($4 :: log_level [ ]) AS level, - unnest($5 :: workspace_agent_log_source [ ]) AS source - RETURNING workspace_agent_logs.agent_id, workspace_agent_logs.created_at, workspace_agent_logs.output, workspace_agent_logs.id, workspace_agent_logs.level, workspace_agent_logs.source + $5 :: uuid AS log_source_id + RETURNING workspace_agent_logs.agent_id, workspace_agent_logs.created_at, workspace_agent_logs.output, workspace_agent_logs.id, workspace_agent_logs.level, workspace_agent_logs.log_source_id ` type InsertWorkspaceAgentLogsParams struct { - AgentID uuid.UUID `db:"agent_id" json:"agent_id"` - CreatedAt []time.Time `db:"created_at" json:"created_at"` - Output []string `db:"output" json:"output"` - Level []LogLevel `db:"level" json:"level"` - Source []WorkspaceAgentLogSource `db:"source" json:"source"` - OutputLength int32 `db:"output_length" json:"output_length"` + AgentID uuid.UUID `db:"agent_id" json:"agent_id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + Output []string `db:"output" json:"output"` + Level []LogLevel `db:"level" json:"level"` + LogSourceID uuid.UUID `db:"log_source_id" json:"log_source_id"` + OutputLength int32 `db:"output_length" json:"output_length"` } func (q *sqlQuerier) InsertWorkspaceAgentLogs(ctx context.Context, arg InsertWorkspaceAgentLogsParams) ([]WorkspaceAgentLog, error) { rows, err := q.db.QueryContext(ctx, insertWorkspaceAgentLogs, arg.AgentID, - pq.Array(arg.CreatedAt), + arg.CreatedAt, pq.Array(arg.Output), pq.Array(arg.Level), - pq.Array(arg.Source), + arg.LogSourceID, arg.OutputLength, ) if err != nil { @@ -7389,7 +7427,7 @@ func (q *sqlQuerier) InsertWorkspaceAgentLogs(ctx context.Context, arg InsertWor &i.Output, &i.ID, &i.Level, - &i.Source, + &i.LogSourceID, ); err != nil { return nil, err } @@ -10322,3 +10360,116 @@ func (q *sqlQuerier) UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.C _, err := q.db.ExecContext(ctx, updateWorkspacesDormantDeletingAtByTemplateID, arg.TimeTilDormantAutodeleteMs, arg.DormantAt, arg.TemplateID) return err } + +const getWorkspaceAgentScriptsByAgentIDs = `-- name: GetWorkspaceAgentScriptsByAgentIDs :many +SELECT workspace_agent_id, log_source_id, log_path, created_at, script, cron, start_blocks_login, run_on_start, run_on_stop, timeout_seconds FROM workspace_agent_scripts WHERE workspace_agent_id = ANY($1 :: uuid [ ]) +` + +func (q *sqlQuerier) GetWorkspaceAgentScriptsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgentScript, error) { + rows, err := q.db.QueryContext(ctx, getWorkspaceAgentScriptsByAgentIDs, pq.Array(ids)) + if err != nil { + return nil, err + } + defer rows.Close() + var items []WorkspaceAgentScript + for rows.Next() { + var i WorkspaceAgentScript + if err := rows.Scan( + &i.WorkspaceAgentID, + &i.LogSourceID, + &i.LogPath, + &i.CreatedAt, + &i.Script, + &i.Cron, + &i.StartBlocksLogin, + &i.RunOnStart, + &i.RunOnStop, + &i.TimeoutSeconds, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const insertWorkspaceAgentScripts = `-- name: InsertWorkspaceAgentScripts :many +INSERT INTO + workspace_agent_scripts (workspace_agent_id, created_at, log_source_id, log_path, script, cron, start_blocks_login, run_on_start, run_on_stop, timeout_seconds) +SELECT + $1 :: uuid AS workspace_agent_id, + $2 :: timestamptz AS created_at, + unnest($3 :: uuid [ ]) AS log_source_id, + unnest($4 :: text [ ]) AS log_path, + unnest($5 :: text [ ]) AS script, + unnest($6 :: text [ ]) AS cron, + unnest($7 :: boolean [ ]) AS start_blocks_login, + unnest($8 :: boolean [ ]) AS run_on_start, + unnest($9 :: boolean [ ]) AS run_on_stop, + unnest($10 :: integer [ ]) AS timeout_seconds +RETURNING workspace_agent_scripts.workspace_agent_id, workspace_agent_scripts.log_source_id, workspace_agent_scripts.log_path, workspace_agent_scripts.created_at, workspace_agent_scripts.script, workspace_agent_scripts.cron, workspace_agent_scripts.start_blocks_login, workspace_agent_scripts.run_on_start, workspace_agent_scripts.run_on_stop, workspace_agent_scripts.timeout_seconds +` + +type InsertWorkspaceAgentScriptsParams struct { + WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + LogSourceID []uuid.UUID `db:"log_source_id" json:"log_source_id"` + LogPath []string `db:"log_path" json:"log_path"` + Script []string `db:"script" json:"script"` + Cron []string `db:"cron" json:"cron"` + StartBlocksLogin []bool `db:"start_blocks_login" json:"start_blocks_login"` + RunOnStart []bool `db:"run_on_start" json:"run_on_start"` + RunOnStop []bool `db:"run_on_stop" json:"run_on_stop"` + TimeoutSeconds []int32 `db:"timeout_seconds" json:"timeout_seconds"` +} + +func (q *sqlQuerier) InsertWorkspaceAgentScripts(ctx context.Context, arg InsertWorkspaceAgentScriptsParams) ([]WorkspaceAgentScript, error) { + rows, err := q.db.QueryContext(ctx, insertWorkspaceAgentScripts, + arg.WorkspaceAgentID, + arg.CreatedAt, + pq.Array(arg.LogSourceID), + pq.Array(arg.LogPath), + pq.Array(arg.Script), + pq.Array(arg.Cron), + pq.Array(arg.StartBlocksLogin), + pq.Array(arg.RunOnStart), + pq.Array(arg.RunOnStop), + pq.Array(arg.TimeoutSeconds), + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []WorkspaceAgentScript + for rows.Next() { + var i WorkspaceAgentScript + if err := rows.Scan( + &i.WorkspaceAgentID, + &i.LogSourceID, + &i.LogPath, + &i.CreatedAt, + &i.Script, + &i.Cron, + &i.StartBlocksLogin, + &i.RunOnStart, + &i.RunOnStop, + &i.TimeoutSeconds, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/coderd/database/queries/workspaceagents.sql b/coderd/database/queries/workspaceagents.sql index bc2145df70e22..0e9ec08152a69 100644 --- a/coderd/database/queries/workspaceagents.sql +++ b/coderd/database/queries/workspaceagents.sql @@ -40,21 +40,16 @@ INSERT INTO architecture, environment_variables, operating_system, - startup_script, directory, instance_metadata, resource_metadata, connection_timeout_seconds, troubleshooting_url, motd_file, - startup_script_behavior, - startup_script_timeout_seconds, - shutdown_script, - shutdown_script_timeout_seconds, display_apps ) VALUES - ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22) RETURNING *; + ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17) RETURNING *; -- name: UpdateWorkspaceAgentConnectionByID :exec UPDATE @@ -156,15 +151,29 @@ WITH new_length AS ( logs_length = logs_length + @output_length WHERE workspace_agents.id = @agent_id ) INSERT INTO - workspace_agent_logs (agent_id, created_at, output, level, source) + workspace_agent_logs (agent_id, created_at, output, level, log_source_id) SELECT @agent_id :: uuid AS agent_id, - unnest(@created_at :: timestamptz [ ]) AS created_at, + @created_at :: timestamptz AS created_at, unnest(@output :: VARCHAR(1024) [ ]) AS output, unnest(@level :: log_level [ ]) AS level, - unnest(@source :: workspace_agent_log_source [ ]) AS source + @log_source_id :: uuid AS log_source_id RETURNING workspace_agent_logs.*; +-- name: InsertWorkspaceAgentLogSources :many +INSERT INTO + workspace_agent_log_sources (workspace_agent_id, created_at, id, display_name, icon) + SELECT + @workspace_agent_id :: uuid AS workspace_agent_id, + @created_at :: timestamptz AS created_at, + unnest(@id :: uuid [ ]) AS id, + unnest(@display_name :: VARCHAR(127) [ ]) AS display_name, + unnest(@icon :: text [ ]) AS icon + RETURNING workspace_agent_log_sources.*; + +-- name: GetWorkspaceAgentLogSourcesByAgentIDs :many +SELECT * FROM workspace_agent_log_sources WHERE workspace_agent_id = ANY(@ids :: uuid [ ]); + -- If an agent hasn't connected in the last 7 days, we purge it's logs. -- Logs can take up a lot of space, so it's important we clean up frequently. -- name: DeleteOldWorkspaceAgentLogs :exec diff --git a/coderd/database/queries/workspacescripts.sql b/coderd/database/queries/workspacescripts.sql new file mode 100644 index 0000000000000..8dc234afd37d3 --- /dev/null +++ b/coderd/database/queries/workspacescripts.sql @@ -0,0 +1,18 @@ +-- name: InsertWorkspaceAgentScripts :many +INSERT INTO + workspace_agent_scripts (workspace_agent_id, created_at, log_source_id, log_path, script, cron, start_blocks_login, run_on_start, run_on_stop, timeout_seconds) +SELECT + @workspace_agent_id :: uuid AS workspace_agent_id, + @created_at :: timestamptz AS created_at, + unnest(@log_source_id :: uuid [ ]) AS log_source_id, + unnest(@log_path :: text [ ]) AS log_path, + unnest(@script :: text [ ]) AS script, + unnest(@cron :: text [ ]) AS cron, + unnest(@start_blocks_login :: boolean [ ]) AS start_blocks_login, + unnest(@run_on_start :: boolean [ ]) AS run_on_start, + unnest(@run_on_stop :: boolean [ ]) AS run_on_stop, + unnest(@timeout_seconds :: integer [ ]) AS timeout_seconds +RETURNING workspace_agent_scripts.*; + +-- name: GetWorkspaceAgentScriptsByAgentIDs :many +SELECT * FROM workspace_agent_scripts WHERE workspace_agent_id = ANY(@ids :: uuid [ ]); diff --git a/coderd/database/unique_constraint.go b/coderd/database/unique_constraint.go index c673270f2be02..5be23df0d8bcf 100644 --- a/coderd/database/unique_constraint.go +++ b/coderd/database/unique_constraint.go @@ -6,27 +6,61 @@ type UniqueConstraint string // UniqueConstraint enums. const ( + UniqueAgentStatsPkey UniqueConstraint = "agent_stats_pkey" // ALTER TABLE ONLY workspace_agent_stats ADD CONSTRAINT agent_stats_pkey PRIMARY KEY (id); + UniqueAPIKeysPkey UniqueConstraint = "api_keys_pkey" // ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id); + UniqueAuditLogsPkey UniqueConstraint = "audit_logs_pkey" // ALTER TABLE ONLY audit_logs ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id); UniqueDbcryptKeysActiveKeyDigestKey UniqueConstraint = "dbcrypt_keys_active_key_digest_key" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_active_key_digest_key UNIQUE (active_key_digest); + UniqueDbcryptKeysPkey UniqueConstraint = "dbcrypt_keys_pkey" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_pkey PRIMARY KEY (number); UniqueDbcryptKeysRevokedKeyDigestKey UniqueConstraint = "dbcrypt_keys_revoked_key_digest_key" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_revoked_key_digest_key UNIQUE (revoked_key_digest); UniqueFilesHashCreatedByKey UniqueConstraint = "files_hash_created_by_key" // ALTER TABLE ONLY files ADD CONSTRAINT files_hash_created_by_key UNIQUE (hash, created_by); + UniqueFilesPkey UniqueConstraint = "files_pkey" // ALTER TABLE ONLY files ADD CONSTRAINT files_pkey PRIMARY KEY (id); UniqueGitAuthLinksProviderIDUserIDKey UniqueConstraint = "git_auth_links_provider_id_user_id_key" // ALTER TABLE ONLY git_auth_links ADD CONSTRAINT git_auth_links_provider_id_user_id_key UNIQUE (provider_id, user_id); + UniqueGitSSHKeysPkey UniqueConstraint = "gitsshkeys_pkey" // ALTER TABLE ONLY gitsshkeys ADD CONSTRAINT gitsshkeys_pkey PRIMARY KEY (user_id); UniqueGroupMembersUserIDGroupIDKey UniqueConstraint = "group_members_user_id_group_id_key" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_user_id_group_id_key UNIQUE (user_id, group_id); UniqueGroupsNameOrganizationIDKey UniqueConstraint = "groups_name_organization_id_key" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_name_organization_id_key UNIQUE (name, organization_id); + UniqueGroupsPkey UniqueConstraint = "groups_pkey" // ALTER TABLE ONLY groups ADD CONSTRAINT groups_pkey PRIMARY KEY (id); UniqueLicensesJWTKey UniqueConstraint = "licenses_jwt_key" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_jwt_key UNIQUE (jwt); + UniqueLicensesPkey UniqueConstraint = "licenses_pkey" // ALTER TABLE ONLY licenses ADD CONSTRAINT licenses_pkey PRIMARY KEY (id); + UniqueOrganizationMembersPkey UniqueConstraint = "organization_members_pkey" // ALTER TABLE ONLY organization_members ADD CONSTRAINT organization_members_pkey PRIMARY KEY (organization_id, user_id); + UniqueOrganizationsPkey UniqueConstraint = "organizations_pkey" // ALTER TABLE ONLY organizations ADD CONSTRAINT organizations_pkey PRIMARY KEY (id); UniqueParameterSchemasJobIDNameKey UniqueConstraint = "parameter_schemas_job_id_name_key" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_job_id_name_key UNIQUE (job_id, name); + UniqueParameterSchemasPkey UniqueConstraint = "parameter_schemas_pkey" // ALTER TABLE ONLY parameter_schemas ADD CONSTRAINT parameter_schemas_pkey PRIMARY KEY (id); + UniqueParameterValuesPkey UniqueConstraint = "parameter_values_pkey" // ALTER TABLE ONLY parameter_values ADD CONSTRAINT parameter_values_pkey PRIMARY KEY (id); UniqueParameterValuesScopeIDNameKey UniqueConstraint = "parameter_values_scope_id_name_key" // ALTER TABLE ONLY parameter_values ADD CONSTRAINT parameter_values_scope_id_name_key UNIQUE (scope_id, name); UniqueProvisionerDaemonsNameKey UniqueConstraint = "provisioner_daemons_name_key" // ALTER TABLE ONLY provisioner_daemons ADD CONSTRAINT provisioner_daemons_name_key UNIQUE (name); + UniqueProvisionerDaemonsPkey UniqueConstraint = "provisioner_daemons_pkey" // ALTER TABLE ONLY provisioner_daemons ADD CONSTRAINT provisioner_daemons_pkey PRIMARY KEY (id); + UniqueProvisionerJobLogsPkey UniqueConstraint = "provisioner_job_logs_pkey" // ALTER TABLE ONLY provisioner_job_logs ADD CONSTRAINT provisioner_job_logs_pkey PRIMARY KEY (id); + UniqueProvisionerJobsPkey UniqueConstraint = "provisioner_jobs_pkey" // ALTER TABLE ONLY provisioner_jobs ADD CONSTRAINT provisioner_jobs_pkey PRIMARY KEY (id); UniqueSiteConfigsKeyKey UniqueConstraint = "site_configs_key_key" // ALTER TABLE ONLY site_configs ADD CONSTRAINT site_configs_key_key UNIQUE (key); + UniqueTailnetAgentsPkey UniqueConstraint = "tailnet_agents_pkey" // ALTER TABLE ONLY tailnet_agents ADD CONSTRAINT tailnet_agents_pkey PRIMARY KEY (id, coordinator_id); + UniqueTailnetClientSubscriptionsPkey UniqueConstraint = "tailnet_client_subscriptions_pkey" // ALTER TABLE ONLY tailnet_client_subscriptions ADD CONSTRAINT tailnet_client_subscriptions_pkey PRIMARY KEY (client_id, coordinator_id, agent_id); + UniqueTailnetClientsPkey UniqueConstraint = "tailnet_clients_pkey" // ALTER TABLE ONLY tailnet_clients ADD CONSTRAINT tailnet_clients_pkey PRIMARY KEY (id, coordinator_id); + UniqueTailnetCoordinatorsPkey UniqueConstraint = "tailnet_coordinators_pkey" // ALTER TABLE ONLY tailnet_coordinators ADD CONSTRAINT tailnet_coordinators_pkey PRIMARY KEY (id); UniqueTemplateVersionParametersTemplateVersionIDNameKey UniqueConstraint = "template_version_parameters_template_version_id_name_key" // ALTER TABLE ONLY template_version_parameters ADD CONSTRAINT template_version_parameters_template_version_id_name_key UNIQUE (template_version_id, name); UniqueTemplateVersionVariablesTemplateVersionIDNameKey UniqueConstraint = "template_version_variables_template_version_id_name_key" // ALTER TABLE ONLY template_version_variables ADD CONSTRAINT template_version_variables_template_version_id_name_key UNIQUE (template_version_id, name); + UniqueTemplateVersionsPkey UniqueConstraint = "template_versions_pkey" // ALTER TABLE ONLY template_versions ADD CONSTRAINT template_versions_pkey PRIMARY KEY (id); UniqueTemplateVersionsTemplateIDNameKey UniqueConstraint = "template_versions_template_id_name_key" // ALTER TABLE ONLY template_versions ADD CONSTRAINT template_versions_template_id_name_key UNIQUE (template_id, name); + UniqueTemplatesPkey UniqueConstraint = "templates_pkey" // ALTER TABLE ONLY templates ADD CONSTRAINT templates_pkey PRIMARY KEY (id); + UniqueUserLinksPkey UniqueConstraint = "user_links_pkey" // ALTER TABLE ONLY user_links ADD CONSTRAINT user_links_pkey PRIMARY KEY (user_id, login_type); + UniqueUsersPkey UniqueConstraint = "users_pkey" // ALTER TABLE ONLY users ADD CONSTRAINT users_pkey PRIMARY KEY (id); + UniqueWorkspaceAgentLogSourcesPkey UniqueConstraint = "workspace_agent_log_sources_pkey" // ALTER TABLE ONLY workspace_agent_log_sources ADD CONSTRAINT workspace_agent_log_sources_pkey PRIMARY KEY (workspace_agent_id, id); + UniqueWorkspaceAgentMetadataPkey UniqueConstraint = "workspace_agent_metadata_pkey" // ALTER TABLE ONLY workspace_agent_metadata ADD CONSTRAINT workspace_agent_metadata_pkey PRIMARY KEY (workspace_agent_id, key); + UniqueWorkspaceAgentStartupLogsPkey UniqueConstraint = "workspace_agent_startup_logs_pkey" // ALTER TABLE ONLY workspace_agent_logs ADD CONSTRAINT workspace_agent_startup_logs_pkey PRIMARY KEY (id); + UniqueWorkspaceAgentsPkey UniqueConstraint = "workspace_agents_pkey" // ALTER TABLE ONLY workspace_agents ADD CONSTRAINT workspace_agents_pkey PRIMARY KEY (id); + UniqueWorkspaceAppStatsPkey UniqueConstraint = "workspace_app_stats_pkey" // ALTER TABLE ONLY workspace_app_stats ADD CONSTRAINT workspace_app_stats_pkey PRIMARY KEY (id); UniqueWorkspaceAppStatsUserIDAgentIDSessionIDKey UniqueConstraint = "workspace_app_stats_user_id_agent_id_session_id_key" // ALTER TABLE ONLY workspace_app_stats ADD CONSTRAINT workspace_app_stats_user_id_agent_id_session_id_key UNIQUE (user_id, agent_id, session_id); UniqueWorkspaceAppsAgentIDSlugIndex UniqueConstraint = "workspace_apps_agent_id_slug_idx" // ALTER TABLE ONLY workspace_apps ADD CONSTRAINT workspace_apps_agent_id_slug_idx UNIQUE (agent_id, slug); + UniqueWorkspaceAppsPkey UniqueConstraint = "workspace_apps_pkey" // ALTER TABLE ONLY workspace_apps ADD CONSTRAINT workspace_apps_pkey PRIMARY KEY (id); UniqueWorkspaceBuildParametersWorkspaceBuildIDNameKey UniqueConstraint = "workspace_build_parameters_workspace_build_id_name_key" // ALTER TABLE ONLY workspace_build_parameters ADD CONSTRAINT workspace_build_parameters_workspace_build_id_name_key UNIQUE (workspace_build_id, name); UniqueWorkspaceBuildsJobIDKey UniqueConstraint = "workspace_builds_job_id_key" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_job_id_key UNIQUE (job_id); + UniqueWorkspaceBuildsPkey UniqueConstraint = "workspace_builds_pkey" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_pkey PRIMARY KEY (id); UniqueWorkspaceBuildsWorkspaceIDBuildNumberKey UniqueConstraint = "workspace_builds_workspace_id_build_number_key" // ALTER TABLE ONLY workspace_builds ADD CONSTRAINT workspace_builds_workspace_id_build_number_key UNIQUE (workspace_id, build_number); + UniqueWorkspaceProxiesPkey UniqueConstraint = "workspace_proxies_pkey" // ALTER TABLE ONLY workspace_proxies ADD CONSTRAINT workspace_proxies_pkey PRIMARY KEY (id); UniqueWorkspaceProxiesRegionIDUnique UniqueConstraint = "workspace_proxies_region_id_unique" // ALTER TABLE ONLY workspace_proxies ADD CONSTRAINT workspace_proxies_region_id_unique UNIQUE (region_id); UniqueWorkspaceResourceMetadataName UniqueConstraint = "workspace_resource_metadata_name" // ALTER TABLE ONLY workspace_resource_metadata ADD CONSTRAINT workspace_resource_metadata_name UNIQUE (workspace_resource_id, key); + UniqueWorkspaceResourceMetadataPkey UniqueConstraint = "workspace_resource_metadata_pkey" // ALTER TABLE ONLY workspace_resource_metadata ADD CONSTRAINT workspace_resource_metadata_pkey PRIMARY KEY (id); + UniqueWorkspaceResourcesPkey UniqueConstraint = "workspace_resources_pkey" // ALTER TABLE ONLY workspace_resources ADD CONSTRAINT workspace_resources_pkey PRIMARY KEY (id); + UniqueWorkspacesPkey UniqueConstraint = "workspaces_pkey" // ALTER TABLE ONLY workspaces ADD CONSTRAINT workspaces_pkey PRIMARY KEY (id); UniqueIndexAPIKeyName UniqueConstraint = "idx_api_key_name" // CREATE UNIQUE INDEX idx_api_key_name ON api_keys USING btree (user_id, token_name) WHERE (login_type = 'token'::login_type); UniqueIndexOrganizationName UniqueConstraint = "idx_organization_name" // CREATE UNIQUE INDEX idx_organization_name ON organizations USING btree (name); UniqueIndexOrganizationNameLower UniqueConstraint = "idx_organization_name_lower" // CREATE UNIQUE INDEX idx_organization_name_lower ON organizations USING btree (lower(name)); diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 0657cf358c4bc..92a7b988c7b3a 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1367,39 +1367,23 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. } } - // Set the default in case it was not provided (e.g. echo provider). - if prAgent.GetStartupScriptBehavior() == "" { - prAgent.StartupScriptBehavior = string(codersdk.WorkspaceAgentStartupScriptBehaviorNonBlocking) - } - agentID := uuid.New() dbAgent, err := db.InsertWorkspaceAgent(ctx, database.InsertWorkspaceAgentParams{ - ID: agentID, - CreatedAt: dbtime.Now(), - UpdatedAt: dbtime.Now(), - ResourceID: resource.ID, - Name: prAgent.Name, - AuthToken: authToken, - AuthInstanceID: instanceID, - Architecture: prAgent.Architecture, - EnvironmentVariables: env, - Directory: prAgent.Directory, - OperatingSystem: prAgent.OperatingSystem, - StartupScript: sql.NullString{ - String: prAgent.StartupScript, - Valid: prAgent.StartupScript != "", - }, - ConnectionTimeoutSeconds: prAgent.GetConnectionTimeoutSeconds(), - TroubleshootingURL: prAgent.GetTroubleshootingUrl(), - MOTDFile: prAgent.GetMotdFile(), - StartupScriptBehavior: database.StartupScriptBehavior(prAgent.GetStartupScriptBehavior()), - StartupScriptTimeoutSeconds: prAgent.GetStartupScriptTimeoutSeconds(), - ShutdownScript: sql.NullString{ - String: prAgent.ShutdownScript, - Valid: prAgent.ShutdownScript != "", - }, - ShutdownScriptTimeoutSeconds: prAgent.GetShutdownScriptTimeoutSeconds(), - DisplayApps: convertDisplayApps(prAgent.GetDisplayApps()), + ID: agentID, + CreatedAt: dbtime.Now(), + UpdatedAt: dbtime.Now(), + ResourceID: resource.ID, + Name: prAgent.Name, + AuthToken: authToken, + AuthInstanceID: instanceID, + Architecture: prAgent.Architecture, + EnvironmentVariables: env, + Directory: prAgent.Directory, + OperatingSystem: prAgent.OperatingSystem, + ConnectionTimeoutSeconds: prAgent.GetConnectionTimeoutSeconds(), + TroubleshootingURL: prAgent.GetTroubleshootingUrl(), + MOTDFile: prAgent.GetMotdFile(), + DisplayApps: convertDisplayApps(prAgent.GetDisplayApps()), }) if err != nil { return xerrors.Errorf("insert agent: %w", err) @@ -1421,6 +1405,57 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. } } + logSourceIDs := make([]uuid.UUID, 0, len(prAgent.Scripts)) + logSourceDisplayNames := make([]string, 0, len(prAgent.Scripts)) + logSourceIcons := make([]string, 0, len(prAgent.Scripts)) + scriptLogPaths := make([]string, 0, len(prAgent.Scripts)) + scriptSources := make([]string, 0, len(prAgent.Scripts)) + scriptCron := make([]string, 0, len(prAgent.Scripts)) + scriptTimeout := make([]int32, 0, len(prAgent.Scripts)) + scriptStartBlocksLogin := make([]bool, 0, len(prAgent.Scripts)) + scriptRunOnStart := make([]bool, 0, len(prAgent.Scripts)) + scriptRunOnStop := make([]bool, 0, len(prAgent.Scripts)) + + for _, script := range prAgent.Scripts { + logSourceIDs = append(logSourceIDs, uuid.New()) + logSourceDisplayNames = append(logSourceDisplayNames, script.DisplayName) + logSourceIcons = append(logSourceIcons, script.Icon) + scriptLogPaths = append(scriptLogPaths, script.LogPath) + scriptSources = append(scriptSources, script.Script) + scriptCron = append(scriptCron, script.Cron) + scriptTimeout = append(scriptTimeout, script.TimeoutSeconds) + scriptStartBlocksLogin = append(scriptStartBlocksLogin, script.StartBlocksLogin) + scriptRunOnStart = append(scriptRunOnStart, script.RunOnStart) + scriptRunOnStop = append(scriptRunOnStop, script.RunOnStop) + } + + _, err = db.InsertWorkspaceAgentLogSources(ctx, database.InsertWorkspaceAgentLogSourcesParams{ + WorkspaceAgentID: agentID, + ID: logSourceIDs, + CreatedAt: dbtime.Now(), + DisplayName: logSourceDisplayNames, + Icon: logSourceIcons, + }) + if err != nil { + return xerrors.Errorf("insert agent log sources: %w", err) + } + + _, err = db.InsertWorkspaceAgentScripts(ctx, database.InsertWorkspaceAgentScriptsParams{ + WorkspaceAgentID: agentID, + LogSourceID: logSourceIDs, + LogPath: scriptLogPaths, + CreatedAt: dbtime.Now(), + Script: scriptSources, + Cron: scriptCron, + TimeoutSeconds: scriptTimeout, + StartBlocksLogin: scriptStartBlocksLogin, + RunOnStart: scriptRunOnStart, + RunOnStop: scriptRunOnStop, + }) + if err != nil { + return xerrors.Errorf("insert agent scripts: %w", err) + } + for _, app := range prAgent.Apps { slug := app.Slug if slug == "" { diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index 871024f41c691..82f9855cf90d5 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -1576,7 +1576,6 @@ func TestInsertWorkspaceResource(t *testing.T) { Env: map[string]string{ "something": "test", }, - StartupScript: "value", OperatingSystem: "linux", Architecture: "amd64", Auth: &sdkproto.Agent_Token{ @@ -1585,7 +1584,10 @@ func TestInsertWorkspaceResource(t *testing.T) { Apps: []*sdkproto.App{{ Slug: "a", }}, - ShutdownScript: "shutdown", + Scripts: []*sdkproto.Script{{ + DisplayName: "Startup", + Icon: "/test.png", + }}, DisplayApps: &sdkproto.DisplayApps{ Vscode: true, PortForwardingHelper: true, @@ -1604,8 +1606,6 @@ func TestInsertWorkspaceResource(t *testing.T) { agent := agents[0] require.Equal(t, "amd64", agent.Architecture) require.Equal(t, "linux", agent.OperatingSystem) - require.Equal(t, "value", agent.StartupScript.String) - require.Equal(t, "shutdown", agent.ShutdownScript.String) want, err := json.Marshal(map[string]string{ "something": "test", }) diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index 22218aaf83cc4..5e3d27ee32800 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -124,6 +124,32 @@ func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request, return } + // nolint:gocritic // GetWorkspaceAgentScriptsByAgentIDs is a system function. + scripts, err := api.Database.GetWorkspaceAgentScriptsByAgentIDs(dbauthz.AsSystemRestricted(ctx), resourceAgentIDs) + if errors.Is(err, sql.ErrNoRows) { + err = nil + } + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching workspace agent scripts.", + Detail: err.Error(), + }) + return + } + + // nolint:gocritic // GetWorkspaceAgentLogSourcesByAgentIDs is a system function. + logSources, err := api.Database.GetWorkspaceAgentLogSourcesByAgentIDs(dbauthz.AsSystemRestricted(ctx), resourceAgentIDs) + if errors.Is(err, sql.ErrNoRows) { + err = nil + } + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching workspace agent log sources.", + Detail: err.Error(), + }) + return + } + // nolint:gocritic // GetWorkspaceResourceMetadataByResourceIDs is a system function. resourceMetadata, err := api.Database.GetWorkspaceResourceMetadataByResourceIDs(dbauthz.AsSystemRestricted(ctx), resourceIDs) if err != nil { @@ -147,9 +173,21 @@ func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request, dbApps = append(dbApps, app) } } + dbScripts := make([]database.WorkspaceAgentScript, 0) + for _, script := range scripts { + if script.WorkspaceAgentID == agent.ID { + dbScripts = append(dbScripts, script) + } + } + dbLogSources := make([]database.WorkspaceAgentLogSource, 0) + for _, logSource := range logSources { + if logSource.WorkspaceAgentID == agent.ID { + dbLogSources = append(dbLogSources, logSource) + } + } apiAgent, err := convertWorkspaceAgent( - api.DERPMap(), *api.TailnetCoordinator.Load(), agent, convertProvisionedApps(dbApps), api.AgentInactiveDisconnectTimeout, + api.DERPMap(), *api.TailnetCoordinator.Load(), agent, convertProvisionedApps(dbApps), convertScripts(dbScripts), convertLogSources(dbLogSources), api.AgentInactiveDisconnectTimeout, api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), ) if err != nil { diff --git a/coderd/telemetry/telemetry.go b/coderd/telemetry/telemetry.go index f37770f2fb234..e37a948a0c60c 100644 --- a/coderd/telemetry/telemetry.go +++ b/coderd/telemetry/telemetry.go @@ -559,10 +559,8 @@ func ConvertWorkspaceAgent(agent database.WorkspaceAgent) WorkspaceAgent { Architecture: agent.Architecture, OperatingSystem: agent.OperatingSystem, EnvironmentVariables: agent.EnvironmentVariables.Valid, - StartupScript: agent.StartupScript.Valid, Directory: agent.Directory != "", ConnectionTimeoutSeconds: agent.ConnectionTimeoutSeconds, - ShutdownScript: agent.ShutdownScript.Valid, Subsystems: subsystems, } if agent.FirstConnectedAt.Valid { @@ -792,13 +790,11 @@ type WorkspaceAgent struct { Architecture string `json:"architecture"` OperatingSystem string `json:"operating_system"` EnvironmentVariables bool `json:"environment_variables"` - StartupScript bool `json:"startup_script"` Directory bool `json:"directory"` FirstConnectedAt *time.Time `json:"first_connected_at"` LastConnectedAt *time.Time `json:"last_connected_at"` DisconnectedAt *time.Time `json:"disconnected_at"` ConnectionTimeoutSeconds int32 `json:"connection_timeout_seconds"` - ShutdownScript bool `json:"shutdown_script"` Subsystems []string `json:"subsystems"` } diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 82778ca119e12..c506c83e20666 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -56,10 +56,29 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() workspaceAgent := httpmw.WorkspaceAgentParam(r) - dbApps, err := api.Database.GetWorkspaceAppsByAgentID(ctx, workspaceAgent.ID) - if err != nil && !xerrors.Is(err, sql.ErrNoRows) { + var ( + dbApps []database.WorkspaceApp + scripts []database.WorkspaceAgentScript + logSources []database.WorkspaceAgentLogSource + ) + + var eg errgroup.Group + eg.Go(func() (err error) { + dbApps, err = api.Database.GetWorkspaceAppsByAgentID(ctx, workspaceAgent.ID) + return err + }) + eg.Go(func() (err error) { + scripts, err = api.Database.GetWorkspaceAgentScriptsByAgentIDs(ctx, []uuid.UUID{workspaceAgent.ID}) + return err + }) + eg.Go(func() (err error) { + logSources, err = api.Database.GetWorkspaceAgentLogSourcesByAgentIDs(ctx, []uuid.UUID{workspaceAgent.ID}) + return err + }) + err := eg.Wait() + if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace agent applications.", + Message: "Internal error fetching workspace agent.", Detail: err.Error(), }) return @@ -99,7 +118,7 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) { } apiAgent, err := convertWorkspaceAgent( - api.DERPMap(), *api.TailnetCoordinator.Load(), workspaceAgent, convertApps(dbApps, workspaceAgent, owner, workspace), api.AgentInactiveDisconnectTimeout, + api.DERPMap(), *api.TailnetCoordinator.Load(), workspaceAgent, convertApps(dbApps, workspaceAgent, owner, workspace), convertScripts(scripts), convertLogSources(logSources), api.AgentInactiveDisconnectTimeout, api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), ) if err != nil { @@ -124,7 +143,7 @@ func (api *API) workspaceAgentManifest(rw http.ResponseWriter, r *http.Request) ctx := r.Context() workspaceAgent := httpmw.WorkspaceAgent(r) apiAgent, err := convertWorkspaceAgent( - api.DERPMap(), *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout, + api.DERPMap(), *api.TailnetCoordinator.Load(), workspaceAgent, nil, nil, nil, api.AgentInactiveDisconnectTimeout, api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), ) if err != nil { @@ -134,52 +153,57 @@ func (api *API) workspaceAgentManifest(rw http.ResponseWriter, r *http.Request) }) return } - dbApps, err := api.Database.GetWorkspaceAppsByAgentID(ctx, workspaceAgent.ID) - if err != nil && !xerrors.Is(err, sql.ErrNoRows) { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace agent applications.", - Detail: err.Error(), - }) - return - } - metadata, err := api.Database.GetWorkspaceAgentMetadata(ctx, workspaceAgent.ID) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace agent metadata.", - Detail: err.Error(), - }) - return - } + var ( + dbApps []database.WorkspaceApp + scripts []database.WorkspaceAgentScript + metadata []database.WorkspaceAgentMetadatum + resource database.WorkspaceResource + build database.WorkspaceBuild + workspace database.Workspace + owner database.User + ) - resource, err := api.Database.GetWorkspaceResourceByID(ctx, workspaceAgent.ResourceID) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace resource.", - Detail: err.Error(), - }) - return - } - build, err := api.Database.GetWorkspaceBuildByJobID(ctx, resource.JobID) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace build.", - Detail: err.Error(), - }) - return - } - workspace, err := api.Database.GetWorkspaceByID(ctx, build.WorkspaceID) - if err != nil { - httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace.", - Detail: err.Error(), - }) - return - } - owner, err := api.Database.GetUserByID(ctx, workspace.OwnerID) + var eg errgroup.Group + eg.Go(func() (err error) { + dbApps, err = api.Database.GetWorkspaceAppsByAgentID(ctx, workspaceAgent.ID) + if err != nil && !xerrors.Is(err, sql.ErrNoRows) { + return err + } + return nil + }) + eg.Go(func() (err error) { + // nolint:gocritic // This is necessary to fetch agent scripts! + scripts, err = api.Database.GetWorkspaceAgentScriptsByAgentIDs(dbauthz.AsSystemRestricted(ctx), []uuid.UUID{workspaceAgent.ID}) + return err + }) + eg.Go(func() (err error) { + metadata, err = api.Database.GetWorkspaceAgentMetadata(ctx, workspaceAgent.ID) + return err + }) + eg.Go(func() (err error) { + resource, err = api.Database.GetWorkspaceResourceByID(ctx, workspaceAgent.ResourceID) + if err != nil { + return xerrors.Errorf("getting resource by id: %w", err) + } + build, err = api.Database.GetWorkspaceBuildByJobID(ctx, resource.JobID) + if err != nil { + return xerrors.Errorf("getting workspace build by job id: %w", err) + } + workspace, err = api.Database.GetWorkspaceByID(ctx, build.WorkspaceID) + if err != nil { + return xerrors.Errorf("getting workspace by id: %w", err) + } + owner, err = api.Database.GetUserByID(ctx, workspace.OwnerID) + if err != nil { + return xerrors.Errorf("getting workspace owner by id: %w", err) + } + return err + }) + err = eg.Wait() if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ - Message: "Internal error fetching workspace owner.", + Message: "Internal error fetching workspace agent manifest.", Detail: err.Error(), }) return @@ -200,17 +224,14 @@ func (api *API) workspaceAgentManifest(rw http.ResponseWriter, r *http.Request) httpapi.Write(ctx, rw, http.StatusOK, agentsdk.Manifest{ AgentID: apiAgent.ID, Apps: convertApps(dbApps, workspaceAgent, owner, workspace), + Scripts: convertScripts(scripts), DERPMap: api.DERPMap(), DERPForceWebSockets: api.DeploymentValues.DERP.Config.ForceWebSockets.Value(), GitAuthConfigs: len(api.GitAuthConfigs), EnvironmentVariables: apiAgent.EnvironmentVariables, - StartupScript: apiAgent.StartupScript, Directory: apiAgent.Directory, VSCodePortProxyURI: vscodeProxyURI, MOTDFile: workspaceAgent.MOTDFile, - StartupScriptTimeout: time.Duration(apiAgent.StartupScriptTimeoutSeconds) * time.Second, - ShutdownScript: apiAgent.ShutdownScript, - ShutdownScriptTimeout: time.Duration(apiAgent.ShutdownScriptTimeoutSeconds) * time.Second, DisableDirectConnections: api.DeploymentValues.DERP.Config.BlockDirect.Value(), Metadata: convertWorkspaceAgentMetadataDesc(metadata), }) @@ -230,7 +251,7 @@ func (api *API) postWorkspaceAgentStartup(rw http.ResponseWriter, r *http.Reques ctx := r.Context() workspaceAgent := httpmw.WorkspaceAgent(r) apiAgent, err := convertWorkspaceAgent( - api.DERPMap(), *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout, + api.DERPMap(), *api.TailnetCoordinator.Load(), workspaceAgent, nil, nil, nil, api.AgentInactiveDisconnectTimeout, api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), ) if err != nil { @@ -321,13 +342,37 @@ func (api *API) patchWorkspaceAgentLogs(rw http.ResponseWriter, r *http.Request) }) return } - createdAt := make([]time.Time, 0) + // This is to support the legacy API where the log source ID was + // not provided in the request body. We default to the external + // log source in this case. + if req.LogSourceID == uuid.Nil { + // Use the external log source + externalSources, err := api.Database.InsertWorkspaceAgentLogSources(ctx, database.InsertWorkspaceAgentLogSourcesParams{ + WorkspaceAgentID: workspaceAgent.ID, + CreatedAt: dbtime.Now(), + ID: []uuid.UUID{agentsdk.ExternalLogSourceID}, + DisplayName: []string{"External"}, + Icon: []string{"/emojis/1f310.png"}, + }) + if database.IsUniqueViolation(err, database.UniqueWorkspaceAgentLogSourcesPkey) { + err = nil + req.LogSourceID = agentsdk.ExternalLogSourceID + } + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Failed to create external log source.", + Detail: err.Error(), + }) + return + } + if len(externalSources) == 1 { + req.LogSourceID = externalSources[0].ID + } + } output := make([]string, 0) level := make([]database.LogLevel, 0) - source := make([]database.WorkspaceAgentLogSource, 0) outputLength := 0 for _, logEntry := range req.Logs { - createdAt = append(createdAt, logEntry.CreatedAt) output = append(output, logEntry.Output) outputLength += len(logEntry.Output) if logEntry.Level == "" { @@ -343,28 +388,14 @@ func (api *API) patchWorkspaceAgentLogs(rw http.ResponseWriter, r *http.Request) return } level = append(level, parsedLevel) - - if logEntry.Source == "" { - // Default to "startup_script" to support older agents that didn't have the source field. - logEntry.Source = codersdk.WorkspaceAgentLogSourceStartupScript - } - parsedSource := database.WorkspaceAgentLogSource(logEntry.Source) - if !parsedSource.Valid() { - httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ - Message: "Invalid log source provided.", - Detail: fmt.Sprintf("invalid log source: %q", logEntry.Source), - }) - return - } - source = append(source, parsedSource) } logs, err := api.Database.InsertWorkspaceAgentLogs(ctx, database.InsertWorkspaceAgentLogsParams{ AgentID: workspaceAgent.ID, - CreatedAt: createdAt, + CreatedAt: dbtime.Now(), Output: output, Level: level, - Source: source, + LogSourceID: req.LogSourceID, OutputLength: int32(outputLength), }) if err != nil { @@ -734,7 +765,7 @@ func (api *API) workspaceAgentListeningPorts(rw http.ResponseWriter, r *http.Req workspaceAgent := httpmw.WorkspaceAgentParam(r) apiAgent, err := convertWorkspaceAgent( - api.DERPMap(), *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout, + api.DERPMap(), *api.TailnetCoordinator.Load(), workspaceAgent, nil, nil, nil, api.AgentInactiveDisconnectTimeout, api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), ) if err != nil { @@ -1403,6 +1434,37 @@ func convertApps(dbApps []database.WorkspaceApp, agent database.WorkspaceAgent, return apps } +func convertLogSources(dbLogSources []database.WorkspaceAgentLogSource) []codersdk.WorkspaceAgentLogSource { + logSources := make([]codersdk.WorkspaceAgentLogSource, 0) + for _, dbLogSource := range dbLogSources { + logSources = append(logSources, codersdk.WorkspaceAgentLogSource{ + ID: dbLogSource.ID, + DisplayName: dbLogSource.DisplayName, + WorkspaceAgentID: dbLogSource.WorkspaceAgentID, + CreatedAt: dbLogSource.CreatedAt, + Icon: dbLogSource.Icon, + }) + } + return logSources +} + +func convertScripts(dbScripts []database.WorkspaceAgentScript) []codersdk.WorkspaceAgentScript { + scripts := make([]codersdk.WorkspaceAgentScript, 0) + for _, dbScript := range dbScripts { + scripts = append(scripts, codersdk.WorkspaceAgentScript{ + LogPath: dbScript.LogPath, + LogSourceID: dbScript.LogSourceID, + Script: dbScript.Script, + Cron: dbScript.Cron, + RunOnStart: dbScript.RunOnStart, + RunOnStop: dbScript.RunOnStop, + StartBlocksLogin: dbScript.StartBlocksLogin, + Timeout: time.Duration(dbScript.TimeoutSeconds) * time.Second, + }) + } + return scripts +} + func convertWorkspaceAgentMetadataDesc(mds []database.WorkspaceAgentMetadatum) []codersdk.WorkspaceAgentMetadataDescription { metadata := make([]codersdk.WorkspaceAgentMetadataDescription, 0) for _, datum := range mds { @@ -1417,7 +1479,10 @@ func convertWorkspaceAgentMetadataDesc(mds []database.WorkspaceAgentMetadatum) [ return metadata } -func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.Coordinator, dbAgent database.WorkspaceAgent, apps []codersdk.WorkspaceApp, agentInactiveDisconnectTimeout time.Duration, agentFallbackTroubleshootingURL string) (codersdk.WorkspaceAgent, error) { +func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.Coordinator, + dbAgent database.WorkspaceAgent, apps []codersdk.WorkspaceApp, scripts []codersdk.WorkspaceAgentScript, logSources []codersdk.WorkspaceAgentLogSource, + agentInactiveDisconnectTimeout time.Duration, agentFallbackTroubleshootingURL string, +) (codersdk.WorkspaceAgent, error) { var envs map[string]string if dbAgent.EnvironmentVariables.Valid { err := json.Unmarshal(dbAgent.EnvironmentVariables.RawMessage, &envs) @@ -1434,33 +1499,41 @@ func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.Coordin subsystems[i] = codersdk.AgentSubsystem(subsystem) } + legacyStartupScriptBehavior := codersdk.WorkspaceAgentStartupScriptBehaviorNonBlocking + for _, script := range scripts { + if !script.RunOnStart { + continue + } + if !script.StartBlocksLogin { + continue + } + legacyStartupScriptBehavior = codersdk.WorkspaceAgentStartupScriptBehaviorBlocking + } + 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, - StartupScriptBehavior: codersdk.WorkspaceAgentStartupScriptBehavior(dbAgent.StartupScriptBehavior), - StartupScriptTimeoutSeconds: dbAgent.StartupScriptTimeoutSeconds, - LogsLength: dbAgent.LogsLength, - LogsOverflowed: dbAgent.LogsOverflowed, - Version: dbAgent.Version, - EnvironmentVariables: envs, - Directory: dbAgent.Directory, - ExpandedDirectory: dbAgent.ExpandedDirectory, - Apps: apps, - ConnectionTimeoutSeconds: dbAgent.ConnectionTimeoutSeconds, - TroubleshootingURL: troubleshootingURL, - LifecycleState: codersdk.WorkspaceAgentLifecycle(dbAgent.LifecycleState), - LoginBeforeReady: dbAgent.StartupScriptBehavior != database.StartupScriptBehaviorBlocking, - ShutdownScript: dbAgent.ShutdownScript.String, - ShutdownScriptTimeoutSeconds: dbAgent.ShutdownScriptTimeoutSeconds, - Subsystems: subsystems, - DisplayApps: convertDisplayApps(dbAgent.DisplayApps), + 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, + Scripts: scripts, + StartupScriptBehavior: legacyStartupScriptBehavior, + LogsLength: dbAgent.LogsLength, + LogsOverflowed: dbAgent.LogsOverflowed, + LogSources: logSources, + Version: dbAgent.Version, + EnvironmentVariables: envs, + Directory: dbAgent.Directory, + ExpandedDirectory: dbAgent.ExpandedDirectory, + Apps: apps, + ConnectionTimeoutSeconds: dbAgent.ConnectionTimeoutSeconds, + TroubleshootingURL: troubleshootingURL, + LifecycleState: codersdk.WorkspaceAgentLifecycle(dbAgent.LifecycleState), + Subsystems: subsystems, + DisplayApps: convertDisplayApps(dbAgent.DisplayApps), } node := coordinator.Node(dbAgent.ID) if node != nil { @@ -2327,6 +2400,7 @@ func convertWorkspaceAgentLog(logEntry database.WorkspaceAgentLog) codersdk.Work CreatedAt: logEntry.CreatedAt, Output: logEntry.Output, Level: codersdk.LogLevel(logEntry.Level), + SourceID: logEntry.LogSourceID, } } diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 1a0e5e2cd7b54..4547588ecddaf 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -268,7 +268,7 @@ func TestWorkspaceAgent(t *testing.T) { }) } -func TestWorkspaceAgentStartupLogs(t *testing.T) { +func TestWorkspaceAgentLogs(t *testing.T) { t.Parallel() t.Run("Success", func(t *testing.T) { t.Parallel() diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index 947f2ec8774a6..998bf37ce157b 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -12,6 +12,7 @@ import ( "github.com/go-chi/chi/v5" "github.com/google/uuid" "golang.org/x/exp/slices" + "golang.org/x/sync/errgroup" "golang.org/x/xerrors" "cdr.dev/slog" @@ -77,6 +78,8 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) { data.metadata, data.agents, data.apps, + data.scripts, + data.logSources, data.templateVersions[0], ) if err != nil { @@ -191,6 +194,8 @@ func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { data.metadata, data.agents, data.apps, + data.scripts, + data.logSources, data.templateVersions, ) if err != nil { @@ -279,6 +284,8 @@ func (api *API) workspaceBuildByBuildNumber(rw http.ResponseWriter, r *http.Requ data.metadata, data.agents, data.apps, + data.scripts, + data.logSources, data.templateVersions[0], ) if err != nil { @@ -404,6 +411,8 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { []database.WorkspaceResourceMetadatum{}, []database.WorkspaceAgent{}, []database.WorkspaceApp{}, + []database.WorkspaceAgentScript{}, + []database.WorkspaceAgentLogSource{}, database.TemplateVersion{}, ) if err != nil { @@ -637,6 +646,8 @@ type workspaceBuildsData struct { metadata []database.WorkspaceResourceMetadatum agents []database.WorkspaceAgent apps []database.WorkspaceApp + scripts []database.WorkspaceAgentScript + logSources []database.WorkspaceAgentLogSource } func (api *API) workspaceBuildsData(ctx context.Context, workspaces []database.Workspace, workspaceBuilds []database.WorkspaceBuild) (workspaceBuildsData, error) { @@ -715,10 +726,31 @@ func (api *API) workspaceBuildsData(ctx context.Context, workspaces []database.W agentIDs = append(agentIDs, agent.ID) } - // nolint:gocritic // Getting workspace apps by agent IDs is a system function. - apps, err := api.Database.GetWorkspaceAppsByAgentIDs(dbauthz.AsSystemRestricted(ctx), agentIDs) - if err != nil && !errors.Is(err, sql.ErrNoRows) { - return workspaceBuildsData{}, xerrors.Errorf("fetching workspace apps: %w", err) + var ( + apps []database.WorkspaceApp + scripts []database.WorkspaceAgentScript + logSources []database.WorkspaceAgentLogSource + ) + + var eg errgroup.Group + eg.Go(func() (err error) { + // nolint:gocritic // Getting workspace apps by agent IDs is a system function. + apps, err = api.Database.GetWorkspaceAppsByAgentIDs(dbauthz.AsSystemRestricted(ctx), agentIDs) + return err + }) + eg.Go(func() (err error) { + // nolint:gocritic // Getting workspace scripts by agent IDs is a system function. + scripts, err = api.Database.GetWorkspaceAgentScriptsByAgentIDs(dbauthz.AsSystemRestricted(ctx), agentIDs) + return err + }) + eg.Go(func() error { + // nolint:gocritic // Getting workspace agent log sources by agent IDs is a system function. + logSources, err = api.Database.GetWorkspaceAgentLogSourcesByAgentIDs(dbauthz.AsSystemRestricted(ctx), agentIDs) + return err + }) + err = eg.Wait() + if err != nil { + return workspaceBuildsData{}, err } return workspaceBuildsData{ @@ -729,6 +761,8 @@ func (api *API) workspaceBuildsData(ctx context.Context, workspaces []database.W metadata: metadata, agents: agents, apps: apps, + scripts: scripts, + logSources: logSources, }, nil } @@ -741,6 +775,8 @@ func (api *API) convertWorkspaceBuilds( resourceMetadata []database.WorkspaceResourceMetadatum, resourceAgents []database.WorkspaceAgent, agentApps []database.WorkspaceApp, + agentScripts []database.WorkspaceAgentScript, + agentLogSources []database.WorkspaceAgentLogSource, templateVersions []database.TemplateVersion, ) ([]codersdk.WorkspaceBuild, error) { workspaceByID := map[uuid.UUID]database.Workspace{} @@ -781,6 +817,8 @@ func (api *API) convertWorkspaceBuilds( resourceMetadata, resourceAgents, agentApps, + agentScripts, + agentLogSources, templateVersion, ) if err != nil { @@ -802,6 +840,8 @@ func (api *API) convertWorkspaceBuild( resourceMetadata []database.WorkspaceResourceMetadatum, resourceAgents []database.WorkspaceAgent, agentApps []database.WorkspaceApp, + agentScripts []database.WorkspaceAgentScript, + agentLogSources []database.WorkspaceAgentLogSource, templateVersion database.TemplateVersion, ) (codersdk.WorkspaceBuild, error) { userByID := map[uuid.UUID]database.User{} @@ -824,6 +864,14 @@ func (api *API) convertWorkspaceBuild( for _, app := range agentApps { appsByAgentID[app.AgentID] = append(appsByAgentID[app.AgentID], app) } + scriptsByAgentID := map[uuid.UUID][]database.WorkspaceAgentScript{} + for _, script := range agentScripts { + scriptsByAgentID[script.WorkspaceAgentID] = append(scriptsByAgentID[script.WorkspaceAgentID], script) + } + logSourcesByAgentID := map[uuid.UUID][]database.WorkspaceAgentLogSource{} + for _, logSource := range agentLogSources { + logSourcesByAgentID[logSource.WorkspaceAgentID] = append(logSourcesByAgentID[logSource.WorkspaceAgentID], logSource) + } owner, exists := userByID[workspace.OwnerID] if !exists { @@ -837,8 +885,10 @@ func (api *API) convertWorkspaceBuild( apiAgents := make([]codersdk.WorkspaceAgent, 0) for _, agent := range agents { apps := appsByAgentID[agent.ID] + scripts := scriptsByAgentID[agent.ID] + logSources := logSourcesByAgentID[agent.ID] apiAgent, err := convertWorkspaceAgent( - api.DERPMap(), *api.TailnetCoordinator.Load(), agent, convertApps(apps, agent, owner, workspace), api.AgentInactiveDisconnectTimeout, + api.DERPMap(), *api.TailnetCoordinator.Load(), agent, convertApps(apps, agent, owner, workspace), convertScripts(scripts), convertLogSources(logSources), api.AgentInactiveDisconnectTimeout, api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), ) if err != nil { diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 7cfd333a8fcc0..6aa08b991705d 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -542,6 +542,8 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req []database.WorkspaceResourceMetadatum{}, []database.WorkspaceAgent{}, []database.WorkspaceApp{}, + []database.WorkspaceAgentScript{}, + []database.WorkspaceAgentLogSource{}, database.TemplateVersion{}, ) if err != nil { @@ -1125,6 +1127,8 @@ func (api *API) workspaceData(ctx context.Context, workspaces []database.Workspa data.metadata, data.agents, data.apps, + data.scripts, + data.logSources, data.templateVersions, ) if err != nil { diff --git a/codersdk/agentsdk/agentsdk.go b/codersdk/agentsdk/agentsdk.go index fb1b2f497410b..c0750b1a22999 100644 --- a/codersdk/agentsdk/agentsdk.go +++ b/codersdk/agentsdk/agentsdk.go @@ -23,6 +23,13 @@ import ( "github.com/coder/retry" ) +// ExternalLogSourceID is the statically-defined ID of a log-source that +// appears as "External" in the dashboard. +// +// This is to support legacy API-consumers that do not create their own +// log-source. This should be removed in the future. +var ExternalLogSourceID = uuid.MustParse("3b579bf4-1ed8-4b99-87a8-e9a1e3410410") + // New returns a client that is used to interact with the // Coder API from a workspace agent. func New(serverURL *url.URL) *Client { @@ -91,14 +98,21 @@ type Manifest struct { DERPMap *tailcfg.DERPMap `json:"derpmap"` DERPForceWebSockets bool `json:"derp_force_websockets"` EnvironmentVariables map[string]string `json:"environment_variables"` - StartupScript string `json:"startup_script"` - StartupScriptTimeout time.Duration `json:"startup_script_timeout"` Directory string `json:"directory"` MOTDFile string `json:"motd_file"` - ShutdownScript string `json:"shutdown_script"` - ShutdownScriptTimeout time.Duration `json:"shutdown_script_timeout"` DisableDirectConnections bool `json:"disable_direct_connections"` Metadata []codersdk.WorkspaceAgentMetadataDescription `json:"metadata"` + Scripts []codersdk.WorkspaceAgentScript `json:"scripts"` +} + +type LogSource struct { + ID uuid.UUID `json:"id"` + DisplayName string `json:"display_name"` + Icon string `json:"icon"` +} + +type Script struct { + Script string `json:"script"` } // Manifest fetches manifest for the currently authenticated workspace agent. @@ -631,14 +645,14 @@ func (c *Client) PostStartup(ctx context.Context, req PostStartupRequest) error } type Log struct { - CreatedAt time.Time `json:"created_at"` - Output string `json:"output"` - Level codersdk.LogLevel `json:"level"` - Source codersdk.WorkspaceAgentLogSource `json:"source"` + CreatedAt time.Time `json:"created_at"` + Output string `json:"output"` + Level codersdk.LogLevel `json:"level"` } type PatchLogs struct { - Logs []Log `json:"logs"` + LogSourceID uuid.UUID `json:"log_source_id"` + Logs []Log `json:"logs"` } // PatchLogs writes log messages to the agent startup script. @@ -655,6 +669,29 @@ func (c *Client) PatchLogs(ctx context.Context, req PatchLogs) error { return nil } +type PostLogSource struct { + // ID is a unique identifier for the log source. + // It is scoped to a workspace agent, and can be statically + // defined inside code to prevent duplicate sources from being + // created for the same agent. + ID uuid.UUID `json:"id"` + DisplayName string `json:"display_name"` + Icon string `json:"icon"` +} + +func (c *Client) PostLogSource(ctx context.Context, req PostLogSource) (codersdk.WorkspaceAgentLogSource, error) { + res, err := c.SDK.Request(ctx, http.MethodPost, "/api/v2/workspaceagents/me/log-source", req) + if err != nil { + return codersdk.WorkspaceAgentLogSource{}, err + } + defer res.Body.Close() + if res.StatusCode != http.StatusCreated { + return codersdk.WorkspaceAgentLogSource{}, codersdk.ReadBodyAsError(res) + } + var logSource codersdk.WorkspaceAgentLogSource + return logSource, json.NewDecoder(res.Body).Decode(&logSource) +} + // GetServiceBanner relays the service banner config. func (c *Client) GetServiceBanner(ctx context.Context) (codersdk.ServiceBannerConfig, error) { res, err := c.SDK.Request(ctx, http.MethodGet, "/api/v2/appearance", nil) diff --git a/codersdk/agentsdk/logs.go b/codersdk/agentsdk/logs.go index 4b859070dbd94..8f300f4c3ea35 100644 --- a/codersdk/agentsdk/logs.go +++ b/codersdk/agentsdk/logs.go @@ -10,6 +10,8 @@ import ( "golang.org/x/xerrors" + "github.com/google/uuid" + "cdr.dev/slog" "github.com/coder/coder/v2/codersdk" "github.com/coder/retry" @@ -20,7 +22,7 @@ type startupLogsWriter struct { ctx context.Context send func(ctx context.Context, log ...Log) error level codersdk.LogLevel - source codersdk.WorkspaceAgentLogSource + source uuid.UUID } func (w *startupLogsWriter) Write(p []byte) (int, error) { @@ -44,7 +46,6 @@ func (w *startupLogsWriter) Write(p []byte) (int, error) { CreatedAt: time.Now().UTC(), // UTC, like dbtime.Now(). Level: w.level, Output: string(partial) + string(p[:nl-cr]), - Source: w.source, }) if err != nil { return n - len(p), err @@ -67,24 +68,20 @@ func (w *startupLogsWriter) Close() error { CreatedAt: time.Now().UTC(), // UTC, like dbtime.Now(). Level: w.level, Output: w.buf.String(), - Source: w.source, }) } return nil } -// StartupLogsWriter returns an io.WriteCloser that sends logs via the +// LogsWriter returns an io.WriteCloser that sends logs via the // provided sender. The sender is expected to be non-blocking. Calling // Close flushes any remaining partially written log lines but is -// otherwise no-op. If the context passed to StartupLogsWriter is +// otherwise no-op. If the context passed to LogsWriter is // canceled, any remaining logs will be discarded. // // Neither Write nor Close is safe for concurrent use and must be used // by a single goroutine. -func StartupLogsWriter(ctx context.Context, sender func(ctx context.Context, log ...Log) error, source codersdk.WorkspaceAgentLogSource, level codersdk.LogLevel) io.WriteCloser { - if source == "" { - source = codersdk.WorkspaceAgentLogSourceExternal - } +func LogsWriter(ctx context.Context, sender func(ctx context.Context, log ...Log) error, source uuid.UUID, level codersdk.LogLevel) io.WriteCloser { return &startupLogsWriter{ ctx: ctx, send: sender, @@ -98,7 +95,7 @@ func StartupLogsWriter(ctx context.Context, sender func(ctx context.Context, log // has been called. Calling sendLog concurrently is not supported. If // the context passed to flushAndClose is canceled, any remaining logs // will be discarded. -func LogsSender(patchLogs func(ctx context.Context, req PatchLogs) error, logger slog.Logger) (sendLog func(ctx context.Context, log ...Log) error, flushAndClose func(context.Context) error) { +func LogsSender(sourceID uuid.UUID, patchLogs func(ctx context.Context, req PatchLogs) error, logger slog.Logger) (sendLog func(ctx context.Context, log ...Log) error, flushAndClose func(context.Context) error) { // The main context is used to close the sender goroutine and cancel // any outbound requests to the API. The shutdown context is used to // signal the sender goroutine to flush logs and then exit. @@ -158,7 +155,8 @@ func LogsSender(patchLogs func(ctx context.Context, req PatchLogs) error, logger // shutdown. for r := retry.New(time.Second, 5*time.Second); r.Wait(ctx); { err := patchLogs(ctx, PatchLogs{ - Logs: backlog, + Logs: backlog, + LogSourceID: sourceID, }) if err == nil { break diff --git a/codersdk/agentsdk/logs_test.go b/codersdk/agentsdk/logs_test.go index 7c1d7b0bf814c..90e4ff42107d7 100644 --- a/codersdk/agentsdk/logs_test.go +++ b/codersdk/agentsdk/logs_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/exp/slices" @@ -39,12 +40,10 @@ func TestStartupLogsWriter_Write(t *testing.T) { ctx: context.Background(), level: codersdk.LogLevelInfo, writes: []string{"hello world\n"}, - source: codersdk.WorkspaceAgentLogSourceShutdownScript, want: []agentsdk.Log{ { Level: codersdk.LogLevelInfo, Output: "hello world", - Source: codersdk.WorkspaceAgentLogSourceShutdownScript, }, }, }, @@ -57,12 +56,10 @@ func TestStartupLogsWriter_Write(t *testing.T) { { Level: codersdk.LogLevelInfo, Output: "hello world", - Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "goodbye world", - Source: codersdk.WorkspaceAgentLogSourceExternal, }, }, }, @@ -75,32 +72,26 @@ func TestStartupLogsWriter_Write(t *testing.T) { { Level: codersdk.LogLevelInfo, Output: "", - Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "", - Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "hello world", - Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "", - Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "", - Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "goodbye world", - Source: codersdk.WorkspaceAgentLogSourceExternal, }, }, }, @@ -113,7 +104,6 @@ func TestStartupLogsWriter_Write(t *testing.T) { { Level: codersdk.LogLevelInfo, Output: "hello world", - Source: codersdk.WorkspaceAgentLogSourceExternal, }, }, }, @@ -127,12 +117,10 @@ func TestStartupLogsWriter_Write(t *testing.T) { { Level: codersdk.LogLevelInfo, Output: "hello world", - Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "goodbye world", - Source: codersdk.WorkspaceAgentLogSourceExternal, }, }, }, @@ -145,12 +133,10 @@ func TestStartupLogsWriter_Write(t *testing.T) { { Level: codersdk.LogLevelInfo, Output: "hello world", - Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "goodbye world", - Source: codersdk.WorkspaceAgentLogSourceExternal, }, }, }, @@ -163,17 +149,14 @@ func TestStartupLogsWriter_Write(t *testing.T) { { Level: codersdk.LogLevelInfo, Output: "hello world", - Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "\r", - Source: codersdk.WorkspaceAgentLogSourceExternal, }, { Level: codersdk.LogLevelInfo, Output: "goodbye world", - Source: codersdk.WorkspaceAgentLogSourceExternal, }, }, }, @@ -201,7 +184,7 @@ func TestStartupLogsWriter_Write(t *testing.T) { got = append(got, log...) return nil } - w := agentsdk.StartupLogsWriter(tt.ctx, send, tt.source, tt.level) + w := agentsdk.LogsWriter(tt.ctx, send, uuid.New(), tt.level) for _, s := range tt.writes { _, err := w.Write([]byte(s)) if err != nil { @@ -291,7 +274,7 @@ func TestStartupLogsSender(t *testing.T) { return nil } - sendLog, flushAndClose := agentsdk.LogsSender(patchLogs, slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + sendLog, flushAndClose := agentsdk.LogsSender(uuid.New(), patchLogs, slogtest.Make(t, nil).Leveled(slog.LevelDebug)) defer func() { err := flushAndClose(ctx) require.NoError(t, err) @@ -330,7 +313,7 @@ func TestStartupLogsSender(t *testing.T) { return nil } - sendLog, flushAndClose := agentsdk.LogsSender(patchLogs, slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + sendLog, flushAndClose := agentsdk.LogsSender(uuid.New(), patchLogs, slogtest.Make(t, nil).Leveled(slog.LevelDebug)) defer func() { _ = flushAndClose(ctx) }() @@ -361,7 +344,7 @@ func TestStartupLogsSender(t *testing.T) { return nil } - sendLog, flushAndClose := agentsdk.LogsSender(patchLogs, slogtest.Make(t, nil).Leveled(slog.LevelDebug)) + sendLog, flushAndClose := agentsdk.LogsSender(uuid.New(), patchLogs, slogtest.Make(t, nil).Leveled(slog.LevelDebug)) defer func() { _ = flushAndClose(ctx) }() diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 7f5caac54f657..367fafa087a6d 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -58,7 +58,7 @@ const ( // Starting returns true if the agent is in the process of starting. func (l WorkspaceAgentLifecycle) Starting() bool { switch l { - case WorkspaceAgentLifecycleCreated, WorkspaceAgentLifecycleStarting, WorkspaceAgentLifecycleStartTimeout: + case WorkspaceAgentLifecycleCreated, WorkspaceAgentLifecycleStarting: return true default: return false @@ -101,6 +101,7 @@ var WorkspaceAgentLifecycleOrder = []WorkspaceAgentLifecycle{ // ready (can be overridden). // // Presently, non-blocking is the default, but this may change in the future. +// Deprecated: `coder_script` allows configuration on a per-script basis. type WorkspaceAgentStartupScriptBehavior string const ( @@ -144,42 +145,61 @@ const ( ) 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"` - StartedAt *time.Time `json:"started_at,omitempty" format:"date-time"` - ReadyAt *time.Time `json:"ready_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"` - StartupScriptBehavior WorkspaceAgentStartupScriptBehavior `json:"startup_script_behavior"` - StartupScriptTimeoutSeconds int32 `json:"startup_script_timeout_seconds"` // 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. - LogsLength int32 `json:"logs_length"` - LogsOverflowed bool `json:"logs_overflowed"` - Directory string `json:"directory,omitempty"` - ExpandedDirectory string `json:"expanded_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"` + StartedAt *time.Time `json:"started_at,omitempty" format:"date-time"` + ReadyAt *time.Time `json:"ready_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"` + LogsLength int32 `json:"logs_length"` + LogsOverflowed bool `json:"logs_overflowed"` + Directory string `json:"directory,omitempty"` + ExpandedDirectory string `json:"expanded_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"` - // Deprecated: Use StartupScriptBehavior instead. - LoginBeforeReady bool `json:"login_before_ready"` - ShutdownScript string `json:"shutdown_script,omitempty"` - ShutdownScriptTimeoutSeconds int32 `json:"shutdown_script_timeout_seconds"` - Subsystems []AgentSubsystem `json:"subsystems"` - Health WorkspaceAgentHealth `json:"health"` // Health reports the health of the agent. - DisplayApps []DisplayApp `json:"display_apps"` + DERPLatency map[string]DERPRegion `json:"latency,omitempty"` + ConnectionTimeoutSeconds int32 `json:"connection_timeout_seconds"` + TroubleshootingURL string `json:"troubleshooting_url"` + Subsystems []AgentSubsystem `json:"subsystems"` + Health WorkspaceAgentHealth `json:"health"` // Health reports the health of the agent. + DisplayApps []DisplayApp `json:"display_apps"` + LogSources []WorkspaceAgentLogSource `json:"log_sources"` + Scripts []WorkspaceAgentScript `json:"scripts"` + + // StartupScriptBehavior is a legacy field that is deprecated in favor + // of the `coder_script` resource. It's only referenced by old clients. + // Deprecated: Remove in the future! + StartupScriptBehavior WorkspaceAgentStartupScriptBehavior `json:"startup_script_behavior"` +} + +type WorkspaceAgentLogSource struct { + WorkspaceAgentID uuid.UUID `json:"workspace_agent_id" format:"uuid"` + ID uuid.UUID `json:"id" format:"uuid"` + CreatedAt time.Time `json:"created_at" format:"date-time"` + DisplayName string `json:"display_name"` + Icon string `json:"icon"` +} + +type WorkspaceAgentScript struct { + LogSourceID uuid.UUID `json:"log_source_id" format:"uuid"` + LogPath string `json:"log_path"` + Script string `json:"script"` + Cron string `json:"cron"` + RunOnStart bool `json:"run_on_start"` + RunOnStop bool `json:"run_on_stop"` + StartBlocksLogin bool `json:"start_blocks_login"` + Timeout time.Duration `json:"timeout"` } type WorkspaceAgentHealth struct { @@ -530,13 +550,6 @@ func (c *Client) WorkspaceAgent(ctx context.Context, id uuid.UUID) (WorkspaceAge if err != nil { return WorkspaceAgent{}, err } - // Backwards compatibility for cases where the API is older then the client. - if workspaceAgent.StartupScriptBehavior == "" { - workspaceAgent.StartupScriptBehavior = WorkspaceAgentStartupScriptBehaviorNonBlocking - if !workspaceAgent.LoginBeforeReady { - workspaceAgent.StartupScriptBehavior = WorkspaceAgentStartupScriptBehaviorBlocking - } - } return workspaceAgent, nil } @@ -762,6 +775,7 @@ type WorkspaceAgentLog struct { CreatedAt time.Time `json:"created_at" format:"date-time"` Output string `json:"output"` Level LogLevel `json:"level"` + SourceID uuid.UUID `json:"source_id" format:"uuid"` } type AgentSubsystem string @@ -780,14 +794,3 @@ func (s AgentSubsystem) Valid() bool { return false } } - -type WorkspaceAgentLogSource string - -const ( - WorkspaceAgentLogSourceStartupScript WorkspaceAgentLogSource = "startup_script" - WorkspaceAgentLogSourceShutdownScript WorkspaceAgentLogSource = "shutdown_script" - WorkspaceAgentLogSourceKubernetes WorkspaceAgentLogSource = "kubernetes" - WorkspaceAgentLogSourceEnvbox WorkspaceAgentLogSource = "envbox" - WorkspaceAgentLogSourceEnvbuilder WorkspaceAgentLogSource = "envbuilder" - WorkspaceAgentLogSourceExternal WorkspaceAgentLogSource = "external" -) diff --git a/docs/api/agents.md b/docs/api/agents.md index 5edaeb5604ebc..326f523415f41 100644 --- a/docs/api/agents.md +++ b/docs/api/agents.md @@ -311,12 +311,12 @@ curl -X PATCH http://coder-server:8080/api/v2/workspaceagents/me/logs \ ```json { + "log_source_id": "string", "logs": [ { "created_at": "string", "level": "trace", - "output": "string", - "source": "startup_script" + "output": "string" } ] } @@ -470,10 +470,18 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/me/manifest \ } ], "motd_file": "string", - "shutdown_script": "string", - "shutdown_script_timeout": 0, - "startup_script": "string", - "startup_script_timeout": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "vscode_port_proxy_uri": "string" } ``` @@ -576,12 +584,12 @@ curl -X PATCH http://coder-server:8080/api/v2/workspaceagents/me/startup-logs \ ```json { + "log_source_id": "string", "logs": [ { "created_at": "string", "level": "trace", - "output": "string", - "source": "startup_script" + "output": "string" } ] } @@ -693,19 +701,35 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent} \ } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", @@ -921,7 +945,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/log "created_at": "2019-08-24T14:15:22Z", "id": 0, "level": "trace", - "output": "string" + "output": "string", + "source_id": "ae50a35c-df42-4eff-ba26-f8bc28d2af81" } ] ``` @@ -943,6 +968,7 @@ Status Code **200** | `» id` | integer | false | | | | `» level` | [codersdk.LogLevel](schemas.md#codersdkloglevel) | false | | | | `» output` | string | false | | | +| `» source_id` | string(uuid) | false | | | #### Enumerated Values @@ -1015,7 +1041,8 @@ curl -X GET http://coder-server:8080/api/v2/workspaceagents/{workspaceagent}/sta "created_at": "2019-08-24T14:15:22Z", "id": 0, "level": "trace", - "output": "string" + "output": "string", + "source_id": "ae50a35c-df42-4eff-ba26-f8bc28d2af81" } ] ``` @@ -1037,6 +1064,7 @@ Status Code **200** | `» id` | integer | false | | | | `» level` | [codersdk.LogLevel](schemas.md#codersdkloglevel) | false | | | | `» output` | string | false | | | +| `» source_id` | string(uuid) | false | | | #### Enumerated Values diff --git a/docs/api/builds.md b/docs/api/builds.md index 46a825d92c0bc..a1e8f25a6e69d 100644 --- a/docs/api/builds.md +++ b/docs/api/builds.md @@ -108,19 +108,35 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", @@ -272,19 +288,35 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild} \ } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", @@ -575,19 +607,35 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/res } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", @@ -625,78 +673,88 @@ 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. | -| `»»» subdomain_name` | string | false | | Subdomain name is the application domain exposed on the `coder server`. | -| `»»» 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 | | | -| `»» display_apps` | array | false | | | -| `»» environment_variables` | object | false | | | -| `»»» [any property]` | string | false | | | -| `»» expanded_directory` | string | false | | | -| `»» first_connected_at` | string(date-time) | false | | | -| `»» health` | [codersdk.WorkspaceAgentHealth](schemas.md#codersdkworkspaceagenthealth) | false | | Health reports the health of the agent. | -| `»»» healthy` | boolean | false | | Healthy is true if the agent is healthy. | -| `»»» reason` | string | false | | Reason is a human-readable explanation of the agent's health. It is empty if Healthy is true. | -| `»» id` | string(uuid) | false | | | -| `»» instance_id` | string | false | | | -| `»» last_connected_at` | string(date-time) | false | | | -| `»» latency` | object | false | | Latency is mapped by region name (e.g. "New York City", "Seattle"). | -| `»»» [any property]` | [codersdk.DERPRegion](schemas.md#codersdkderpregion) | false | | | -| `»»»» latency_ms` | number | false | | | -| `»»»» preferred` | boolean | false | | | -| `»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | | -| `»» login_before_ready` | boolean | false | | Deprecated: Use StartupScriptBehavior instead. | -| `»» logs_length` | integer | false | | | -| `»» logs_overflowed` | boolean | false | | | -| `»» name` | string | false | | | -| `»» operating_system` | string | false | | | -| `»» ready_at` | string(date-time) | false | | | -| `»» resource_id` | string(uuid) | false | | | -| `»» shutdown_script` | string | false | | | -| `»» shutdown_script_timeout_seconds` | integer | false | | | -| `»» started_at` | string(date-time) | false | | | -| `»» startup_script` | string | false | | | -| `»» startup_script_behavior` | [codersdk.WorkspaceAgentStartupScriptBehavior](schemas.md#codersdkworkspaceagentstartupscriptbehavior) | 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 | | | -| `»» subsystems` | array | 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. | +| `»»» subdomain_name` | string | false | | Subdomain name is the application domain exposed on the `coder server`. | +| `»»» 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 | | | +| `»» display_apps` | array | false | | | +| `»» environment_variables` | object | false | | | +| `»»» [any property]` | string | false | | | +| `»» expanded_directory` | string | false | | | +| `»» first_connected_at` | string(date-time) | false | | | +| `»» health` | [codersdk.WorkspaceAgentHealth](schemas.md#codersdkworkspaceagenthealth) | false | | Health reports the health of the agent. | +| `»»» healthy` | boolean | false | | Healthy is true if the agent is healthy. | +| `»»» reason` | string | false | | Reason is a human-readable explanation of the agent's health. It is empty if Healthy is true. | +| `»» 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 | | | +| `»» log_sources` | array | false | | | +| `»»» created_at` | string(date-time) | false | | | +| `»»» display_name` | string | false | | | +| `»»» icon` | string | false | | | +| `»»» id` | string(uuid) | false | | | +| `»»» workspace_agent_id` | string(uuid) | false | | | +| `»» logs_length` | integer | false | | | +| `»» logs_overflowed` | boolean | false | | | +| `»» name` | string | false | | | +| `»» operating_system` | string | false | | | +| `»» ready_at` | string(date-time) | false | | | +| `»» resource_id` | string(uuid) | false | | | +| `»» scripts` | array | false | | | +| `»»» cron` | string | false | | | +| `»»» log_path` | string | false | | | +| `»»» log_source_id` | string(uuid) | false | | | +| `»»» run_on_start` | boolean | false | | | +| `»»» run_on_stop` | boolean | false | | | +| `»»» script` | string | false | | | +| `»»» start_blocks_login` | boolean | false | | | +| `»»» timeout` | integer | false | | | +| `»» started_at` | string(date-time) | false | | | +| `»» startup_script_behavior` | [codersdk.WorkspaceAgentStartupScriptBehavior](schemas.md#codersdkworkspaceagentstartupscriptbehavior) | false | | Startup script behavior is a legacy field that is deprecated in favor of the `coder_script` resource. It's only referenced by old clients. Deprecated: Remove in the future! | +| `»» status` | [codersdk.WorkspaceAgentStatus](schemas.md#codersdkworkspaceagentstatus) | false | | | +| `»» subsystems` | array | 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 @@ -836,19 +894,35 @@ curl -X GET http://coder-server:8080/api/v2/workspacebuilds/{workspacebuild}/sta } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", @@ -1005,19 +1079,35 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", @@ -1066,112 +1156,122 @@ 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 | | | -| `»» error_code` | [codersdk.JobErrorCode](schemas.md#codersdkjoberrorcode) | false | | | -| `»» file_id` | string(uuid) | false | | | -| `»» id` | string(uuid) | false | | | -| `»» queue_position` | integer | false | | | -| `»» queue_size` | integer | 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 | | | -| `» max_deadline` | string(date-time) | 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. | -| `»»»» subdomain_name` | string | false | | Subdomain name is the application domain exposed on the `coder server`. | -| `»»»» 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 | | | -| `»»» display_apps` | array | false | | | -| `»»» environment_variables` | object | false | | | -| `»»»» [any property]` | string | false | | | -| `»»» expanded_directory` | string | false | | | -| `»»» first_connected_at` | string(date-time) | false | | | -| `»»» health` | [codersdk.WorkspaceAgentHealth](schemas.md#codersdkworkspaceagenthealth) | false | | Health reports the health of the agent. | -| `»»»» healthy` | boolean | false | | Healthy is true if the agent is healthy. | -| `»»»» reason` | string | false | | Reason is a human-readable explanation of the agent's health. It is empty if Healthy is true. | -| `»»» id` | string(uuid) | false | | | -| `»»» instance_id` | string | false | | | -| `»»» last_connected_at` | string(date-time) | false | | | -| `»»» latency` | object | false | | Latency is mapped by region name (e.g. "New York City", "Seattle"). | -| `»»»» [any property]` | [codersdk.DERPRegion](schemas.md#codersdkderpregion) | false | | | -| `»»»»» latency_ms` | number | false | | | -| `»»»»» preferred` | boolean | false | | | -| `»»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | | -| `»»» login_before_ready` | boolean | false | | Deprecated: Use StartupScriptBehavior instead. | -| `»»» logs_length` | integer | false | | | -| `»»» logs_overflowed` | boolean | false | | | -| `»»» name` | string | false | | | -| `»»» operating_system` | string | false | | | -| `»»» ready_at` | string(date-time) | false | | | -| `»»» resource_id` | string(uuid) | false | | | -| `»»» shutdown_script` | string | false | | | -| `»»» shutdown_script_timeout_seconds` | integer | false | | | -| `»»» started_at` | string(date-time) | false | | | -| `»»» startup_script` | string | false | | | -| `»»» startup_script_behavior` | [codersdk.WorkspaceAgentStartupScriptBehavior](schemas.md#codersdkworkspaceagentstartupscriptbehavior) | 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 | | | -| `»»» subsystems` | array | 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 | | | +| `»» error_code` | [codersdk.JobErrorCode](schemas.md#codersdkjoberrorcode) | false | | | +| `»» file_id` | string(uuid) | false | | | +| `»» id` | string(uuid) | false | | | +| `»» queue_position` | integer | false | | | +| `»» queue_size` | integer | 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 | | | +| `» max_deadline` | string(date-time) | 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. | +| `»»»» subdomain_name` | string | false | | Subdomain name is the application domain exposed on the `coder server`. | +| `»»»» 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 | | | +| `»»» display_apps` | array | false | | | +| `»»» environment_variables` | object | false | | | +| `»»»» [any property]` | string | false | | | +| `»»» expanded_directory` | string | false | | | +| `»»» first_connected_at` | string(date-time) | false | | | +| `»»» health` | [codersdk.WorkspaceAgentHealth](schemas.md#codersdkworkspaceagenthealth) | false | | Health reports the health of the agent. | +| `»»»» healthy` | boolean | false | | Healthy is true if the agent is healthy. | +| `»»»» reason` | string | false | | Reason is a human-readable explanation of the agent's health. It is empty if Healthy is true. | +| `»»» 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 | | | +| `»»» log_sources` | array | false | | | +| `»»»» created_at` | string(date-time) | false | | | +| `»»»» display_name` | string | false | | | +| `»»»» icon` | string | false | | | +| `»»»» id` | string(uuid) | false | | | +| `»»»» workspace_agent_id` | string(uuid) | false | | | +| `»»» logs_length` | integer | false | | | +| `»»» logs_overflowed` | boolean | false | | | +| `»»» name` | string | false | | | +| `»»» operating_system` | string | false | | | +| `»»» ready_at` | string(date-time) | false | | | +| `»»» resource_id` | string(uuid) | false | | | +| `»»» scripts` | array | false | | | +| `»»»» cron` | string | false | | | +| `»»»» log_path` | string | false | | | +| `»»»» log_source_id` | string(uuid) | false | | | +| `»»»» run_on_start` | boolean | false | | | +| `»»»» run_on_stop` | boolean | false | | | +| `»»»» script` | string | false | | | +| `»»»» start_blocks_login` | boolean | false | | | +| `»»»» timeout` | integer | false | | | +| `»»» started_at` | string(date-time) | false | | | +| `»»» startup_script_behavior` | [codersdk.WorkspaceAgentStartupScriptBehavior](schemas.md#codersdkworkspaceagentstartupscriptbehavior) | false | | Startup script behavior is a legacy field that is deprecated in favor of the `coder_script` resource. It's only referenced by old clients. Deprecated: Remove in the future! | +| `»»» status` | [codersdk.WorkspaceAgentStatus](schemas.md#codersdkworkspaceagentstatus) | false | | | +| `»»» subsystems` | array | 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 @@ -1355,19 +1455,35 @@ curl -X POST http://coder-server:8080/api/v2/workspaces/{workspace}/builds \ } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", diff --git a/docs/api/schemas.md b/docs/api/schemas.md index e2546adcf8ad7..176c3bd4cac37 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -163,19 +163,17 @@ { "created_at": "string", "level": "trace", - "output": "string", - "source": "startup_script" + "output": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------ | -------------------------------------------------------------------- | -------- | ------------ | ----------- | -| `created_at` | string | false | | | -| `level` | [codersdk.LogLevel](#codersdkloglevel) | false | | | -| `output` | string | false | | | -| `source` | [codersdk.WorkspaceAgentLogSource](#codersdkworkspaceagentlogsource) | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------ | -------------------------------------- | -------- | ------------ | ----------- | +| `created_at` | string | false | | | +| `level` | [codersdk.LogLevel](#codersdkloglevel) | false | | | +| `output` | string | false | | | ## agentsdk.Manifest @@ -279,10 +277,18 @@ } ], "motd_file": "string", - "shutdown_script": "string", - "shutdown_script_timeout": 0, - "startup_script": "string", - "startup_script_timeout": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "vscode_port_proxy_uri": "string" } ``` @@ -302,22 +308,19 @@ | `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. | | `metadata` | array of [codersdk.WorkspaceAgentMetadataDescription](#codersdkworkspaceagentmetadatadescription) | false | | | | `motd_file` | string | false | | | -| `shutdown_script` | string | false | | | -| `shutdown_script_timeout` | integer | false | | | -| `startup_script` | string | false | | | -| `startup_script_timeout` | integer | false | | | +| `scripts` | array of [codersdk.WorkspaceAgentScript](#codersdkworkspaceagentscript) | false | | | | `vscode_port_proxy_uri` | string | false | | | ## agentsdk.PatchLogs ```json { + "log_source_id": "string", "logs": [ { "created_at": "string", "level": "trace", - "output": "string", - "source": "startup_script" + "output": "string" } ] } @@ -325,9 +328,10 @@ ### Properties -| Name | Type | Required | Restrictions | Description | -| ------ | ------------------------------------- | -------- | ------------ | ----------- | -| `logs` | array of [agentsdk.Log](#agentsdklog) | false | | | +| Name | Type | Required | Restrictions | Description | +| --------------- | ------------------------------------- | -------- | ------------ | ----------- | +| `log_source_id` | string | false | | | +| `logs` | array of [agentsdk.Log](#agentsdklog) | false | | | ## agentsdk.PostAppHealthsRequest @@ -5474,19 +5478,35 @@ If the schedule is empty, the user will be updated to use the default schedule.| } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", @@ -5618,19 +5638,35 @@ If the schedule is empty, the user will be updated to use the default schedule.| } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", @@ -5641,44 +5677,41 @@ If the schedule is empty, the user will be updated to use the default schedule.| ### 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 | | | -| `display_apps` | array of [codersdk.DisplayApp](#codersdkdisplayapp) | false | | | -| `environment_variables` | object | false | | | -| » `[any property]` | string | false | | | -| `expanded_directory` | string | false | | | -| `first_connected_at` | string | false | | | -| `health` | [codersdk.WorkspaceAgentHealth](#codersdkworkspaceagenthealth) | false | | Health reports the health of the agent. | -| `id` | string | false | | | -| `instance_id` | string | false | | | -| `last_connected_at` | string | false | | | -| `latency` | object | false | | Latency is mapped by region name (e.g. "New York City", "Seattle"). | -| » `[any property]` | [codersdk.DERPRegion](#codersdkderpregion) | false | | | -| `lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](#codersdkworkspaceagentlifecycle) | false | | | -| `login_before_ready` | boolean | false | | Deprecated: Use StartupScriptBehavior instead. | -| `logs_length` | integer | false | | | -| `logs_overflowed` | boolean | false | | | -| `name` | string | false | | | -| `operating_system` | string | false | | | -| `ready_at` | string | false | | | -| `resource_id` | string | false | | | -| `shutdown_script` | string | false | | | -| `shutdown_script_timeout_seconds` | integer | false | | | -| `started_at` | string | false | | | -| `startup_script` | string | false | | | -| `startup_script_behavior` | [codersdk.WorkspaceAgentStartupScriptBehavior](#codersdkworkspaceagentstartupscriptbehavior) | 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 | | | -| `subsystems` | array of [codersdk.AgentSubsystem](#codersdkagentsubsystem) | false | | | -| `troubleshooting_url` | string | false | | | -| `updated_at` | string | false | | | -| `version` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ---------------------------- | -------------------------------------------------------------------------------------------- | -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `apps` | array of [codersdk.WorkspaceApp](#codersdkworkspaceapp) | false | | | +| `architecture` | string | false | | | +| `connection_timeout_seconds` | integer | false | | | +| `created_at` | string | false | | | +| `directory` | string | false | | | +| `disconnected_at` | string | false | | | +| `display_apps` | array of [codersdk.DisplayApp](#codersdkdisplayapp) | false | | | +| `environment_variables` | object | false | | | +| » `[any property]` | string | false | | | +| `expanded_directory` | string | false | | | +| `first_connected_at` | string | false | | | +| `health` | [codersdk.WorkspaceAgentHealth](#codersdkworkspaceagenthealth) | false | | Health reports the health of the agent. | +| `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 | | | +| `log_sources` | array of [codersdk.WorkspaceAgentLogSource](#codersdkworkspaceagentlogsource) | false | | | +| `logs_length` | integer | false | | | +| `logs_overflowed` | boolean | false | | | +| `name` | string | false | | | +| `operating_system` | string | false | | | +| `ready_at` | string | false | | | +| `resource_id` | string | false | | | +| `scripts` | array of [codersdk.WorkspaceAgentScript](#codersdkworkspaceagentscript) | false | | | +| `started_at` | string | false | | | +| `startup_script_behavior` | [codersdk.WorkspaceAgentStartupScriptBehavior](#codersdkworkspaceagentstartupscriptbehavior) | false | | Startup script behavior is a legacy field that is deprecated in favor of the `coder_script` resource. It's only referenced by old clients. Deprecated: Remove in the future! | +| `status` | [codersdk.WorkspaceAgentStatus](#codersdkworkspaceagentstatus) | false | | | +| `subsystems` | array of [codersdk.AgentSubsystem](#codersdkagentsubsystem) | false | | | +| `troubleshooting_url` | string | false | | | +| `updated_at` | string | false | | | +| `version` | string | false | | | ## codersdk.WorkspaceAgentConnectionInfo @@ -5839,7 +5872,8 @@ If the schedule is empty, the user will be updated to use the default schedule.| "created_at": "2019-08-24T14:15:22Z", "id": 0, "level": "trace", - "output": "string" + "output": "string", + "source_id": "ae50a35c-df42-4eff-ba26-f8bc28d2af81" } ``` @@ -5851,25 +5885,29 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `id` | integer | false | | | | `level` | [codersdk.LogLevel](#codersdkloglevel) | false | | | | `output` | string | false | | | +| `source_id` | string | false | | | ## codersdk.WorkspaceAgentLogSource ```json -"startup_script" +{ + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" +} ``` ### Properties -#### Enumerated Values - -| Value | -| ----------------- | -| `startup_script` | -| `shutdown_script` | -| `kubernetes` | -| `envbox` | -| `envbuilder` | -| `external` | +| Name | Type | Required | Restrictions | Description | +| -------------------- | ------ | -------- | ------------ | ----------- | +| `created_at` | string | false | | | +| `display_name` | string | false | | | +| `icon` | string | false | | | +| `id` | string | false | | | +| `workspace_agent_id` | string | false | | | ## codersdk.WorkspaceAgentMetadataDescription @@ -5893,6 +5931,34 @@ If the schedule is empty, the user will be updated to use the default schedule.| | `script` | string | false | | | | `timeout` | integer | false | | | +## codersdk.WorkspaceAgentScript + +```json +{ + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| -------------------- | ------- | -------- | ------------ | ----------- | +| `cron` | string | false | | | +| `log_path` | string | false | | | +| `log_source_id` | string | false | | | +| `run_on_start` | boolean | false | | | +| `run_on_stop` | boolean | false | | | +| `script` | string | false | | | +| `start_blocks_login` | boolean | false | | | +| `timeout` | integer | false | | | + ## codersdk.WorkspaceAgentStartupScriptBehavior ```json @@ -6091,19 +6157,35 @@ If the schedule is empty, the user will be updated to use the default schedule.| } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", @@ -6404,19 +6486,35 @@ If the schedule is empty, the user will be updated to use the default schedule.| } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", @@ -6619,19 +6717,35 @@ If the schedule is empty, the user will be updated to use the default schedule.| } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", diff --git a/docs/api/templates.md b/docs/api/templates.md index 2e5eac3abe7f2..526fc193fa684 100644 --- a/docs/api/templates.md +++ b/docs/api/templates.md @@ -1619,19 +1619,35 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/d } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", @@ -1669,78 +1685,88 @@ 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. | -| `»»» subdomain_name` | string | false | | Subdomain name is the application domain exposed on the `coder server`. | -| `»»» 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 | | | -| `»» display_apps` | array | false | | | -| `»» environment_variables` | object | false | | | -| `»»» [any property]` | string | false | | | -| `»» expanded_directory` | string | false | | | -| `»» first_connected_at` | string(date-time) | false | | | -| `»» health` | [codersdk.WorkspaceAgentHealth](schemas.md#codersdkworkspaceagenthealth) | false | | Health reports the health of the agent. | -| `»»» healthy` | boolean | false | | Healthy is true if the agent is healthy. | -| `»»» reason` | string | false | | Reason is a human-readable explanation of the agent's health. It is empty if Healthy is true. | -| `»» id` | string(uuid) | false | | | -| `»» instance_id` | string | false | | | -| `»» last_connected_at` | string(date-time) | false | | | -| `»» latency` | object | false | | Latency is mapped by region name (e.g. "New York City", "Seattle"). | -| `»»» [any property]` | [codersdk.DERPRegion](schemas.md#codersdkderpregion) | false | | | -| `»»»» latency_ms` | number | false | | | -| `»»»» preferred` | boolean | false | | | -| `»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | | -| `»» login_before_ready` | boolean | false | | Deprecated: Use StartupScriptBehavior instead. | -| `»» logs_length` | integer | false | | | -| `»» logs_overflowed` | boolean | false | | | -| `»» name` | string | false | | | -| `»» operating_system` | string | false | | | -| `»» ready_at` | string(date-time) | false | | | -| `»» resource_id` | string(uuid) | false | | | -| `»» shutdown_script` | string | false | | | -| `»» shutdown_script_timeout_seconds` | integer | false | | | -| `»» started_at` | string(date-time) | false | | | -| `»» startup_script` | string | false | | | -| `»» startup_script_behavior` | [codersdk.WorkspaceAgentStartupScriptBehavior](schemas.md#codersdkworkspaceagentstartupscriptbehavior) | 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 | | | -| `»» subsystems` | array | 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. | +| `»»» subdomain_name` | string | false | | Subdomain name is the application domain exposed on the `coder server`. | +| `»»» 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 | | | +| `»» display_apps` | array | false | | | +| `»» environment_variables` | object | false | | | +| `»»» [any property]` | string | false | | | +| `»» expanded_directory` | string | false | | | +| `»» first_connected_at` | string(date-time) | false | | | +| `»» health` | [codersdk.WorkspaceAgentHealth](schemas.md#codersdkworkspaceagenthealth) | false | | Health reports the health of the agent. | +| `»»» healthy` | boolean | false | | Healthy is true if the agent is healthy. | +| `»»» reason` | string | false | | Reason is a human-readable explanation of the agent's health. It is empty if Healthy is true. | +| `»» 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 | | | +| `»» log_sources` | array | false | | | +| `»»» created_at` | string(date-time) | false | | | +| `»»» display_name` | string | false | | | +| `»»» icon` | string | false | | | +| `»»» id` | string(uuid) | false | | | +| `»»» workspace_agent_id` | string(uuid) | false | | | +| `»» logs_length` | integer | false | | | +| `»» logs_overflowed` | boolean | false | | | +| `»» name` | string | false | | | +| `»» operating_system` | string | false | | | +| `»» ready_at` | string(date-time) | false | | | +| `»» resource_id` | string(uuid) | false | | | +| `»» scripts` | array | false | | | +| `»»» cron` | string | false | | | +| `»»» log_path` | string | false | | | +| `»»» log_source_id` | string(uuid) | false | | | +| `»»» run_on_start` | boolean | false | | | +| `»»» run_on_stop` | boolean | false | | | +| `»»» script` | string | false | | | +| `»»» start_blocks_login` | boolean | false | | | +| `»»» timeout` | integer | false | | | +| `»» started_at` | string(date-time) | false | | | +| `»» startup_script_behavior` | [codersdk.WorkspaceAgentStartupScriptBehavior](schemas.md#codersdkworkspaceagentstartupscriptbehavior) | false | | Startup script behavior is a legacy field that is deprecated in favor of the `coder_script` resource. It's only referenced by old clients. Deprecated: Remove in the future! | +| `»» status` | [codersdk.WorkspaceAgentStatus](schemas.md#codersdkworkspaceagentstatus) | false | | | +| `»» subsystems` | array | 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 @@ -2014,19 +2040,35 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/r } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", @@ -2064,78 +2106,88 @@ 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. | -| `»»» subdomain_name` | string | false | | Subdomain name is the application domain exposed on the `coder server`. | -| `»»» 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 | | | -| `»» display_apps` | array | false | | | -| `»» environment_variables` | object | false | | | -| `»»» [any property]` | string | false | | | -| `»» expanded_directory` | string | false | | | -| `»» first_connected_at` | string(date-time) | false | | | -| `»» health` | [codersdk.WorkspaceAgentHealth](schemas.md#codersdkworkspaceagenthealth) | false | | Health reports the health of the agent. | -| `»»» healthy` | boolean | false | | Healthy is true if the agent is healthy. | -| `»»» reason` | string | false | | Reason is a human-readable explanation of the agent's health. It is empty if Healthy is true. | -| `»» id` | string(uuid) | false | | | -| `»» instance_id` | string | false | | | -| `»» last_connected_at` | string(date-time) | false | | | -| `»» latency` | object | false | | Latency is mapped by region name (e.g. "New York City", "Seattle"). | -| `»»» [any property]` | [codersdk.DERPRegion](schemas.md#codersdkderpregion) | false | | | -| `»»»» latency_ms` | number | false | | | -| `»»»» preferred` | boolean | false | | | -| `»» lifecycle_state` | [codersdk.WorkspaceAgentLifecycle](schemas.md#codersdkworkspaceagentlifecycle) | false | | | -| `»» login_before_ready` | boolean | false | | Deprecated: Use StartupScriptBehavior instead. | -| `»» logs_length` | integer | false | | | -| `»» logs_overflowed` | boolean | false | | | -| `»» name` | string | false | | | -| `»» operating_system` | string | false | | | -| `»» ready_at` | string(date-time) | false | | | -| `»» resource_id` | string(uuid) | false | | | -| `»» shutdown_script` | string | false | | | -| `»» shutdown_script_timeout_seconds` | integer | false | | | -| `»» started_at` | string(date-time) | false | | | -| `»» startup_script` | string | false | | | -| `»» startup_script_behavior` | [codersdk.WorkspaceAgentStartupScriptBehavior](schemas.md#codersdkworkspaceagentstartupscriptbehavior) | 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 | | | -| `»» subsystems` | array | 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. | +| `»»» subdomain_name` | string | false | | Subdomain name is the application domain exposed on the `coder server`. | +| `»»» 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 | | | +| `»» display_apps` | array | false | | | +| `»» environment_variables` | object | false | | | +| `»»» [any property]` | string | false | | | +| `»» expanded_directory` | string | false | | | +| `»» first_connected_at` | string(date-time) | false | | | +| `»» health` | [codersdk.WorkspaceAgentHealth](schemas.md#codersdkworkspaceagenthealth) | false | | Health reports the health of the agent. | +| `»»» healthy` | boolean | false | | Healthy is true if the agent is healthy. | +| `»»» reason` | string | false | | Reason is a human-readable explanation of the agent's health. It is empty if Healthy is true. | +| `»» 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 | | | +| `»» log_sources` | array | false | | | +| `»»» created_at` | string(date-time) | false | | | +| `»»» display_name` | string | false | | | +| `»»» icon` | string | false | | | +| `»»» id` | string(uuid) | false | | | +| `»»» workspace_agent_id` | string(uuid) | false | | | +| `»» logs_length` | integer | false | | | +| `»» logs_overflowed` | boolean | false | | | +| `»» name` | string | false | | | +| `»» operating_system` | string | false | | | +| `»» ready_at` | string(date-time) | false | | | +| `»» resource_id` | string(uuid) | false | | | +| `»» scripts` | array | false | | | +| `»»» cron` | string | false | | | +| `»»» log_path` | string | false | | | +| `»»» log_source_id` | string(uuid) | false | | | +| `»»» run_on_start` | boolean | false | | | +| `»»» run_on_stop` | boolean | false | | | +| `»»» script` | string | false | | | +| `»»» start_blocks_login` | boolean | false | | | +| `»»» timeout` | integer | false | | | +| `»» started_at` | string(date-time) | false | | | +| `»» startup_script_behavior` | [codersdk.WorkspaceAgentStartupScriptBehavior](schemas.md#codersdkworkspaceagentstartupscriptbehavior) | false | | Startup script behavior is a legacy field that is deprecated in favor of the `coder_script` resource. It's only referenced by old clients. Deprecated: Remove in the future! | +| `»» status` | [codersdk.WorkspaceAgentStatus](schemas.md#codersdkworkspaceagentstatus) | false | | | +| `»» subsystems` | array | 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 diff --git a/docs/api/workspaces.md b/docs/api/workspaces.md index f11f2bda3973e..56aaabee03025 100644 --- a/docs/api/workspaces.md +++ b/docs/api/workspaces.md @@ -138,19 +138,35 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/member } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", @@ -329,19 +345,35 @@ curl -X GET http://coder-server:8080/api/v2/users/{user}/workspace/{workspacenam } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", @@ -519,19 +551,35 @@ curl -X GET http://coder-server:8080/api/v2/workspaces \ } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", @@ -711,19 +759,35 @@ curl -X GET http://coder-server:8080/api/v2/workspaces/{workspace} \ } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", @@ -982,19 +1046,35 @@ curl -X PUT http://coder-server:8080/api/v2/workspaces/{workspace}/dormant \ } }, "lifecycle_state": "created", - "login_before_ready": true, + "log_sources": [ + { + "created_at": "2019-08-24T14:15:22Z", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1" + } + ], "logs_length": 0, "logs_overflowed": true, "name": "string", "operating_system": "string", "ready_at": "2019-08-24T14:15:22Z", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", - "shutdown_script": "string", - "shutdown_script_timeout_seconds": 0, + "scripts": [ + { + "cron": "string", + "log_path": "string", + "log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a", + "run_on_start": true, + "run_on_stop": true, + "script": "string", + "start_blocks_login": true, + "timeout": 0 + } + ], "started_at": "2019-08-24T14:15:22Z", - "startup_script": "string", "startup_script_behavior": "blocking", - "startup_script_timeout_seconds": 0, "status": "connecting", "subsystems": ["envbox"], "troubleshooting_url": "string", diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go index 3382b7e7b7847..4cdd16890731c 100644 --- a/provisioner/terraform/resources.go +++ b/provisioner/terraform/resources.go @@ -29,25 +29,26 @@ type agentMetadata struct { // 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"` // Deprecated, but remains here for backwards compatibility. - LoginBeforeReady bool `mapstructure:"login_before_ready"` - StartupScriptBehavior string `mapstructure:"startup_script_behavior"` - StartupScriptTimeoutSeconds int32 `mapstructure:"startup_script_timeout"` - ShutdownScript string `mapstructure:"shutdown_script"` - ShutdownScriptTimeoutSeconds int32 `mapstructure:"shutdown_script_timeout"` - Metadata []agentMetadata `mapstructure:"metadata"` - DisplayApps []agentDisplayAppsAttributes `mapstructure:"display_apps"` + StartupScript string `mapstructure:"startup_script"` + StartupScriptBehavior string `mapstructure:"startup_script_behavior"` + StartupScriptTimeoutSeconds int32 `mapstructure:"startup_script_timeout"` + LoginBeforeReady bool `mapstructure:"login_before_ready"` + ShutdownScript string `mapstructure:"shutdown_script"` + ShutdownScriptTimeoutSeconds int32 `mapstructure:"shutdown_script_timeout"` + + ConnectionTimeoutSeconds int32 `mapstructure:"connection_timeout"` + TroubleshootingURL string `mapstructure:"troubleshooting_url"` + MOTDFile string `mapstructure:"motd_file"` + Metadata []agentMetadata `mapstructure:"metadata"` + DisplayApps []agentDisplayAppsAttributes `mapstructure:"display_apps"` } type agentDisplayAppsAttributes struct { @@ -76,6 +77,19 @@ type agentAppAttributes struct { Healthcheck []appHealthcheckAttributes `mapstructure:"healthcheck"` } +type agentScriptAttributes struct { + AgentID string `mapstructure:"agent_id"` + DisplayName string `mapstructure:"display_name"` + Icon string `mapstructure:"icon"` + Script string `mapstructure:"script"` + Cron string `mapstructure:"cron"` + LogPath string `mapstructure:"log_path"` + StartBlocksLogin bool `mapstructure:"start_blocks_login"` + RunOnStart bool `mapstructure:"run_on_start"` + RunOnStop bool `mapstructure:"run_on_stop"` + TimeoutSeconds int32 `mapstructure:"timeout"` +} + // A mapping of attributes on the "healthcheck" resource. type appHealthcheckAttributes struct { URL string `mapstructure:"url"` @@ -107,6 +121,7 @@ type State struct { // ConvertState consumes Terraform state and a GraphViz representation // produced by `terraform graph` to produce resources consumable by Coder. +// nolint:gocognit // This function makes more sense being large for now, until refactored. func ConvertState(modules []*tfjson.StateModule, rawGraph string) (*State, error) { parsedGraph, err := gographviz.ParseString(rawGraph) if err != nil { @@ -206,22 +221,39 @@ func ConvertState(modules []*tfjson.StateModule, rawGraph string) (*State, error } agent := &proto.Agent{ - Name: tfResource.Name, - Id: attrs.ID, - Env: attrs.Env, - StartupScript: attrs.StartupScript, - OperatingSystem: attrs.OperatingSystem, - Architecture: attrs.Architecture, - Directory: attrs.Directory, - ConnectionTimeoutSeconds: attrs.ConnectionTimeoutSeconds, - TroubleshootingUrl: attrs.TroubleshootingURL, - MotdFile: attrs.MOTDFile, - StartupScriptBehavior: startupScriptBehavior, - StartupScriptTimeoutSeconds: attrs.StartupScriptTimeoutSeconds, - ShutdownScript: attrs.ShutdownScript, - ShutdownScriptTimeoutSeconds: attrs.ShutdownScriptTimeoutSeconds, - Metadata: metadata, - DisplayApps: displayApps, + Name: tfResource.Name, + Id: attrs.ID, + Env: attrs.Env, + OperatingSystem: attrs.OperatingSystem, + Architecture: attrs.Architecture, + Directory: attrs.Directory, + ConnectionTimeoutSeconds: attrs.ConnectionTimeoutSeconds, + TroubleshootingUrl: attrs.TroubleshootingURL, + MotdFile: attrs.MOTDFile, + Metadata: metadata, + DisplayApps: displayApps, + } + // Support the legacy script attributes in the agent! + if attrs.StartupScript != "" { + agent.Scripts = append(agent.Scripts, &proto.Script{ + // This is ▶️ + Icon: "/emojis/25b6.png", + LogPath: "coder-startup-script.log", + DisplayName: "Startup Script", + Script: attrs.StartupScript, + StartBlocksLogin: startupScriptBehavior == string(codersdk.WorkspaceAgentStartupScriptBehaviorBlocking), + RunOnStart: true, + }) + } + if attrs.ShutdownScript != "" { + agent.Scripts = append(agent.Scripts, &proto.Script{ + // This is ◀️ + Icon: "/emojis/25c0.png", + LogPath: "coder-shutdown-script.log", + DisplayName: "Shutdown Script", + Script: attrs.ShutdownScript, + RunOnStop: true, + }) } switch attrs.Auth { case "token": @@ -403,6 +435,39 @@ func ConvertState(modules []*tfjson.StateModule, rawGraph string) (*State, error } } + // Associate scripts with agents. + for _, resources := range tfResourcesByLabel { + for _, resource := range resources { + if resource.Type != "coder_script" { + continue + } + var attrs agentScriptAttributes + err = mapstructure.Decode(resource.AttributeValues, &attrs) + if err != nil { + return nil, xerrors.Errorf("decode app attributes: %w", err) + } + for _, agents := range resourceAgents { + for _, agent := range agents { + // Find agents with the matching ID and associate them! + if agent.Id != attrs.AgentID { + continue + } + agent.Scripts = append(agent.Scripts, &proto.Script{ + DisplayName: attrs.DisplayName, + Icon: attrs.Icon, + Script: attrs.Script, + Cron: attrs.Cron, + LogPath: attrs.LogPath, + StartBlocksLogin: attrs.StartBlocksLogin, + RunOnStart: attrs.RunOnStart, + RunOnStop: attrs.RunOnStop, + TimeoutSeconds: attrs.TimeoutSeconds, + }) + } + } + } + } + // Associate metadata blocks with resources. resourceMetadata := map[string][]*proto.Resource_Metadata{} resourceHidden := map[string]bool{} @@ -482,7 +547,7 @@ func ConvertState(modules []*tfjson.StateModule, rawGraph string) (*State, error if resource.Mode == tfjson.DataResourceMode { continue } - if resource.Type == "coder_agent" || resource.Type == "coder_agent_instance" || resource.Type == "coder_app" || resource.Type == "coder_metadata" { + if resource.Type == "coder_script" || resource.Type == "coder_agent" || resource.Type == "coder_agent_instance" || resource.Type == "coder_app" || resource.Type == "coder_metadata" { continue } label := convertAddressToLabel(resource.Address) diff --git a/provisioner/terraform/resources_test.go b/provisioner/terraform/resources_test.go index 79d7a56ac4d16..cb3c4237c1f80 100644 --- a/provisioner/terraform/resources_test.go +++ b/provisioner/terraform/resources_test.go @@ -54,7 +54,6 @@ func TestConvertResources(t *testing.T) { OperatingSystem: "linux", Architecture: "amd64", Auth: &proto.Agent_Token{}, - StartupScriptBehavior: "non-blocking", ConnectionTimeoutSeconds: 120, DisplayApps: &displayApps, }}, @@ -72,7 +71,6 @@ func TestConvertResources(t *testing.T) { OperatingSystem: "linux", Architecture: "amd64", Auth: &proto.Agent_Token{}, - StartupScriptBehavior: "non-blocking", ConnectionTimeoutSeconds: 120, DisplayApps: &displayApps, }}, @@ -91,7 +89,6 @@ func TestConvertResources(t *testing.T) { OperatingSystem: "linux", Architecture: "amd64", Auth: &proto.Agent_InstanceId{}, - StartupScriptBehavior: "non-blocking", ConnectionTimeoutSeconds: 120, DisplayApps: &displayApps, }}, @@ -108,7 +105,6 @@ func TestConvertResources(t *testing.T) { OperatingSystem: "linux", Architecture: "amd64", Auth: &proto.Agent_Token{}, - StartupScriptBehavior: "non-blocking", ConnectionTimeoutSeconds: 120, DisplayApps: &displayApps, }}, @@ -121,48 +117,42 @@ 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, - StartupScriptBehavior: "non-blocking", - StartupScriptTimeoutSeconds: 300, - ShutdownScriptTimeoutSeconds: 300, - DisplayApps: &displayApps, + Name: "dev1", + OperatingSystem: "linux", + Architecture: "amd64", + Auth: &proto.Agent_Token{}, + ConnectionTimeoutSeconds: 120, + DisplayApps: &displayApps, }, { - Name: "dev2", - OperatingSystem: "darwin", - Architecture: "amd64", - Auth: &proto.Agent_Token{}, - ConnectionTimeoutSeconds: 1, - MotdFile: "/etc/motd", - StartupScriptBehavior: "non-blocking", - StartupScriptTimeoutSeconds: 30, - ShutdownScript: "echo bye bye", - ShutdownScriptTimeoutSeconds: 30, - DisplayApps: &displayApps, + Name: "dev2", + OperatingSystem: "darwin", + Architecture: "amd64", + Auth: &proto.Agent_Token{}, + ConnectionTimeoutSeconds: 1, + MotdFile: "/etc/motd", + DisplayApps: &displayApps, + Scripts: []*proto.Script{{ + Icon: "/emojis/25c0.png", + DisplayName: "Shutdown Script", + RunOnStop: true, + LogPath: "coder-shutdown-script.log", + Script: "echo bye bye", + }}, }, { - Name: "dev3", - OperatingSystem: "windows", - Architecture: "arm64", - Auth: &proto.Agent_Token{}, - ConnectionTimeoutSeconds: 120, - TroubleshootingUrl: "https://coder.com/troubleshoot", - StartupScriptBehavior: "blocking", - StartupScriptTimeoutSeconds: 300, - ShutdownScriptTimeoutSeconds: 300, - DisplayApps: &displayApps, + Name: "dev3", + OperatingSystem: "windows", + Architecture: "arm64", + Auth: &proto.Agent_Token{}, + ConnectionTimeoutSeconds: 120, + TroubleshootingUrl: "https://coder.com/troubleshoot", + DisplayApps: &displayApps, }, { - Name: "dev4", - OperatingSystem: "linux", - Architecture: "amd64", - Auth: &proto.Agent_Token{}, - ConnectionTimeoutSeconds: 120, - StartupScriptBehavior: "blocking", - StartupScriptTimeoutSeconds: 300, - ShutdownScriptTimeoutSeconds: 300, - DisplayApps: &displayApps, + Name: "dev4", + OperatingSystem: "linux", + Architecture: "amd64", + Auth: &proto.Agent_Token{}, + ConnectionTimeoutSeconds: 120, + DisplayApps: &displayApps, }}, }}, }, @@ -199,7 +189,6 @@ func TestConvertResources(t *testing.T) { }, }, Auth: &proto.Agent_Token{}, - StartupScriptBehavior: "non-blocking", ConnectionTimeoutSeconds: 120, DisplayApps: &displayApps, }}, @@ -224,7 +213,6 @@ func TestConvertResources(t *testing.T) { }, }, Auth: &proto.Agent_Token{}, - StartupScriptBehavior: "non-blocking", ConnectionTimeoutSeconds: 120, DisplayApps: &displayApps, }}, @@ -263,11 +251,8 @@ func TestConvertResources(t *testing.T) { Interval: 5, Timeout: 1, }}, - ShutdownScriptTimeoutSeconds: 300, - StartupScriptTimeoutSeconds: 300, - StartupScriptBehavior: "non-blocking", - ConnectionTimeoutSeconds: 120, - DisplayApps: &displayApps, + ConnectionTimeoutSeconds: 120, + DisplayApps: &displayApps, }}, }}, }, @@ -307,7 +292,6 @@ func TestConvertResources(t *testing.T) { Name: "main", OperatingSystem: "linux", Architecture: "amd64", - StartupScript: " #!/bin/bash\n # home folder can be empty, so copying default bash settings\n if [ ! -f ~/.profile ]; then\n cp /etc/skel/.profile $HOME\n fi\n if [ ! -f ~/.bashrc ]; then\n cp /etc/skel/.bashrc $HOME\n fi\n # install and start code-server\n curl -fsSL https://code-server.dev/install.sh | sh | tee code-server-install.log\n code-server --auth none --port 13337 | tee code-server-install.log &\n", Apps: []*proto.App{ { Icon: "/icon/code.svg", @@ -317,9 +301,15 @@ func TestConvertResources(t *testing.T) { }, }, Auth: &proto.Agent_Token{}, - StartupScriptBehavior: "non-blocking", ConnectionTimeoutSeconds: 120, DisplayApps: &displayApps, + Scripts: []*proto.Script{{ + DisplayName: "Startup Script", + RunOnStart: true, + LogPath: "coder-startup-script.log", + Icon: "/emojis/25b6.png", + Script: " #!/bin/bash\n # home folder can be empty, so copying default bash settings\n if [ ! -f ~/.profile ]; then\n cp /etc/skel/.profile $HOME\n fi\n if [ ! -f ~/.bashrc ]; then\n cp /etc/skel/.bashrc $HOME\n fi\n # install and start code-server\n curl -fsSL https://code-server.dev/install.sh | sh | tee code-server-install.log\n code-server --auth none --port 13337 | tee code-server-install.log &\n", + }}, }}, }, }, @@ -329,15 +319,12 @@ func TestConvertResources(t *testing.T) { Name: "dev", Type: "null_resource", Agents: []*proto.Agent{{ - Name: "dev", - OperatingSystem: "windows", - ShutdownScriptTimeoutSeconds: 300, - StartupScriptTimeoutSeconds: 300, - Architecture: "arm64", - Auth: &proto.Agent_Token{}, - StartupScriptBehavior: "non-blocking", - ConnectionTimeoutSeconds: 120, - DisplayApps: &displayApps, + Name: "dev", + OperatingSystem: "windows", + Architecture: "arm64", + Auth: &proto.Agent_Token{}, + ConnectionTimeoutSeconds: 120, + DisplayApps: &displayApps, }}, }}, parameters: []*proto.RichParameter{{ @@ -411,15 +398,12 @@ func TestConvertResources(t *testing.T) { Name: "dev", Type: "null_resource", Agents: []*proto.Agent{{ - Name: "dev", - OperatingSystem: "windows", - ShutdownScriptTimeoutSeconds: 300, - StartupScriptTimeoutSeconds: 300, - Architecture: "arm64", - Auth: &proto.Agent_Token{}, - StartupScriptBehavior: "non-blocking", - ConnectionTimeoutSeconds: 120, - DisplayApps: &displayApps, + Name: "dev", + OperatingSystem: "windows", + Architecture: "arm64", + Auth: &proto.Agent_Token{}, + ConnectionTimeoutSeconds: 120, + DisplayApps: &displayApps, }}, }}, parameters: []*proto.RichParameter{{ @@ -440,15 +424,12 @@ func TestConvertResources(t *testing.T) { Name: "dev", Type: "null_resource", Agents: []*proto.Agent{{ - Name: "dev", - OperatingSystem: "windows", - ShutdownScriptTimeoutSeconds: 300, - StartupScriptTimeoutSeconds: 300, - Architecture: "arm64", - Auth: &proto.Agent_Token{}, - StartupScriptBehavior: "non-blocking", - ConnectionTimeoutSeconds: 120, - DisplayApps: &displayApps, + Name: "dev", + OperatingSystem: "windows", + Architecture: "arm64", + Auth: &proto.Agent_Token{}, + ConnectionTimeoutSeconds: 120, + DisplayApps: &displayApps, }}, }}, parameters: []*proto.RichParameter{{ @@ -496,15 +477,12 @@ func TestConvertResources(t *testing.T) { Name: "dev", Type: "null_resource", Agents: []*proto.Agent{{ - Name: "main", - OperatingSystem: "linux", - Architecture: "amd64", - Auth: &proto.Agent_Token{}, - StartupScriptBehavior: "non-blocking", - ConnectionTimeoutSeconds: 120, - StartupScriptTimeoutSeconds: 300, - ShutdownScriptTimeoutSeconds: 300, - DisplayApps: &displayApps, + Name: "main", + OperatingSystem: "linux", + Architecture: "amd64", + Auth: &proto.Agent_Token{}, + ConnectionTimeoutSeconds: 120, + DisplayApps: &displayApps, }}, }}, gitAuthProviders: []string{"github", "gitlab"}, @@ -514,14 +492,11 @@ func TestConvertResources(t *testing.T) { Name: "dev", Type: "null_resource", Agents: []*proto.Agent{{ - Name: "main", - OperatingSystem: "linux", - Architecture: "amd64", - Auth: &proto.Agent_Token{}, - StartupScriptBehavior: "non-blocking", - ConnectionTimeoutSeconds: 120, - StartupScriptTimeoutSeconds: 300, - ShutdownScriptTimeoutSeconds: 300, + Name: "main", + OperatingSystem: "linux", + Architecture: "amd64", + Auth: &proto.Agent_Token{}, + ConnectionTimeoutSeconds: 120, DisplayApps: &proto.DisplayApps{ VscodeInsiders: true, WebTerminal: true, @@ -534,15 +509,12 @@ func TestConvertResources(t *testing.T) { Name: "dev", Type: "null_resource", Agents: []*proto.Agent{{ - Name: "main", - OperatingSystem: "linux", - Architecture: "amd64", - Auth: &proto.Agent_Token{}, - StartupScriptBehavior: "non-blocking", - ConnectionTimeoutSeconds: 120, - StartupScriptTimeoutSeconds: 300, - ShutdownScriptTimeoutSeconds: 300, - DisplayApps: &proto.DisplayApps{}, + Name: "main", + OperatingSystem: "linux", + Architecture: "amd64", + Auth: &proto.Agent_Token{}, + ConnectionTimeoutSeconds: 120, + DisplayApps: &proto.DisplayApps{}, }}, }}, }, diff --git a/provisionersdk/proto/provisioner.pb.go b/provisionersdk/proto/provisioner.pb.go index fef1aadb198c2..6b96780c5cdbf 100644 --- a/provisionersdk/proto/provisioner.pb.go +++ b/provisionersdk/proto/provisioner.pb.go @@ -827,14 +827,14 @@ type Agent struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` - Env map[string]string `protobuf:"bytes,3,rep,name=env,proto3" json:"env,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - StartupScript string `protobuf:"bytes,4,opt,name=startup_script,json=startupScript,proto3" json:"startup_script,omitempty"` - OperatingSystem string `protobuf:"bytes,5,opt,name=operating_system,json=operatingSystem,proto3" json:"operating_system,omitempty"` - Architecture string `protobuf:"bytes,6,opt,name=architecture,proto3" json:"architecture,omitempty"` - Directory string `protobuf:"bytes,7,opt,name=directory,proto3" json:"directory,omitempty"` - Apps []*App `protobuf:"bytes,8,rep,name=apps,proto3" json:"apps,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Env map[string]string `protobuf:"bytes,3,rep,name=env,proto3" json:"env,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // Field 4 was startup_script, now removed. + OperatingSystem string `protobuf:"bytes,5,opt,name=operating_system,json=operatingSystem,proto3" json:"operating_system,omitempty"` + Architecture string `protobuf:"bytes,6,opt,name=architecture,proto3" json:"architecture,omitempty"` + Directory string `protobuf:"bytes,7,opt,name=directory,proto3" json:"directory,omitempty"` + Apps []*App `protobuf:"bytes,8,rep,name=apps,proto3" json:"apps,omitempty"` // Types that are assignable to Auth: // // *Agent_Token @@ -844,12 +844,11 @@ type Agent struct { 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"` // Field 14 was bool login_before_ready = 14, now removed. - StartupScriptTimeoutSeconds int32 `protobuf:"varint,15,opt,name=startup_script_timeout_seconds,json=startupScriptTimeoutSeconds,proto3" json:"startup_script_timeout_seconds,omitempty"` - ShutdownScript string `protobuf:"bytes,16,opt,name=shutdown_script,json=shutdownScript,proto3" json:"shutdown_script,omitempty"` - ShutdownScriptTimeoutSeconds int32 `protobuf:"varint,17,opt,name=shutdown_script_timeout_seconds,json=shutdownScriptTimeoutSeconds,proto3" json:"shutdown_script_timeout_seconds,omitempty"` - Metadata []*Agent_Metadata `protobuf:"bytes,18,rep,name=metadata,proto3" json:"metadata,omitempty"` - StartupScriptBehavior string `protobuf:"bytes,19,opt,name=startup_script_behavior,json=startupScriptBehavior,proto3" json:"startup_script_behavior,omitempty"` - DisplayApps *DisplayApps `protobuf:"bytes,20,opt,name=display_apps,json=displayApps,proto3" json:"display_apps,omitempty"` + // Field 15, 16, 17 were related to scripts, which are now removed. + Metadata []*Agent_Metadata `protobuf:"bytes,18,rep,name=metadata,proto3" json:"metadata,omitempty"` + // Field 19 was startup_script_behavior, now removed. + DisplayApps *DisplayApps `protobuf:"bytes,20,opt,name=display_apps,json=displayApps,proto3" json:"display_apps,omitempty"` + Scripts []*Script `protobuf:"bytes,21,rep,name=scripts,proto3" json:"scripts,omitempty"` } func (x *Agent) Reset() { @@ -905,13 +904,6 @@ func (x *Agent) GetEnv() map[string]string { return nil } -func (x *Agent) GetStartupScript() string { - if x != nil { - return x.StartupScript - } - return "" -} - func (x *Agent) GetOperatingSystem() string { if x != nil { return x.OperatingSystem @@ -982,27 +974,6 @@ func (x *Agent) GetMotdFile() string { return "" } -func (x *Agent) GetStartupScriptTimeoutSeconds() int32 { - if x != nil { - return x.StartupScriptTimeoutSeconds - } - return 0 -} - -func (x *Agent) GetShutdownScript() string { - if x != nil { - return x.ShutdownScript - } - return "" -} - -func (x *Agent) GetShutdownScriptTimeoutSeconds() int32 { - if x != nil { - return x.ShutdownScriptTimeoutSeconds - } - return 0 -} - func (x *Agent) GetMetadata() []*Agent_Metadata { if x != nil { return x.Metadata @@ -1010,16 +981,16 @@ func (x *Agent) GetMetadata() []*Agent_Metadata { return nil } -func (x *Agent) GetStartupScriptBehavior() string { +func (x *Agent) GetDisplayApps() *DisplayApps { if x != nil { - return x.StartupScriptBehavior + return x.DisplayApps } - return "" + return nil } -func (x *Agent) GetDisplayApps() *DisplayApps { +func (x *Agent) GetScripts() []*Script { if x != nil { - return x.DisplayApps + return x.Scripts } return nil } @@ -1119,6 +1090,118 @@ func (x *DisplayApps) GetPortForwardingHelper() bool { return false } +// Script represents a script to be run on the workspace. +type Script struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + DisplayName string `protobuf:"bytes,1,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` + Icon string `protobuf:"bytes,2,opt,name=icon,proto3" json:"icon,omitempty"` + Script string `protobuf:"bytes,3,opt,name=script,proto3" json:"script,omitempty"` + Cron string `protobuf:"bytes,4,opt,name=cron,proto3" json:"cron,omitempty"` + StartBlocksLogin bool `protobuf:"varint,5,opt,name=start_blocks_login,json=startBlocksLogin,proto3" json:"start_blocks_login,omitempty"` + RunOnStart bool `protobuf:"varint,6,opt,name=run_on_start,json=runOnStart,proto3" json:"run_on_start,omitempty"` + RunOnStop bool `protobuf:"varint,7,opt,name=run_on_stop,json=runOnStop,proto3" json:"run_on_stop,omitempty"` + TimeoutSeconds int32 `protobuf:"varint,8,opt,name=timeout_seconds,json=timeoutSeconds,proto3" json:"timeout_seconds,omitempty"` + LogPath string `protobuf:"bytes,9,opt,name=log_path,json=logPath,proto3" json:"log_path,omitempty"` +} + +func (x *Script) Reset() { + *x = Script{} + if protoimpl.UnsafeEnabled { + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Script) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Script) ProtoMessage() {} + +func (x *Script) ProtoReflect() protoreflect.Message { + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Script.ProtoReflect.Descriptor instead. +func (*Script) Descriptor() ([]byte, []int) { + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11} +} + +func (x *Script) GetDisplayName() string { + if x != nil { + return x.DisplayName + } + return "" +} + +func (x *Script) GetIcon() string { + if x != nil { + return x.Icon + } + return "" +} + +func (x *Script) GetScript() string { + if x != nil { + return x.Script + } + return "" +} + +func (x *Script) GetCron() string { + if x != nil { + return x.Cron + } + return "" +} + +func (x *Script) GetStartBlocksLogin() bool { + if x != nil { + return x.StartBlocksLogin + } + return false +} + +func (x *Script) GetRunOnStart() bool { + if x != nil { + return x.RunOnStart + } + return false +} + +func (x *Script) GetRunOnStop() bool { + if x != nil { + return x.RunOnStop + } + return false +} + +func (x *Script) GetTimeoutSeconds() int32 { + if x != nil { + return x.TimeoutSeconds + } + return 0 +} + +func (x *Script) GetLogPath() string { + if x != nil { + return x.LogPath + } + return "" +} + // App represents a dev-accessible application on the workspace. type App struct { state protoimpl.MessageState @@ -1141,7 +1224,7 @@ type App struct { func (x *App) Reset() { *x = App{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[11] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1154,7 +1237,7 @@ func (x *App) String() string { func (*App) ProtoMessage() {} func (x *App) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[11] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1167,7 +1250,7 @@ func (x *App) ProtoReflect() protoreflect.Message { // Deprecated: Use App.ProtoReflect.Descriptor instead. func (*App) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{11} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{12} } func (x *App) GetSlug() string { @@ -1247,7 +1330,7 @@ type Healthcheck struct { func (x *Healthcheck) Reset() { *x = Healthcheck{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[12] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1260,7 +1343,7 @@ func (x *Healthcheck) String() string { func (*Healthcheck) ProtoMessage() {} func (x *Healthcheck) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[12] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1273,7 +1356,7 @@ func (x *Healthcheck) ProtoReflect() protoreflect.Message { // Deprecated: Use Healthcheck.ProtoReflect.Descriptor instead. func (*Healthcheck) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{12} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{13} } func (x *Healthcheck) GetUrl() string { @@ -1316,7 +1399,7 @@ type Resource struct { func (x *Resource) Reset() { *x = Resource{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[13] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1329,7 +1412,7 @@ func (x *Resource) String() string { func (*Resource) ProtoMessage() {} func (x *Resource) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[13] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1342,7 +1425,7 @@ func (x *Resource) ProtoReflect() protoreflect.Message { // Deprecated: Use Resource.ProtoReflect.Descriptor instead. func (*Resource) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{13} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{14} } func (x *Resource) GetName() string { @@ -1424,7 +1507,7 @@ type Metadata struct { func (x *Metadata) Reset() { *x = Metadata{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[14] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1437,7 +1520,7 @@ func (x *Metadata) String() string { func (*Metadata) ProtoMessage() {} func (x *Metadata) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[14] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1450,7 +1533,7 @@ func (x *Metadata) ProtoReflect() protoreflect.Message { // Deprecated: Use Metadata.ProtoReflect.Descriptor instead. func (*Metadata) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{14} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{15} } func (x *Metadata) GetCoderUrl() string { @@ -1553,7 +1636,7 @@ type Config struct { func (x *Config) Reset() { *x = Config{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[15] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1566,7 +1649,7 @@ func (x *Config) String() string { func (*Config) ProtoMessage() {} func (x *Config) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[15] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1579,7 +1662,7 @@ func (x *Config) ProtoReflect() protoreflect.Message { // Deprecated: Use Config.ProtoReflect.Descriptor instead. func (*Config) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{15} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{16} } func (x *Config) GetTemplateSourceArchive() []byte { @@ -1613,7 +1696,7 @@ type ParseRequest struct { func (x *ParseRequest) Reset() { *x = ParseRequest{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[16] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1626,7 +1709,7 @@ func (x *ParseRequest) String() string { func (*ParseRequest) ProtoMessage() {} func (x *ParseRequest) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[16] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1639,7 +1722,7 @@ func (x *ParseRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ParseRequest.ProtoReflect.Descriptor instead. func (*ParseRequest) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{16} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{17} } // ParseComplete indicates a request to parse completed. @@ -1656,7 +1739,7 @@ type ParseComplete struct { func (x *ParseComplete) Reset() { *x = ParseComplete{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[17] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1669,7 +1752,7 @@ func (x *ParseComplete) String() string { func (*ParseComplete) ProtoMessage() {} func (x *ParseComplete) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[17] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1682,7 +1765,7 @@ func (x *ParseComplete) ProtoReflect() protoreflect.Message { // Deprecated: Use ParseComplete.ProtoReflect.Descriptor instead. func (*ParseComplete) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{17} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{18} } func (x *ParseComplete) GetError() string { @@ -1721,7 +1804,7 @@ type PlanRequest struct { func (x *PlanRequest) Reset() { *x = PlanRequest{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[18] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1734,7 +1817,7 @@ func (x *PlanRequest) String() string { func (*PlanRequest) ProtoMessage() {} func (x *PlanRequest) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[18] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1747,7 +1830,7 @@ func (x *PlanRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PlanRequest.ProtoReflect.Descriptor instead. func (*PlanRequest) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{18} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{19} } func (x *PlanRequest) GetMetadata() *Metadata { @@ -1793,7 +1876,7 @@ type PlanComplete struct { func (x *PlanComplete) Reset() { *x = PlanComplete{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[19] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1806,7 +1889,7 @@ func (x *PlanComplete) String() string { func (*PlanComplete) ProtoMessage() {} func (x *PlanComplete) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[19] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1819,7 +1902,7 @@ func (x *PlanComplete) ProtoReflect() protoreflect.Message { // Deprecated: Use PlanComplete.ProtoReflect.Descriptor instead. func (*PlanComplete) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{19} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{20} } func (x *PlanComplete) GetError() string { @@ -1863,7 +1946,7 @@ type ApplyRequest struct { func (x *ApplyRequest) Reset() { *x = ApplyRequest{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[20] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1876,7 +1959,7 @@ func (x *ApplyRequest) String() string { func (*ApplyRequest) ProtoMessage() {} func (x *ApplyRequest) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[20] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1889,7 +1972,7 @@ func (x *ApplyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyRequest.ProtoReflect.Descriptor instead. func (*ApplyRequest) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{20} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{21} } func (x *ApplyRequest) GetMetadata() *Metadata { @@ -1915,7 +1998,7 @@ type ApplyComplete struct { func (x *ApplyComplete) Reset() { *x = ApplyComplete{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[21] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1928,7 +2011,7 @@ func (x *ApplyComplete) String() string { func (*ApplyComplete) ProtoMessage() {} func (x *ApplyComplete) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[21] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1941,7 +2024,7 @@ func (x *ApplyComplete) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyComplete.ProtoReflect.Descriptor instead. func (*ApplyComplete) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{21} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{22} } func (x *ApplyComplete) GetState() []byte { @@ -1989,7 +2072,7 @@ type CancelRequest struct { func (x *CancelRequest) Reset() { *x = CancelRequest{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[22] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2002,7 +2085,7 @@ func (x *CancelRequest) String() string { func (*CancelRequest) ProtoMessage() {} func (x *CancelRequest) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[22] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2015,7 +2098,7 @@ func (x *CancelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CancelRequest.ProtoReflect.Descriptor instead. func (*CancelRequest) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{22} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{23} } type Request struct { @@ -2036,7 +2119,7 @@ type Request struct { func (x *Request) Reset() { *x = Request{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[23] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2049,7 +2132,7 @@ func (x *Request) String() string { func (*Request) ProtoMessage() {} func (x *Request) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[23] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2062,7 +2145,7 @@ func (x *Request) ProtoReflect() protoreflect.Message { // Deprecated: Use Request.ProtoReflect.Descriptor instead. func (*Request) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{23} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{24} } func (m *Request) GetType() isRequest_Type { @@ -2158,7 +2241,7 @@ type Response struct { func (x *Response) Reset() { *x = Response{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[24] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2171,7 +2254,7 @@ func (x *Response) String() string { func (*Response) ProtoMessage() {} func (x *Response) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[24] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2184,7 +2267,7 @@ func (x *Response) ProtoReflect() protoreflect.Message { // Deprecated: Use Response.ProtoReflect.Descriptor instead. func (*Response) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{24} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{25} } func (m *Response) GetType() isResponse_Type { @@ -2265,7 +2348,7 @@ type Agent_Metadata struct { func (x *Agent_Metadata) Reset() { *x = Agent_Metadata{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[25] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2278,7 +2361,7 @@ func (x *Agent_Metadata) String() string { func (*Agent_Metadata) ProtoMessage() {} func (x *Agent_Metadata) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[25] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2343,7 +2426,7 @@ type Resource_Metadata struct { func (x *Resource_Metadata) Reset() { *x = Resource_Metadata{} if protoimpl.UnsafeEnabled { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[27] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2356,7 +2439,7 @@ func (x *Resource_Metadata) String() string { func (*Resource_Metadata) ProtoMessage() {} func (x *Resource_Metadata) ProtoReflect() protoreflect.Message { - mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[27] + mi := &file_provisionersdk_proto_provisioner_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2369,7 +2452,7 @@ func (x *Resource_Metadata) ProtoReflect() protoreflect.Message { // Deprecated: Use Resource_Metadata.ProtoReflect.Descriptor instead. func (*Resource_Metadata) Descriptor() ([]byte, []int) { - return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{13, 0} + return file_provisionersdk_proto_provisioner_proto_rawDescGZIP(), []int{14, 0} } func (x *Resource_Metadata) GetKey() string { @@ -2488,290 +2571,294 @@ var file_provisionersdk_proto_provisioner_proto_rawDesc = []byte{ 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x61, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xa8, 0x08, 0x0a, 0x05, 0x41, 0x67, 0x65, + 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0xc3, 0x06, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2d, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x45, 0x6e, 0x76, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x03, 0x65, 0x6e, 0x76, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, - 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, - 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x29, 0x0a, 0x10, - 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, - 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, - 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, - 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x64, - 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x24, 0x0a, 0x04, 0x61, 0x70, 0x70, - 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, - 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x52, 0x04, 0x61, 0x70, 0x70, 0x73, 0x12, - 0x16, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, - 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x21, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, - 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, - 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, - 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, - 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x74, 0x72, 0x6f, 0x75, - 0x62, 0x6c, 0x65, 0x73, 0x68, 0x6f, 0x6f, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x75, 0x72, 0x6c, 0x18, - 0x0c, 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, 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, 0x12, 0x27, 0x0a, 0x0f, 0x73, - 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x10, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x53, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x12, 0x45, 0x0a, 0x1f, 0x73, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, - 0x5f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, - 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x11, 0x20, 0x01, 0x28, 0x05, 0x52, 0x1c, 0x73, - 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x54, 0x69, 0x6d, - 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x37, 0x0a, 0x08, 0x6d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x12, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x67, 0x65, 0x6e, - 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x36, 0x0a, 0x17, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x5f, - 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x5f, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x18, - 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x53, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x42, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x12, 0x3b, 0x0a, 0x0c, - 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x70, 0x70, 0x73, 0x18, 0x14, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x52, 0x0b, 0x64, 0x69, - 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x1a, 0x8d, 0x01, 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, 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, 0x16, 0x0a, 0x06, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, - 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 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, 0x4a, 0x04, 0x08, 0x0e, 0x10, 0x0f, 0x52, - 0x12, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x5f, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x5f, 0x72, 0x65, - 0x61, 0x64, 0x79, 0x22, 0xc6, 0x01, 0x0a, 0x0b, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, - 0x70, 0x70, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x06, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x76, - 0x73, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x73, 0x69, - 0x64, 0x65, 0x72, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x77, 0x65, 0x62, 0x5f, 0x74, 0x65, 0x72, 0x6d, - 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x77, 0x65, 0x62, 0x54, - 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x5f, 0x68, - 0x65, 0x6c, 0x70, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x73, 0x68, - 0x48, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x12, 0x34, 0x0a, 0x16, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x66, - 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x6f, 0x72, 0x77, - 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x48, 0x65, 0x6c, 0x70, 0x65, 0x72, 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, 0xcf, 0x04, 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, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, - 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x21, 0x77, 0x6f, 0x72, 0x6b, 0x73, - 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6f, 0x69, 0x64, 0x63, 0x5f, - 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, - 0x65, 0x72, 0x4f, 0x69, 0x64, 0x63, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x12, 0x41, 0x0a, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, - 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, - 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, - 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, - 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, - 0x61, 0x74, 0x65, 0x49, 0x64, 0x22, 0x8a, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x12, 0x36, 0x0a, 0x17, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x15, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x41, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x32, - 0x0a, 0x15, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x5f, 0x6c, 0x6f, - 0x67, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, - 0x65, 0x6c, 0x22, 0x0e, 0x0a, 0x0c, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x22, 0x8b, 0x01, 0x0a, 0x0d, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, - 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, - 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, - 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, - 0x69, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, - 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, - 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, - 0x22, 0xa6, 0x02, 0x0a, 0x0b, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, - 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 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, 0x02, 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, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, - 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, - 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x4a, 0x0a, - 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, - 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0xc3, 0x01, 0x0a, 0x0c, 0x50, 0x6c, - 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x18, 0x01, 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, 0x02, 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, 0x03, 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, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x67, - 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, - 0x41, 0x0a, 0x0c, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x31, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x22, 0xda, 0x01, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 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, 0x2c, 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x67, - 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, - 0x0f, 0x0a, 0x0d, 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x22, 0x8c, 0x02, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x06, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x48, 0x00, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, 0x0a, 0x05, 0x70, - 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2e, - 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, - 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x31, - 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, - 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, - 0x79, 0x12, 0x34, 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, - 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, - 0xd1, 0x01, 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, 0x32, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, - 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, - 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, - 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, - 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 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, 0x49, 0x0a, 0x0b, 0x50, 0x72, - 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x3a, 0x0a, 0x07, 0x53, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, - 0x65, 0x72, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x70, 0x72, 0x6f, - 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, - 0x76, 0x32, 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, + 0x52, 0x03, 0x65, 0x6e, 0x76, 0x12, 0x29, 0x0a, 0x10, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6e, 0x67, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, + 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, + 0x74, 0x75, 0x72, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, + 0x72, 0x79, 0x12, 0x24, 0x0a, 0x04, 0x61, 0x70, 0x70, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, + 0x70, 0x70, 0x52, 0x04, 0x61, 0x70, 0x70, 0x73, 0x12, 0x16, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x12, 0x21, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, + 0x65, 0x49, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, + 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, + 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x74, 0x72, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x73, 0x68, 0x6f, 0x6f, + 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0c, 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, 0x12, + 0x37, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x12, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x41, 0x67, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x3b, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, + 0x6c, 0x61, 0x79, 0x5f, 0x61, 0x70, 0x70, 0x73, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x44, 0x69, 0x73, + 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, + 0x79, 0x41, 0x70, 0x70, 0x73, 0x12, 0x2d, 0x0a, 0x07, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x73, + 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x52, 0x07, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x73, 0x1a, 0x8d, 0x01, 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, 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, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x12, 0x1a, + 0x0a, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x08, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, + 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x69, 0x6d, + 0x65, 0x6f, 0x75, 0x74, 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, 0x4a, 0x04, 0x08, 0x0e, 0x10, 0x0f, 0x52, 0x12, 0x6c, 0x6f, 0x67, 0x69, + 0x6e, 0x5f, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x79, 0x22, 0xc6, + 0x01, 0x0a, 0x0b, 0x44, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x41, 0x70, 0x70, 0x73, 0x12, 0x16, + 0x0a, 0x06, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, + 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x27, 0x0a, 0x0f, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, + 0x5f, 0x69, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0e, 0x76, 0x73, 0x63, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, + 0x21, 0x0a, 0x0c, 0x77, 0x65, 0x62, 0x5f, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x77, 0x65, 0x62, 0x54, 0x65, 0x72, 0x6d, 0x69, 0x6e, + 0x61, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x73, 0x68, 0x5f, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x73, 0x73, 0x68, 0x48, 0x65, 0x6c, 0x70, 0x65, + 0x72, 0x12, 0x34, 0x0a, 0x16, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, + 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x68, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x14, 0x70, 0x6f, 0x72, 0x74, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x69, 0x6e, + 0x67, 0x48, 0x65, 0x6c, 0x70, 0x65, 0x72, 0x22, 0x9f, 0x02, 0x0a, 0x06, 0x53, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x73, 0x70, 0x6c, 0x61, + 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x63, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x63, 0x72, 0x6f, 0x6e, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x10, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x4c, 0x6f, + 0x67, 0x69, 0x6e, 0x12, 0x20, 0x0a, 0x0c, 0x72, 0x75, 0x6e, 0x5f, 0x6f, 0x6e, 0x5f, 0x73, 0x74, + 0x61, 0x72, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x72, 0x75, 0x6e, 0x4f, 0x6e, + 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x72, 0x75, 0x6e, 0x5f, 0x6f, 0x6e, 0x5f, + 0x73, 0x74, 0x6f, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x75, 0x6e, 0x4f, + 0x6e, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, + 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, + 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x19, + 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6c, 0x6f, 0x67, 0x50, 0x61, 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, 0xcf, 0x04, 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, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x74, 0x65, 0x6d, + 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0f, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x48, 0x0a, 0x21, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x5f, 0x6f, 0x69, 0x64, 0x63, 0x5f, 0x61, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x4f, + 0x69, 0x64, 0x63, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x41, + 0x0a, 0x1d, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x77, 0x6e, 0x65, + 0x72, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1a, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, + 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, + 0x49, 0x64, 0x22, 0x8a, 0x01, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x36, 0x0a, + 0x17, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x61, 0x72, 0x63, 0x68, 0x69, 0x76, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, + 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x72, + 0x63, 0x68, 0x69, 0x76, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x5f, 0x6c, 0x6f, 0x67, 0x5f, 0x6c, + 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x22, + 0x0e, 0x0a, 0x0c, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, + 0x8b, 0x01, 0x0a, 0x0d, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x4c, 0x0a, 0x12, 0x74, 0x65, 0x6d, 0x70, 0x6c, + 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x54, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, 0x61, 0x62, + 0x6c, 0x65, 0x52, 0x11, 0x74, 0x65, 0x6d, 0x70, 0x6c, 0x61, 0x74, 0x65, 0x56, 0x61, 0x72, 0x69, + 0x61, 0x62, 0x6c, 0x65, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x72, 0x65, 0x61, 0x64, 0x6d, 0x65, 0x22, 0xa6, 0x02, + 0x0a, 0x0b, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, + 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, + 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 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, 0x02, 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, 0x73, 0x12, 0x43, 0x0a, 0x0f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, + 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x56, 0x61, 0x72, + 0x69, 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x0e, 0x76, 0x61, 0x72, 0x69, + 0x61, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x12, 0x4a, 0x0a, 0x12, 0x67, 0x69, + 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, + 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, + 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x47, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, + 0x69, 0x64, 0x65, 0x72, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, + 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0xc3, 0x01, 0x0a, 0x0c, 0x50, 0x6c, 0x61, 0x6e, 0x43, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x18, 0x01, 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, 0x02, 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, 0x03, 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, 0x2c, + 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, + 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0x41, 0x0a, 0x0c, + 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x08, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, + 0xda, 0x01, 0x0a, 0x0d, 0x41, 0x70, 0x70, 0x6c, 0x79, 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, 0x2c, + 0x0a, 0x12, 0x67, 0x69, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x72, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x67, 0x69, 0x74, 0x41, + 0x75, 0x74, 0x68, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0x0f, 0x0a, 0x0d, + 0x43, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x8c, 0x02, + 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x06, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, + 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x31, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x04, 0x70, + 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x72, 0x6f, 0x76, + 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x31, 0x0a, 0x05, 0x61, + 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x12, 0x34, + 0x0a, 0x06, 0x63, 0x61, 0x6e, 0x63, 0x65, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x43, 0x61, 0x6e, + 0x63, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x06, 0x63, 0x61, + 0x6e, 0x63, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0xd1, 0x01, 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, + 0x32, 0x0a, 0x05, 0x70, 0x61, 0x72, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, + 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x50, 0x61, 0x72, + 0x73, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x05, 0x70, 0x61, + 0x72, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x19, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x50, 0x6c, 0x61, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x04, + 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x32, 0x0a, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, + 0x72, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, + 0x00, 0x52, 0x05, 0x61, 0x70, 0x70, 0x6c, 0x79, 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, 0x49, 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x76, 0x69, + 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x12, 0x3a, 0x0a, 0x07, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x14, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x73, + 0x69, 0x6f, 0x6e, 0x65, 0x72, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x28, 0x01, + 0x30, 0x01, 0x42, 0x30, 0x5a, 0x2e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x2f, 0x76, 0x32, 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 ( @@ -2787,7 +2874,7 @@ func file_provisionersdk_proto_provisioner_proto_rawDescGZIP() []byte { } var file_provisionersdk_proto_provisioner_proto_enumTypes = make([]protoimpl.EnumInfo, 3) -var file_provisionersdk_proto_provisioner_proto_msgTypes = make([]protoimpl.MessageInfo, 28) +var file_provisionersdk_proto_provisioner_proto_msgTypes = make([]protoimpl.MessageInfo, 29) var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{ (LogLevel)(0), // 0: provisioner.LogLevel (AppSharingLevel)(0), // 1: provisioner.AppSharingLevel @@ -2803,62 +2890,64 @@ var file_provisionersdk_proto_provisioner_proto_goTypes = []interface{}{ (*GitAuthProvider)(nil), // 11: provisioner.GitAuthProvider (*Agent)(nil), // 12: provisioner.Agent (*DisplayApps)(nil), // 13: provisioner.DisplayApps - (*App)(nil), // 14: provisioner.App - (*Healthcheck)(nil), // 15: provisioner.Healthcheck - (*Resource)(nil), // 16: provisioner.Resource - (*Metadata)(nil), // 17: provisioner.Metadata - (*Config)(nil), // 18: provisioner.Config - (*ParseRequest)(nil), // 19: provisioner.ParseRequest - (*ParseComplete)(nil), // 20: provisioner.ParseComplete - (*PlanRequest)(nil), // 21: provisioner.PlanRequest - (*PlanComplete)(nil), // 22: provisioner.PlanComplete - (*ApplyRequest)(nil), // 23: provisioner.ApplyRequest - (*ApplyComplete)(nil), // 24: provisioner.ApplyComplete - (*CancelRequest)(nil), // 25: provisioner.CancelRequest - (*Request)(nil), // 26: provisioner.Request - (*Response)(nil), // 27: provisioner.Response - (*Agent_Metadata)(nil), // 28: provisioner.Agent.Metadata - nil, // 29: provisioner.Agent.EnvEntry - (*Resource_Metadata)(nil), // 30: provisioner.Resource.Metadata + (*Script)(nil), // 14: provisioner.Script + (*App)(nil), // 15: provisioner.App + (*Healthcheck)(nil), // 16: provisioner.Healthcheck + (*Resource)(nil), // 17: provisioner.Resource + (*Metadata)(nil), // 18: provisioner.Metadata + (*Config)(nil), // 19: provisioner.Config + (*ParseRequest)(nil), // 20: provisioner.ParseRequest + (*ParseComplete)(nil), // 21: provisioner.ParseComplete + (*PlanRequest)(nil), // 22: provisioner.PlanRequest + (*PlanComplete)(nil), // 23: provisioner.PlanComplete + (*ApplyRequest)(nil), // 24: provisioner.ApplyRequest + (*ApplyComplete)(nil), // 25: provisioner.ApplyComplete + (*CancelRequest)(nil), // 26: provisioner.CancelRequest + (*Request)(nil), // 27: provisioner.Request + (*Response)(nil), // 28: provisioner.Response + (*Agent_Metadata)(nil), // 29: provisioner.Agent.Metadata + nil, // 30: provisioner.Agent.EnvEntry + (*Resource_Metadata)(nil), // 31: provisioner.Resource.Metadata } var file_provisionersdk_proto_provisioner_proto_depIdxs = []int32{ 5, // 0: provisioner.RichParameter.options:type_name -> provisioner.RichParameterOption 0, // 1: provisioner.Log.level:type_name -> provisioner.LogLevel - 29, // 2: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry - 14, // 3: provisioner.Agent.apps:type_name -> provisioner.App - 28, // 4: provisioner.Agent.metadata:type_name -> provisioner.Agent.Metadata + 30, // 2: provisioner.Agent.env:type_name -> provisioner.Agent.EnvEntry + 15, // 3: provisioner.Agent.apps:type_name -> provisioner.App + 29, // 4: provisioner.Agent.metadata:type_name -> provisioner.Agent.Metadata 13, // 5: provisioner.Agent.display_apps:type_name -> provisioner.DisplayApps - 15, // 6: provisioner.App.healthcheck:type_name -> provisioner.Healthcheck - 1, // 7: provisioner.App.sharing_level:type_name -> provisioner.AppSharingLevel - 12, // 8: provisioner.Resource.agents:type_name -> provisioner.Agent - 30, // 9: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata - 2, // 10: provisioner.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition - 4, // 11: provisioner.ParseComplete.template_variables:type_name -> provisioner.TemplateVariable - 17, // 12: provisioner.PlanRequest.metadata:type_name -> provisioner.Metadata - 7, // 13: provisioner.PlanRequest.rich_parameter_values:type_name -> provisioner.RichParameterValue - 8, // 14: provisioner.PlanRequest.variable_values:type_name -> provisioner.VariableValue - 11, // 15: provisioner.PlanRequest.git_auth_providers:type_name -> provisioner.GitAuthProvider - 16, // 16: provisioner.PlanComplete.resources:type_name -> provisioner.Resource - 6, // 17: provisioner.PlanComplete.parameters:type_name -> provisioner.RichParameter - 17, // 18: provisioner.ApplyRequest.metadata:type_name -> provisioner.Metadata - 16, // 19: provisioner.ApplyComplete.resources:type_name -> provisioner.Resource - 6, // 20: provisioner.ApplyComplete.parameters:type_name -> provisioner.RichParameter - 18, // 21: provisioner.Request.config:type_name -> provisioner.Config - 19, // 22: provisioner.Request.parse:type_name -> provisioner.ParseRequest - 21, // 23: provisioner.Request.plan:type_name -> provisioner.PlanRequest - 23, // 24: provisioner.Request.apply:type_name -> provisioner.ApplyRequest - 25, // 25: provisioner.Request.cancel:type_name -> provisioner.CancelRequest - 9, // 26: provisioner.Response.log:type_name -> provisioner.Log - 20, // 27: provisioner.Response.parse:type_name -> provisioner.ParseComplete - 22, // 28: provisioner.Response.plan:type_name -> provisioner.PlanComplete - 24, // 29: provisioner.Response.apply:type_name -> provisioner.ApplyComplete - 26, // 30: provisioner.Provisioner.Session:input_type -> provisioner.Request - 27, // 31: provisioner.Provisioner.Session:output_type -> provisioner.Response - 31, // [31:32] is the sub-list for method output_type - 30, // [30:31] is the sub-list for method input_type - 30, // [30:30] is the sub-list for extension type_name - 30, // [30:30] is the sub-list for extension extendee - 0, // [0:30] is the sub-list for field type_name + 14, // 6: provisioner.Agent.scripts:type_name -> provisioner.Script + 16, // 7: provisioner.App.healthcheck:type_name -> provisioner.Healthcheck + 1, // 8: provisioner.App.sharing_level:type_name -> provisioner.AppSharingLevel + 12, // 9: provisioner.Resource.agents:type_name -> provisioner.Agent + 31, // 10: provisioner.Resource.metadata:type_name -> provisioner.Resource.Metadata + 2, // 11: provisioner.Metadata.workspace_transition:type_name -> provisioner.WorkspaceTransition + 4, // 12: provisioner.ParseComplete.template_variables:type_name -> provisioner.TemplateVariable + 18, // 13: provisioner.PlanRequest.metadata:type_name -> provisioner.Metadata + 7, // 14: provisioner.PlanRequest.rich_parameter_values:type_name -> provisioner.RichParameterValue + 8, // 15: provisioner.PlanRequest.variable_values:type_name -> provisioner.VariableValue + 11, // 16: provisioner.PlanRequest.git_auth_providers:type_name -> provisioner.GitAuthProvider + 17, // 17: provisioner.PlanComplete.resources:type_name -> provisioner.Resource + 6, // 18: provisioner.PlanComplete.parameters:type_name -> provisioner.RichParameter + 18, // 19: provisioner.ApplyRequest.metadata:type_name -> provisioner.Metadata + 17, // 20: provisioner.ApplyComplete.resources:type_name -> provisioner.Resource + 6, // 21: provisioner.ApplyComplete.parameters:type_name -> provisioner.RichParameter + 19, // 22: provisioner.Request.config:type_name -> provisioner.Config + 20, // 23: provisioner.Request.parse:type_name -> provisioner.ParseRequest + 22, // 24: provisioner.Request.plan:type_name -> provisioner.PlanRequest + 24, // 25: provisioner.Request.apply:type_name -> provisioner.ApplyRequest + 26, // 26: provisioner.Request.cancel:type_name -> provisioner.CancelRequest + 9, // 27: provisioner.Response.log:type_name -> provisioner.Log + 21, // 28: provisioner.Response.parse:type_name -> provisioner.ParseComplete + 23, // 29: provisioner.Response.plan:type_name -> provisioner.PlanComplete + 25, // 30: provisioner.Response.apply:type_name -> provisioner.ApplyComplete + 27, // 31: provisioner.Provisioner.Session:input_type -> provisioner.Request + 28, // 32: provisioner.Provisioner.Session:output_type -> provisioner.Response + 32, // [32:33] is the sub-list for method output_type + 31, // [31:32] is the sub-list for method input_type + 31, // [31:31] is the sub-list for extension type_name + 31, // [31:31] is the sub-list for extension extendee + 0, // [0:31] is the sub-list for field type_name } func init() { file_provisionersdk_proto_provisioner_proto_init() } @@ -3000,7 +3089,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*App); i { + switch v := v.(*Script); i { case 0: return &v.state case 1: @@ -3012,7 +3101,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Healthcheck); i { + switch v := v.(*App); i { case 0: return &v.state case 1: @@ -3024,7 +3113,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Resource); i { + switch v := v.(*Healthcheck); i { case 0: return &v.state case 1: @@ -3036,7 +3125,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Metadata); i { + switch v := v.(*Resource); i { case 0: return &v.state case 1: @@ -3048,7 +3137,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Config); i { + switch v := v.(*Metadata); i { case 0: return &v.state case 1: @@ -3060,7 +3149,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ParseRequest); i { + switch v := v.(*Config); i { case 0: return &v.state case 1: @@ -3072,7 +3161,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ParseComplete); i { + switch v := v.(*ParseRequest); i { case 0: return &v.state case 1: @@ -3084,7 +3173,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PlanRequest); i { + switch v := v.(*ParseComplete); i { case 0: return &v.state case 1: @@ -3096,7 +3185,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PlanComplete); i { + switch v := v.(*PlanRequest); i { case 0: return &v.state case 1: @@ -3108,7 +3197,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ApplyRequest); i { + switch v := v.(*PlanComplete); i { case 0: return &v.state case 1: @@ -3120,7 +3209,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ApplyComplete); i { + switch v := v.(*ApplyRequest); i { case 0: return &v.state case 1: @@ -3132,7 +3221,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CancelRequest); i { + switch v := v.(*ApplyComplete); i { case 0: return &v.state case 1: @@ -3144,7 +3233,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Request); i { + switch v := v.(*CancelRequest); i { case 0: return &v.state case 1: @@ -3156,7 +3245,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Response); i { + switch v := v.(*Request); i { case 0: return &v.state case 1: @@ -3168,6 +3257,18 @@ func file_provisionersdk_proto_provisioner_proto_init() { } } file_provisionersdk_proto_provisioner_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Response); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_provisionersdk_proto_provisioner_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Agent_Metadata); i { case 0: return &v.state @@ -3179,7 +3280,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { return nil } } - file_provisionersdk_proto_provisioner_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { + file_provisionersdk_proto_provisioner_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*Resource_Metadata); i { case 0: return &v.state @@ -3197,14 +3298,14 @@ func file_provisionersdk_proto_provisioner_proto_init() { (*Agent_Token)(nil), (*Agent_InstanceId)(nil), } - file_provisionersdk_proto_provisioner_proto_msgTypes[23].OneofWrappers = []interface{}{ + file_provisionersdk_proto_provisioner_proto_msgTypes[24].OneofWrappers = []interface{}{ (*Request_Config)(nil), (*Request_Parse)(nil), (*Request_Plan)(nil), (*Request_Apply)(nil), (*Request_Cancel)(nil), } - file_provisionersdk_proto_provisioner_proto_msgTypes[24].OneofWrappers = []interface{}{ + file_provisionersdk_proto_provisioner_proto_msgTypes[25].OneofWrappers = []interface{}{ (*Response_Log)(nil), (*Response_Parse)(nil), (*Response_Plan)(nil), @@ -3216,7 +3317,7 @@ func file_provisionersdk_proto_provisioner_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_provisionersdk_proto_provisioner_proto_rawDesc, NumEnums: 3, - NumMessages: 28, + NumMessages: 29, NumExtensions: 0, NumServices: 1, }, diff --git a/provisionersdk/proto/provisioner.proto b/provisionersdk/proto/provisioner.proto index 30ddca7a89e03..0acf000353b69 100644 --- a/provisionersdk/proto/provisioner.proto +++ b/provisionersdk/proto/provisioner.proto @@ -101,7 +101,7 @@ message Agent { string id = 1; string name = 2; map env = 3; - string startup_script = 4; + // Field 4 was startup_script, now removed. string operating_system = 5; string architecture = 6; string directory = 7; @@ -114,12 +114,11 @@ message Agent { string troubleshooting_url = 12; string motd_file = 13; // Field 14 was bool login_before_ready = 14, now removed. - int32 startup_script_timeout_seconds = 15; - string shutdown_script = 16; - int32 shutdown_script_timeout_seconds = 17; + // Field 15, 16, 17 were related to scripts, which are now removed. repeated Metadata metadata = 18; - string startup_script_behavior = 19; + // Field 19 was startup_script_behavior, now removed. DisplayApps display_apps = 20; + repeated Script scripts = 21; } enum AppSharingLevel { @@ -136,6 +135,19 @@ message DisplayApps { bool port_forwarding_helper = 5; } +// Script represents a script to be run on the workspace. +message Script { + string display_name = 1; + string icon = 2; + string script = 3; + string cron = 4; + bool start_blocks_login = 5; + bool run_on_start = 6; + bool run_on_stop = 7; + int32 timeout_seconds = 8; + string log_path = 9; +} + // App represents a dev-accessible application on the workspace. message App { // slug is the unique identifier for the app, usually the name from the diff --git a/scripts/dbgen/main.go b/scripts/dbgen/main.go index 041bc58c9139f..ac946ff8a51ad 100644 --- a/scripts/dbgen/main.go +++ b/scripts/dbgen/main.go @@ -116,7 +116,7 @@ func generateUniqueConstraints() error { case line == "": case strings.HasSuffix(line, ";"): query += line - if strings.Contains(query, "UNIQUE") { + if strings.Contains(query, "UNIQUE") || strings.Contains(query, "PRIMARY KEY") { uniqueConstraints = append(uniqueConstraints, query) } query = "" diff --git a/site/e2e/helpers.ts b/site/e2e/helpers.ts index af39914db5a77..548ac9168624b 100644 --- a/site/e2e/helpers.ts +++ b/site/e2e/helpers.ts @@ -473,6 +473,7 @@ const createTemplateVersionTar = async ( env: {}, id: randomUUID(), metadata: [], + scripts: [], motdFile: "", name: "dev", operatingSystem: "linux", diff --git a/site/e2e/provisionerGenerated.ts b/site/e2e/provisionerGenerated.ts index f22288c8e4bd2..abd81ad7aacc4 100644 --- a/site/e2e/provisionerGenerated.ts +++ b/site/e2e/provisionerGenerated.ts @@ -104,7 +104,7 @@ export interface Agent { id: string; name: string; env: { [key: string]: string }; - startupScript: string; + /** Field 4 was startup_script, now removed. */ operatingSystem: string; architecture: string; directory: string; @@ -114,13 +114,14 @@ export interface Agent { connectionTimeoutSeconds: number; troubleshootingUrl: string; motdFile: string; - /** Field 14 was bool login_before_ready = 14, now removed. */ - startupScriptTimeoutSeconds: number; - shutdownScript: string; - shutdownScriptTimeoutSeconds: number; + /** + * Field 14 was bool login_before_ready = 14, now removed. + * Field 15, 16, 17 were related to scripts, which are now removed. + */ metadata: Agent_Metadata[]; - startupScriptBehavior: string; + /** Field 19 was startup_script_behavior, now removed. */ displayApps: DisplayApps | undefined; + scripts: Script[]; } export interface Agent_Metadata { @@ -144,6 +145,19 @@ export interface DisplayApps { portForwardingHelper: boolean; } +/** Script represents a script to be run on the workspace. */ +export interface Script { + displayName: string; + icon: string; + script: string; + cron: string; + startBlocksLogin: boolean; + runOnStart: boolean; + runOnStop: boolean; + timeoutSeconds: number; + logPath: string; +} + /** App represents a dev-accessible application on the workspace. */ export interface App { /** @@ -470,9 +484,6 @@ export const Agent = { writer.uint32(26).fork(), ).ldelim(); }); - if (message.startupScript !== "") { - writer.uint32(34).string(message.startupScript); - } if (message.operatingSystem !== "") { writer.uint32(42).string(message.operatingSystem); } @@ -500,27 +511,18 @@ export const Agent = { if (message.motdFile !== "") { writer.uint32(106).string(message.motdFile); } - if (message.startupScriptTimeoutSeconds !== 0) { - writer.uint32(120).int32(message.startupScriptTimeoutSeconds); - } - if (message.shutdownScript !== "") { - writer.uint32(130).string(message.shutdownScript); - } - if (message.shutdownScriptTimeoutSeconds !== 0) { - writer.uint32(136).int32(message.shutdownScriptTimeoutSeconds); - } for (const v of message.metadata) { Agent_Metadata.encode(v!, writer.uint32(146).fork()).ldelim(); } - if (message.startupScriptBehavior !== "") { - writer.uint32(154).string(message.startupScriptBehavior); - } if (message.displayApps !== undefined) { DisplayApps.encode( message.displayApps, writer.uint32(162).fork(), ).ldelim(); } + for (const v of message.scripts) { + Script.encode(v!, writer.uint32(170).fork()).ldelim(); + } return writer; }, }; @@ -588,6 +590,42 @@ export const DisplayApps = { }, }; +export const Script = { + encode( + message: Script, + writer: _m0.Writer = _m0.Writer.create(), + ): _m0.Writer { + if (message.displayName !== "") { + writer.uint32(10).string(message.displayName); + } + if (message.icon !== "") { + writer.uint32(18).string(message.icon); + } + if (message.script !== "") { + writer.uint32(26).string(message.script); + } + if (message.cron !== "") { + writer.uint32(34).string(message.cron); + } + if (message.startBlocksLogin === true) { + writer.uint32(40).bool(message.startBlocksLogin); + } + if (message.runOnStart === true) { + writer.uint32(48).bool(message.runOnStart); + } + if (message.runOnStop === true) { + writer.uint32(56).bool(message.runOnStop); + } + if (message.timeoutSeconds !== 0) { + writer.uint32(64).int32(message.timeoutSeconds); + } + if (message.logPath !== "") { + writer.uint32(74).string(message.logPath); + } + return writer; + }, +}; + export const App = { encode(message: App, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { if (message.slug !== "") { diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index cd272eb99b12c..7c10879ebe3ff 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1307,9 +1307,6 @@ export interface WorkspaceAgent { readonly architecture: string; readonly environment_variables: Record; readonly operating_system: string; - readonly startup_script?: string; - readonly startup_script_behavior: WorkspaceAgentStartupScriptBehavior; - readonly startup_script_timeout_seconds: number; readonly logs_length: number; readonly logs_overflowed: boolean; readonly directory?: string; @@ -1319,12 +1316,12 @@ export interface WorkspaceAgent { readonly latency?: Record; readonly connection_timeout_seconds: number; readonly troubleshooting_url: string; - readonly login_before_ready: boolean; - readonly shutdown_script?: string; - readonly shutdown_script_timeout_seconds: number; readonly subsystems: AgentSubsystem[]; readonly health: WorkspaceAgentHealth; readonly display_apps: DisplayApp[]; + readonly log_sources: WorkspaceAgentLogSource[]; + readonly scripts: WorkspaceAgentScript[]; + readonly startup_script_behavior: WorkspaceAgentStartupScriptBehavior; } // From codersdk/workspaceagents.go @@ -1351,6 +1348,16 @@ export interface WorkspaceAgentLog { readonly created_at: string; readonly output: string; readonly level: LogLevel; + readonly source_id: string; +} + +// From codersdk/workspaceagents.go +export interface WorkspaceAgentLogSource { + readonly workspace_agent_id: string; + readonly id: string; + readonly created_at: string; + readonly display_name: string; + readonly icon: string; } // From codersdk/workspaceagents.go @@ -1376,6 +1383,18 @@ export interface WorkspaceAgentMetadataResult { readonly error: string; } +// From codersdk/workspaceagents.go +export interface WorkspaceAgentScript { + readonly log_source_id: string; + readonly log_path: string; + readonly script: string; + readonly cron: string; + readonly run_on_start: boolean; + readonly run_on_stop: boolean; + readonly start_blocks_login: boolean; + readonly timeout: number; +} + // From codersdk/workspaceapps.go export interface WorkspaceApp { readonly id: string; @@ -1865,23 +1884,6 @@ export const WorkspaceAgentLifecycles: WorkspaceAgentLifecycle[] = [ "starting", ]; -// From codersdk/workspaceagents.go -export type WorkspaceAgentLogSource = - | "envbox" - | "envbuilder" - | "external" - | "kubernetes" - | "shutdown_script" - | "startup_script"; -export const WorkspaceAgentLogSources: WorkspaceAgentLogSource[] = [ - "envbox", - "envbuilder", - "external", - "kubernetes", - "shutdown_script", - "startup_script", -]; - // From codersdk/workspaceagents.go export type WorkspaceAgentStartupScriptBehavior = "blocking" | "non-blocking"; export const WorkspaceAgentStartupScriptBehaviors: WorkspaceAgentStartupScriptBehavior[] = diff --git a/site/src/components/Resources/AgentRow.stories.tsx b/site/src/components/Resources/AgentRow.stories.tsx index 5c6e4e00345b4..1c73aa7f789f0 100644 --- a/site/src/components/Resources/AgentRow.stories.tsx +++ b/site/src/components/Resources/AgentRow.stories.tsx @@ -16,6 +16,7 @@ import { MockWorkspaceAgentTimeout, MockWorkspaceApp, MockProxyLatencies, + MockWorkspaceAgentLogSource, } from "testHelpers/entities"; import { AgentRow, LineWithID } from "./AgentRow"; import { ProxyContext, getPreferredProxy } from "contexts/ProxyContext"; @@ -95,6 +96,7 @@ const storybookLogs: LineWithID[] = [ level: "info", output: line, time: "", + source_id: MockWorkspaceAgentLogSource.id, })); const meta: Meta = { @@ -105,8 +107,6 @@ const meta: Meta = { agent: { ...MockWorkspaceAgent, logs_length: storybookLogs.length, - startup_script: - 'set -eux -o pipefail\n\n# install and start code-server\ncurl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/tmp/code-server --version 4.8.3\n/tmp/code-server/bin/code-server --auth none --port 13337 >/tmp/code-server.log 2>&1 &\n\n\nif [ ! -d ~/coder ]; then\n mkdir -p ~/coder\n\n git clone https://github.com/coder/coder ~/coder\nfi\n\nsudo service docker start\nDOTFILES_URI=" "\nrm -f ~/.personalize.log\nif [ -n "${DOTFILES_URI// }" ]; then\n coder dotfiles "$DOTFILES_URI" -y 2>&1 | tee -a ~/.personalize.log\nfi\nif [ -x ~/personalize ]; then\n ~/personalize 2>&1 | tee -a ~/.personalize.log\nelif [ -f ~/personalize ]; then\n echo "~/personalize is not executable, skipping..." | tee -a ~/.personalize.log\nfi\n', }, workspace: MockWorkspace, showApps: true, diff --git a/site/src/components/Resources/AgentRow.tsx b/site/src/components/Resources/AgentRow.tsx index c9ad3312d69ed..737d84852dd2e 100644 --- a/site/src/components/Resources/AgentRow.tsx +++ b/site/src/components/Resources/AgentRow.tsx @@ -1,19 +1,26 @@ -import Popover from "@mui/material/Popover"; -import { makeStyles, useTheme } from "@mui/styles"; +import Collapse from "@mui/material/Collapse"; import Skeleton from "@mui/material/Skeleton"; +import Tooltip from "@mui/material/Tooltip"; +import { makeStyles } from "@mui/styles"; import * as API from "api/api"; -import CodeOutlined from "@mui/icons-material/CodeOutlined"; +import { + Workspace, + WorkspaceAgent, + WorkspaceAgentLogSource, + WorkspaceAgentMetadata, +} from "api/typesGenerated"; import { CloseDropdown, OpenDropdown, } from "components/DropdownArrows/DropdownArrows"; +import { displayError } from "components/GlobalSnackbar/utils"; +import { VSCodeDesktopButton } from "components/Resources/VSCodeDesktopButton/VSCodeDesktopButton"; import { Line, LogLine, logLineHeight, } from "components/WorkspaceBuildLogs/Logs"; -import { PortForwardButton } from "./PortForwardButton"; -import { VSCodeDesktopButton } from "components/Resources/VSCodeDesktopButton/VSCodeDesktopButton"; +import { useProxy } from "contexts/ProxyContext"; import { FC, useCallback, @@ -23,28 +30,19 @@ import { useRef, useState, } from "react"; -import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; -import { darcula } from "react-syntax-highlighter/dist/cjs/styles/prism"; import AutoSizer from "react-virtualized-auto-sizer"; import { FixedSizeList as List, ListOnScrollProps } from "react-window"; import { colors } from "theme/colors"; import { combineClasses } from "utils/combineClasses"; -import { - Workspace, - WorkspaceAgent, - WorkspaceAgentMetadata, -} from "api/typesGenerated"; -import { AppLink } from "./AppLink/AppLink"; -import { SSHButton } from "./SSHButton/SSHButton"; import { Stack } from "../Stack/Stack"; -import { TerminalLink } from "./TerminalLink/TerminalLink"; import { AgentLatency } from "./AgentLatency"; import { AgentMetadata } from "./AgentMetadata"; -import { AgentVersion } from "./AgentVersion"; import { AgentStatus } from "./AgentStatus"; -import Collapse from "@mui/material/Collapse"; -import { useProxy } from "contexts/ProxyContext"; -import { displayError } from "components/GlobalSnackbar/utils"; +import { AgentVersion } from "./AgentVersion"; +import { AppLink } from "./AppLink/AppLink"; +import { PortForwardButton } from "./PortForwardButton"; +import { SSHButton } from "./SSHButton/SSHButton"; +import { TerminalLink } from "./TerminalLink/TerminalLink"; // Logs are stored as the Line interface to make rendering // much more efficient. Instead of mapping objects each time, we're @@ -81,14 +79,18 @@ export const AgentRow: FC = ({ storybookLogs, }) => { const styles = useStyles(); - const theme = useTheme(); - const startupScriptAnchorRef = useRef(null); - const [startupScriptOpen, setStartupScriptOpen] = useState(false); const hasAppsToDisplay = !hideVSCodeDesktopButton || agent.apps.length > 0; const shouldDisplayApps = showApps && ((agent.status === "connected" && hasAppsToDisplay) || agent.status === "connecting"); + const logSourceByID = useMemo(() => { + const sources: { [id: string]: WorkspaceAgentLogSource } = {}; + for (const source of agent.log_sources) { + sources[source.id] = source; + } + return sources; + }, [agent.log_sources]); const hasStartupFeatures = Boolean(agent.logs_length); const { proxy } = useProxy(); const [showLogs, setShowLogs] = useState( @@ -111,6 +113,7 @@ export const AgentRow: FC = ({ level: "error", output: "Startup logs exceeded the max size of 1MB!", time: new Date().toISOString(), + source_id: "", }); } return logs; @@ -292,13 +295,123 @@ export const AgentRow: FC = ({ className={styles.startupLogs} onScroll={handleLogScroll} > - {({ index, style }) => ( - - )} + {({ index, style }) => { + const log = startupLogs[index]; + const sourceIcon: string | undefined = + logSourceByID[log.source_id].icon; + + let assignedIcon = false; + let icon: JSX.Element; + // If no icon is specified, we show a deterministic + // colored circle to identify unique scripts. + if (sourceIcon) { + icon = ( + + ); + } else { + icon = ( +
+ ); + assignedIcon = true; + } + + let nextChangesSource = false; + if (index < startupLogs.length - 1) { + nextChangesSource = + logSourceByID[startupLogs[index + 1].source_id].id !== + log.source_id; + } + // We don't want every line to repeat the icon, because + // that is ugly and repetitive. This removes the icon + // for subsequent lines of the same source and shows a + // line instead, visually indicating they are from the + // same source. + if ( + index > 0 && + logSourceByID[startupLogs[index - 1].source_id].id === + log.source_id + ) { + icon = ( +
+
+ {nextChangesSource && ( +
+ )} +
+ ); + } + + return ( + + {logSourceByID[log.source_id].display_name} + {assignedIcon && ( + +
+ No icon specified! +
+ )} + + } + > + {icon} + + } + /> + ); + }} )} @@ -316,7 +429,7 @@ export const AgentRow: FC = ({ }} > - Hide startup logs + Hide logs ) : ( )} - - - - setStartupScriptOpen(false)} - anchorEl={startupScriptAnchorRef.current} - > -
- - {agent.startup_script || ""} - -
-
)} @@ -390,6 +462,7 @@ const useAgentLogs = ( useEffect(() => { if (!enabled) { socket.current?.close(); + setLogs([]); return; } @@ -403,6 +476,7 @@ const useAgentLogs = ( level: log.level || "info", output: log.output, time: log.created_at, + source_id: log.source_id, })); if (!previousLogs) { @@ -539,10 +613,6 @@ const useStyles = makeStyles((theme) => ({ }, }, - startupScriptPopover: { - backgroundColor: theme.palette.background.default, - }, - agentNameAndStatus: { display: "flex", alignItems: "center", @@ -631,14 +701,30 @@ const useStyles = makeStyles((theme) => ({ color: theme.palette.warning.light, }, - scriptButton: { - "& svg": { - width: theme.spacing(2), - height: theme.spacing(2), - }, - }, - agentOS: { textTransform: "capitalize", }, })); + +// These colors were picked at random. Feel free +// to add more, adjust, or change! Users will not +// depend on these colors. +const scriptDisplayColors = [ + "#85A3B2", + "#A37EB2", + "#C29FDE", + "#90B3D7", + "#829AC7", + "#728B8E", + "#506080", + "#5654B0", + "#6B56D6", + "#7847CC", +]; + +const determineScriptDisplayColor = (displayName: string): string => { + const hash = displayName.split("").reduce((hash, char) => { + return (hash << 5) + hash + char.charCodeAt(0); // bit-shift and add for our simple hash + }, 0); + return scriptDisplayColors[Math.abs(hash) % scriptDisplayColors.length]; +}; diff --git a/site/src/components/Resources/AgentStatus.tsx b/site/src/components/Resources/AgentStatus.tsx index 6b67e957e33ea..c8179249d1ac5 100644 --- a/site/src/components/Resources/AgentStatus.tsx +++ b/site/src/components/Resources/AgentStatus.tsx @@ -244,39 +244,39 @@ const OffLifecycle: React.FC = () => { const ConnectedStatus: React.FC<{ agent: WorkspaceAgent; }> = ({ agent }) => { - switch (agent.startup_script_behavior) { - case "non-blocking": - return ; - case "blocking": - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - ); + // This is to support legacy agents that do not support + // reporting the lifecycle_state field. + if (agent.scripts.length === 0) { + return ; } + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + ); }; const DisconnectedStatus: React.FC = () => { diff --git a/site/src/components/WorkspaceBuildLogs/Logs.tsx b/site/src/components/WorkspaceBuildLogs/Logs.tsx index ca1830a865571..7b61183b79726 100644 --- a/site/src/components/WorkspaceBuildLogs/Logs.tsx +++ b/site/src/components/WorkspaceBuildLogs/Logs.tsx @@ -10,6 +10,7 @@ export interface Line { time: string; output: string; level: LogLevel; + source_id: string; } export interface LogsProps { @@ -55,7 +56,9 @@ export const LogLine: FC<{ hideTimestamp?: boolean; number?: number; style?: React.CSSProperties; -}> = ({ line, hideTimestamp, number, style }) => { + sourceIcon?: JSX.Element; + maxNumber?: number; +}> = ({ line, hideTimestamp, number, maxNumber, sourceIcon, style }) => { const styles = useStyles(); const output = useMemo(() => { return convert.toHtml(line.output.split(/\r/g).pop() as string); @@ -71,6 +74,7 @@ export const LogLine: FC<{ ])} style={style} > + {sourceIcon} {!hideTimestamp && ( <> {number ? number : dayjs(line.time).format(`HH:mm:ss.SSS`)} @@ -112,6 +119,7 @@ const useStyles = makeStyles((theme) => ({ line: { wordBreak: "break-all", display: "flex", + alignItems: "center", fontSize: 14, color: theme.palette.text.primary, fontFamily: MONOSPACE_FONT_FAMILY, @@ -143,7 +151,6 @@ const useStyles = makeStyles((theme) => ({ }, time: { userSelect: "none", - width: theme.spacing(12.5), whiteSpace: "pre", display: "inline-block", color: theme.palette.text.secondary, diff --git a/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx b/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx index 543be745af3d7..00082937cbd77 100644 --- a/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx +++ b/site/src/components/WorkspaceBuildLogs/WorkspaceBuildLogs.tsx @@ -72,6 +72,7 @@ export const WorkspaceBuildLogs: FC = ({ time: log.created_at, output: log.output, level: log.log_level, + source_id: log.log_source, })); const duration = getStageDurationInSeconds(logs); const shouldDisplayDuration = duration !== undefined; diff --git a/site/src/pages/TerminalPage/TerminalPageAlert.tsx b/site/src/pages/TerminalPage/TerminalPageAlert.tsx index 13199a0911366..6c5e3862fb622 100644 --- a/site/src/pages/TerminalPage/TerminalPageAlert.tsx +++ b/site/src/pages/TerminalPage/TerminalPageAlert.tsx @@ -53,7 +53,7 @@ const mapAlertTypeToText: MapAlertTypeToComponent = { severity: "info", children: ( <> - Startup script is still running. You can continue using this terminal, + Startup scripts are still running. You can continue using this terminal, but{" "} - Startup script has completed successfully. The workspace is ready but + Startup scripts have completed successfully. The workspace is ready but this{" "}