fix: run the interactive installer loop to report errors
In the previous implementation, even though `installer.err` was set, it was never checked 🤦. The run loop was stolen from the dashboard code. Fixes #8205 Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
parent
87be76b878
commit
593afeea38
@ -9,10 +9,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/gdamore/tcell/v2"
|
"github.com/gdamore/tcell/v2"
|
||||||
"github.com/rivo/tview"
|
"github.com/rivo/tview"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
"github.com/siderolabs/talos/internal/pkg/tui/components"
|
"github.com/siderolabs/talos/internal/pkg/tui/components"
|
||||||
machineapi "github.com/siderolabs/talos/pkg/machinery/api/machine"
|
machineapi "github.com/siderolabs/talos/pkg/machinery/api/machine"
|
||||||
@ -38,8 +38,6 @@ type Page struct {
|
|||||||
type Installer struct {
|
type Installer struct {
|
||||||
pages *tview.Pages
|
pages *tview.Pages
|
||||||
app *tview.Application
|
app *tview.Application
|
||||||
wg sync.WaitGroup
|
|
||||||
err error
|
|
||||||
ctx context.Context //nolint:containedctx
|
ctx context.Context //nolint:containedctx
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
addedPages map[string]bool
|
addedPages map[string]bool
|
||||||
@ -73,85 +71,78 @@ const (
|
|||||||
|
|
||||||
// Run starts interactive installer.
|
// Run starts interactive installer.
|
||||||
func (installer *Installer) Run(conn *Connection) error {
|
func (installer *Installer) Run(conn *Connection) error {
|
||||||
installer.startApp()
|
|
||||||
defer installer.stopApp()
|
|
||||||
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
description string
|
|
||||||
)
|
|
||||||
|
|
||||||
for phase := phaseInit; phase <= phaseApply; {
|
|
||||||
switch phase {
|
|
||||||
case phaseInit:
|
|
||||||
description = "get the node information"
|
|
||||||
err = installer.init(conn)
|
|
||||||
case phaseConfigure:
|
|
||||||
description = "generate the configuration"
|
|
||||||
err = installer.configure()
|
|
||||||
case phaseApply:
|
|
||||||
description = "apply the configuration"
|
|
||||||
err = installer.apply(conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil && err != context.Canceled {
|
|
||||||
choice := installer.showModal(
|
|
||||||
fmt.Sprintf("Failed to %s", description),
|
|
||||||
err.Error(),
|
|
||||||
"Quit", "Retry",
|
|
||||||
)
|
|
||||||
|
|
||||||
if choice == 1 {
|
|
||||||
// apply should be retried from configure
|
|
||||||
if phase == phaseApply {
|
|
||||||
phase = phaseConfigure
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
phase++
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (installer *Installer) startApp() {
|
|
||||||
if installer.app != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
installer.wg.Add(1)
|
|
||||||
installer.app = tview.NewApplication()
|
installer.app = tview.NewApplication()
|
||||||
|
|
||||||
go func() {
|
var eg *errgroup.Group
|
||||||
defer installer.wg.Done()
|
|
||||||
|
eg, installer.ctx = errgroup.WithContext(installer.ctx)
|
||||||
|
|
||||||
|
eg.Go(func() error {
|
||||||
defer installer.cancel()
|
defer installer.cancel()
|
||||||
|
|
||||||
if err := installer.app.SetRoot(installer.pages, true).EnableMouse(true).Run(); err != nil {
|
return installer.app.SetRoot(installer.pages, true).EnableMouse(true).Run()
|
||||||
installer.err = err
|
})
|
||||||
|
|
||||||
|
eg.Go(func() error {
|
||||||
|
defer installer.app.Stop()
|
||||||
|
|
||||||
|
<-installer.ctx.Done()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
eg.Go(func() error {
|
||||||
|
defer installer.cancel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
description string
|
||||||
|
)
|
||||||
|
|
||||||
|
for phase := phaseInit; phase <= phaseApply; {
|
||||||
|
switch phase {
|
||||||
|
case phaseInit:
|
||||||
|
description = "get the node information"
|
||||||
|
err = installer.init(conn)
|
||||||
|
case phaseConfigure:
|
||||||
|
description = "generate the configuration"
|
||||||
|
err = installer.configure()
|
||||||
|
case phaseApply:
|
||||||
|
description = "apply the configuration"
|
||||||
|
err = installer.apply(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && err != context.Canceled {
|
||||||
|
choice := installer.showModal(
|
||||||
|
fmt.Sprintf("Failed to %s", description),
|
||||||
|
err.Error(),
|
||||||
|
"Quit", "Retry",
|
||||||
|
)
|
||||||
|
|
||||||
|
if choice == 1 {
|
||||||
|
// apply should be retried from configure
|
||||||
|
if phase == phaseApply {
|
||||||
|
phase = phaseConfigure
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
phase++
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (installer *Installer) stopApp() {
|
return nil
|
||||||
if installer.app == nil {
|
})
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
installer.app.Stop()
|
return eg.Wait()
|
||||||
installer.wg.Wait()
|
|
||||||
installer.app = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (installer *Installer) init(conn *Connection) (err error) {
|
func (installer *Installer) init(conn *Connection) (err error) {
|
||||||
installer.startApp()
|
|
||||||
|
|
||||||
s := components.NewSpinner(
|
s := components.NewSpinner(
|
||||||
fmt.Sprintf("Connecting to the maintenance service at [green::]%s[white::]", conn.nodeEndpoint),
|
fmt.Sprintf("Connecting to the maintenance service at [green::]%s[white::]", conn.nodeEndpoint),
|
||||||
spinner,
|
spinner,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user