mirror of
https://github.com/coder/coder.git
synced 2026-06-03 13:08:25 +00:00
ae492495ee
## Problem Follow-on to: - https://github.com/coder/coder/pull/25089 `coder exp sync start` still printed a generic success message when the unit was ready on the first status check. That hid whether the unit had no dependencies or had dependencies that were already satisfied before `sync start` ran. Before: ```text Success ``` ## Solution Print explicit startup output for both ready-at-first-check cases. After, dependencies already satisfied: ```text Unit "test-unit" started immediately, dependencies already satisfied: [dep-unit, dep-unit-2] ``` After, no dependencies: ```text Unit "test-unit" started with no dependencies ``` The existing waiting path is unchanged and still reports the dependencies while waiting and after waiting finishes. Co-authored-by: Sas Swart <sas.swart.cdk@gmail.com>
125 lines
3.4 KiB
Go
125 lines
3.4 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/coder/coder/v2/agent/agentsocket"
|
|
"github.com/coder/coder/v2/agent/unit"
|
|
"github.com/coder/coder/v2/cli/cliui"
|
|
"github.com/coder/serpent"
|
|
)
|
|
|
|
const (
|
|
syncPollInterval = 1 * time.Second
|
|
)
|
|
|
|
func (*RootCmd) syncStart(socketPath *string) *serpent.Command {
|
|
var timeout time.Duration
|
|
|
|
cmd := &serpent.Command{
|
|
Use: "start <unit>",
|
|
Short: "Wait until all unit dependencies are satisfied",
|
|
Long: "Wait until all dependencies are satisfied, consider the unit to have started, then allow it to proceed. This command polls until dependencies are ready, then marks the unit as started.",
|
|
Handler: func(i *serpent.Invocation) error {
|
|
ctx := i.Context()
|
|
|
|
if len(i.Args) != 1 {
|
|
return xerrors.New("exactly one unit name is required")
|
|
}
|
|
unitName := unit.ID(i.Args[0])
|
|
|
|
if timeout > 0 {
|
|
var cancel context.CancelFunc
|
|
ctx, cancel = context.WithTimeout(ctx, timeout)
|
|
defer cancel()
|
|
}
|
|
|
|
opts := []agentsocket.Option{}
|
|
if *socketPath != "" {
|
|
opts = append(opts, agentsocket.WithPath(*socketPath))
|
|
}
|
|
|
|
client, err := agentsocket.NewClient(ctx, opts...)
|
|
if err != nil {
|
|
return xerrors.Errorf("connect to agent socket: %w", err)
|
|
}
|
|
defer client.Close()
|
|
|
|
statusResp, err := client.SyncStatus(ctx, unitName)
|
|
if err != nil {
|
|
return xerrors.Errorf("get status failed: %w", err)
|
|
}
|
|
ready := statusResp.IsReady
|
|
|
|
var allDependencies []string
|
|
var unsatisfiedDependencies []string
|
|
for _, dep := range statusResp.Dependencies {
|
|
allDependencies = append(allDependencies, string(dep.DependsOn))
|
|
if !dep.IsSatisfied {
|
|
unsatisfiedDependencies = append(unsatisfiedDependencies, string(dep.DependsOn))
|
|
}
|
|
}
|
|
slices.Sort(allDependencies)
|
|
slices.Sort(unsatisfiedDependencies)
|
|
|
|
if !ready {
|
|
waitedForList := strings.Join(unsatisfiedDependencies, ", ")
|
|
|
|
cliui.Infof(i.Stdout, "Unit %q is waiting for dependencies to be satisfied: [%s]", unitName, waitedForList)
|
|
|
|
ticker := time.NewTicker(syncPollInterval)
|
|
defer ticker.Stop()
|
|
|
|
pollLoop:
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
if ctx.Err() == context.DeadlineExceeded {
|
|
return xerrors.Errorf("timeout waiting for dependencies of unit '%s'", unitName)
|
|
}
|
|
return ctx.Err()
|
|
case <-ticker.C:
|
|
ready, err := client.SyncReady(ctx, unitName)
|
|
if err != nil {
|
|
return xerrors.Errorf("error checking dependencies: %w", err)
|
|
}
|
|
if ready {
|
|
break pollLoop
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := client.SyncStart(ctx, unitName); err != nil {
|
|
return xerrors.Errorf("start unit failed: %w", err)
|
|
}
|
|
|
|
switch {
|
|
case len(allDependencies) == 0:
|
|
cliui.Info(i.Stdout, fmt.Sprintf("Unit %q started with no dependencies", unitName))
|
|
case len(unsatisfiedDependencies) == 0:
|
|
cliui.Info(i.Stdout, fmt.Sprintf("Unit %q started immediately, dependencies already satisfied: [%s]", unitName, strings.Join(allDependencies, ", ")))
|
|
default:
|
|
cliui.Info(i.Stdout, fmt.Sprintf("Unit %q finished waiting for dependencies: [%s]", unitName, strings.Join(unsatisfiedDependencies, ", ")))
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
cmd.Options = append(cmd.Options, serpent.Option{
|
|
Flag: "timeout",
|
|
Description: "Maximum time to wait for dependencies (e.g., 30s, 5m). 5m by default.",
|
|
Value: serpent.DurationOf(&timeout),
|
|
Default: "5m",
|
|
})
|
|
|
|
return cmd
|
|
}
|