cli: do not use interactive TUI elements when control terminal (tty) is not available (fixes #386)

This commit is contained in:
Pasha Sviderski
2026-06-18 18:22:48 +10:00
parent 7bb3cb9682
commit 2f717a10a7
10 changed files with 81 additions and 56 deletions
+5
View File
@@ -8,6 +8,7 @@ import (
"charm.land/huh/v2"
"github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/internal/cli/completion"
"github.com/psviderski/uncloud/internal/cli/tui"
"github.com/spf13/cobra"
)
@@ -50,6 +51,10 @@ func selectContext(uncli *cli.CLI) error {
if len(uncli.Config.Contexts) == 0 {
return fmt.Errorf("no contexts found in Uncloud config (%s)", uncli.Config.Path())
}
if !tui.IsTerminalAvailable() {
return fmt.Errorf("cannot select a context interactively without a terminal. " +
"Pass the context name explicitly: uc ctx use CONTEXT")
}
contextNames := slices.Sorted(maps.Keys(uncli.Config.Contexts))
+1 -1
View File
@@ -203,7 +203,7 @@ func runDeploy(ctx context.Context, uncli *cli.CLI, opts deployOptions) error {
// Ask for plan confirmation before proceeding with the deployment unless auto-confirmed with --yes.
if !opts.yes {
if !tui.IsStdinTerminal() {
if !tui.IsTerminalAvailable() {
return errors.New("cannot ask to confirm deployment plan in non-interactive mode, " +
"use --yes flag or set UNCLOUD_AUTO_CONFIRM=true to auto-confirm")
}
+3 -14
View File
@@ -8,7 +8,6 @@ import (
"strings"
"time"
"charm.land/huh/v2/spinner"
"charm.land/lipgloss/v2"
"github.com/docker/compose/v2/pkg/progress"
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
@@ -173,19 +172,9 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteMachine,
}
// Wait for the cluster to be initialised on the machine to be able to deploy the Caddy service.
err = spinner.New().
Title(" Waiting for the machine to join the cluster...").
Type(spinner.MiniDot).
WithTheme(spinner.ThemeFunc(func(isDark bool) *spinner.Styles {
return &spinner.Styles{
Spinner: lipgloss.NewStyle().Foreground(lipgloss.Yellow),
Title: lipgloss.NewStyle(),
}
})).
ActionWithErr(func(ctx context.Context) error {
return machineClient.WaitClusterReady(ctx, 5*time.Minute)
}).
Run()
err = tui.RunSpinner(ctx, "Waiting for the machine to join the cluster...", func(ctx context.Context) error {
return machineClient.WaitClusterReady(ctx, 5*time.Minute)
})
if err != nil {
return fmt.Errorf("wait for machine to join the cluster: %w", err)
}
+4 -15
View File
@@ -7,13 +7,12 @@ import (
"strings"
"time"
"charm.land/huh/v2/spinner"
"charm.land/lipgloss/v2"
"github.com/docker/compose/v2/pkg/progress"
"github.com/psviderski/uncloud/cmd/uncloud/caddy"
"github.com/psviderski/uncloud/cmd/uncloud/dns"
"github.com/psviderski/uncloud/internal/cli"
"github.com/psviderski/uncloud/internal/cli/config"
"github.com/psviderski/uncloud/internal/cli/tui"
"github.com/psviderski/uncloud/internal/machine/api/pb"
"github.com/psviderski/uncloud/internal/machine/cluster"
"github.com/psviderski/uncloud/internal/machine/network"
@@ -225,19 +224,9 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
// Since the cluster API needs a few moments to become ready after cluster initialisation,
// we keep the user informed during this wait. We wait here even if no Caddy or DNS is requested
// as the cluster needs to be ready so that commands such as 'uc machine ls' work immediately after init.
err = spinner.New().
Title(" Waiting for the cluster to be ready...").
Type(spinner.MiniDot).
WithTheme(spinner.ThemeFunc(func(isDark bool) *spinner.Styles {
return &spinner.Styles{
Spinner: lipgloss.NewStyle().Foreground(lipgloss.Yellow),
Title: lipgloss.NewStyle(),
}
})).
ActionWithErr(func(ctx context.Context) error {
return client.WaitClusterReady(ctx, 1*time.Minute)
}).
Run()
err = tui.RunSpinner(ctx, "Waiting for the cluster to be ready...", func(ctx context.Context) error {
return client.WaitClusterReady(ctx, 1*time.Minute)
})
if err != nil {
return fmt.Errorf("wait for cluster to be ready: %w", err)
}
+4 -15
View File
@@ -6,7 +6,6 @@ import (
"sort"
"time"
"charm.land/huh/v2/spinner"
"charm.land/lipgloss/v2"
"github.com/docker/docker/api/types/container"
"github.com/docker/go-units"
@@ -83,20 +82,10 @@ func runPs(ctx context.Context, uncli *cli.CLI, opts psOptions) error {
defer clusterClient.Close()
var containers []containerInfo
err = spinner.New().
Title(" Collecting container info...").
Type(spinner.MiniDot).
WithTheme(spinner.ThemeFunc(func(isDark bool) *spinner.Styles {
return &spinner.Styles{
Spinner: lipgloss.NewStyle().Foreground(lipgloss.Yellow),
Title: lipgloss.NewStyle(),
}
})).
ActionWithErr(func(ctx context.Context) error {
containers, err = collectContainers(ctx, clusterClient)
return err
}).
Run()
err = tui.RunSpinner(ctx, "Collecting container info...", func(ctx context.Context) error {
containers, err = collectContainers(ctx, clusterClient)
return err
})
if err != nil {
return fmt.Errorf("collect containers: %w", err)
}
+1 -1
View File
@@ -136,7 +136,7 @@ func scale(ctx context.Context, uncli *cli.CLI, opts scaleOptions) error {
// Ask for confirmation unless auto-confirmed with --yes.
if !opts.yes {
if !tui.IsStdinTerminal() {
if !tui.IsTerminalAvailable() {
return errors.New("cannot ask to confirm scaling plan in non-interactive mode, " +
"use --yes flag or set UNCLOUD_AUTO_CONFIRM=true to auto-confirm")
}