fix(cli): show sync wait dependencies (#25089)

## Problem
`coder exp sync want` and `coder exp sync start` both printed generic
success messages, which hid the dependency units involved in startup
coordination.

Before, declaring dependencies with `sync want` printed:

```text
Success
```

Before, `sync start` printed while waiting, then finished with another
generic success message:

```text
Waiting for dependencies of unit 'test-unit' to be satisfied...
Success
```

## Solution
Print the dependency units in both cases, using wording that matches
where the command is in the lifecycle.

After, `sync want` prints the dependencies it declared for the unit:

```text
Unit "test-unit" declared dependencies: [dep-unit]
```

After, `sync start` enumerates the dependencies while it is waiting,
then prints the same dependencies after the unit starts executing:

```text
Unit "test-unit" is waiting for dependencies to be satisfied: [dep-unit, dep-unit-2]
Unit "test-unit" finished waiting for dependencies: [dep-unit, dep-unit-2]
```

The sync golden tests now cover the updated output, including multiple
dependencies for `sync start`.
This commit is contained in:
Max Schwenk
2026-05-14 08:45:20 -04:00
committed by GitHub
parent cb37047dce
commit f3e90b334d
5 changed files with 48 additions and 13 deletions
+20 -3
View File
@@ -2,6 +2,9 @@ package cli
import (
"context"
"fmt"
"slices"
"strings"
"time"
"golang.org/x/xerrors"
@@ -48,13 +51,23 @@ func (*RootCmd) syncStart(socketPath *string) *serpent.Command {
}
defer client.Close()
ready, err := client.SyncReady(ctx, unitName)
statusResp, err := client.SyncStatus(ctx, unitName)
if err != nil {
return xerrors.Errorf("error checking dependencies: %w", err)
return xerrors.Errorf("get status failed: %w", err)
}
ready := statusResp.IsReady
var waitedFor []string
if !ready {
cliui.Infof(i.Stdout, "Waiting for dependencies of unit '%s' to be satisfied...", unitName)
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()
@@ -83,7 +96,11 @@ func (*RootCmd) syncStart(socketPath *string) *serpent.Command {
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
},
+20 -5
View File
@@ -93,19 +93,20 @@ func TestSyncCommands_Golden(t *testing.T) {
ctx := testutil.Context(t, testutil.WaitShort)
// Set up dependency: test-unit depends on dep-unit
// Set up dependencies: test-unit depends on dep-unit and dep-unit-2.
client, err := agentsocket.NewClient(ctx, agentsocket.WithPath(path))
require.NoError(t, err)
// Declare dependency
err = client.SyncWant(ctx, "test-unit", "dep-unit")
require.NoError(t, err)
err = client.SyncWant(ctx, "test-unit", "dep-unit-2")
require.NoError(t, err)
client.Close()
outBuf := testutil.NewWaitBuffer()
done := make(chan error, 1)
go func() {
if err := outBuf.WaitFor(ctx, "Waiting"); err != nil {
if err := outBuf.WaitFor(ctx, "is waiting for dependencies"); err != nil {
done <- err
return
}
@@ -118,13 +119,23 @@ func TestSyncCommands_Golden(t *testing.T) {
}
defer compClient.Close()
// Start and complete the dependency unit.
// Start and complete both dependency units.
err = compClient.SyncStart(compCtx, "dep-unit")
if err != nil {
done <- err
return
}
err = compClient.SyncComplete(compCtx, "dep-unit")
if err != nil {
done <- err
return
}
err = compClient.SyncStart(compCtx, "dep-unit-2")
if err != nil {
done <- err
return
}
err = compClient.SyncComplete(compCtx, "dep-unit-2")
done <- err
}()
@@ -132,7 +143,7 @@ func TestSyncCommands_Golden(t *testing.T) {
inv.Stdout = outBuf
inv.Stderr = outBuf
// Run the start command - it should wait for the dependency.
// Run the start command. It should wait for the dependencies.
err = inv.WithContext(ctx).Run()
require.NoError(t, err)
@@ -179,6 +190,10 @@ func TestSyncCommands_Golden(t *testing.T) {
err := inv.WithContext(ctx).Run()
require.NoError(t, err)
require.Contains(t, outBuf.String(), "Unit \"test-unit\" declared dependencies: [dep-1, dep-2, dep-3]")
require.Contains(t, outBuf.String(), "dep-1")
require.Contains(t, outBuf.String(), "dep-2")
require.Contains(t, outBuf.String(), "dep-3")
// Verify all dependencies were registered by checking status.
outBuf.Reset()
+4 -1
View File
@@ -1,6 +1,9 @@
package cli
import (
"fmt"
"strings"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/agent/agentsocket"
@@ -39,7 +42,7 @@ func (*RootCmd) syncWant(socketPath *string) *serpent.Command {
}
}
cliui.Info(i.Stdout, "Success")
cliui.Info(i.Stdout, fmt.Sprintf("Unit %q declared dependencies: [%s]", dependentUnit, strings.Join(i.Args[1:], ", ")))
return nil
},
@@ -1,2 +1,2 @@
Waiting for dependencies of unit 'test-unit' to be satisfied...
Success
Unit "test-unit" is waiting for dependencies to be satisfied: [dep-unit, dep-unit-2]
Unit "test-unit" finished waiting for dependencies: [dep-unit, dep-unit-2]
+1 -1
View File
@@ -1 +1 @@
Success
Unit "test-unit" declared dependencies: [dep-unit]