mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
882689a888
## Summary Backports #25089 to `release/2.32` so `coder exp sync want` and `coder exp sync start` print the dependency units involved in startup coordination instead of generic success messages. ## Validation - `git diff --check origin/release/2.32..HEAD` - `go test ./cli -run TestSyncCommands -count=1` > [!NOTE] > `make test RUN=TestSyncCommands` hit an unrelated `codersdk/toolsdk` filtered-test failure because that package expects all tools to be tested. The affected CLI test passed with the package-scoped command above. > 🤖 This PR was created with the help of Coder Agents, and needs a human review. 🧑💻 Co-authored-by: Max Schwenk <maschwenk@gmail.com>
118 lines
2.9 KiB
Go
118 lines
2.9 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 waitedFor []string
|
|
if !ready {
|
|
for _, dep := range statusResp.Dependencies {
|
|
if !dep.IsSatisfied {
|
|
waitedFor = append(waitedFor, string(dep.DependsOn))
|
|
}
|
|
}
|
|
slices.Sort(waitedFor)
|
|
waitedForList := strings.Join(waitedFor, ", ")
|
|
|
|
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)
|
|
}
|
|
|
|
if len(waitedFor) == 0 {
|
|
cliui.Info(i.Stdout, "Success")
|
|
} else {
|
|
cliui.Info(i.Stdout, fmt.Sprintf("Unit %q finished waiting for dependencies: [%s]", unitName, strings.Join(waitedFor, ", ")))
|
|
}
|
|
|
|
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
|
|
}
|