From 40ce44366f81add8a4d575fa64dbc6028071a03f Mon Sep 17 00:00:00 2001 From: Volker Theile Date: Tue, 26 Mar 2024 10:55:50 +0100 Subject: [PATCH] Bump golangci-lint + fix lint issues Signed-off-by: Volker Theile (cherry picked from commit dd16a9522d63e400af29140406845820721fe2ba) # Conflicts: # Dockerfile.dapper # pkg/console/install_panels.go # pkg/console/util.go # pkg/preflight/checks.go # pkg/preflight/checks_test.go # pkg/widgets/panel.go --- Dockerfile.dapper | 6 + pkg/config/coerce.go | 2 +- pkg/config/cos_test.go | 6 +- pkg/config/rename.go | 2 +- pkg/console/console.go | 2 +- pkg/console/dashboard_panels.go | 6 +- pkg/console/helper.go | 10 +- pkg/console/install_panels.go | 238 +++++++++++++++++++++++++------- pkg/console/util.go | 25 ++++ pkg/console/util_test.go | 2 +- pkg/console/webhooks_test.go | 2 +- pkg/preflight/checks.go | 166 ++++++++++++++++++++++ pkg/preflight/checks_test.go | 169 +++++++++++++++++++++++ pkg/util/disk.go | 2 +- pkg/util/disk_test.go | 8 +- pkg/widgets/dropdown.go | 4 +- pkg/widgets/panel.go | 24 ++++ pkg/widgets/select.go | 2 +- pkg/widgets/util.go | 4 +- 19 files changed, 602 insertions(+), 78 deletions(-) create mode 100644 pkg/preflight/checks.go create mode 100644 pkg/preflight/checks_test.go diff --git a/Dockerfile.dapper b/Dockerfile.dapper index 6d37c1efd..134853a23 100644 --- a/Dockerfile.dapper +++ b/Dockerfile.dapper @@ -19,6 +19,12 @@ ENV ARCH $DAPPER_HOST_ARCH RUN zypper -n rm container-suseconnect && \ zypper -n install git curl docker gzip tar wget zstd squashfs xorriso awk jq mtools dosfstools unzip rsync patch RUN curl -sfL https://github.com/mikefarah/yq/releases/download/v4.21.1/yq_linux_${ARCH} -o /usr/bin/yq && chmod +x /usr/bin/yq +<<<<<<< HEAD +======= +RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.57.1 + +# only needed for raw image generation. currently skipped for arm builds +>>>>>>> dd16a95 (Bump golangci-lint + fix lint issues) RUN if [ "${ARCH}" == "amd64" ]; then \ curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.55.2; \ fi diff --git a/pkg/config/coerce.go b/pkg/config/coerce.go index 26c554591..35e24959e 100644 --- a/pkg/config/coerce.go +++ b/pkg/config/coerce.go @@ -34,7 +34,7 @@ func (t *typeConverter) ToInternal(data map[string]interface{}) error { return t.mappers.ToInternal(data) } -func (t *typeConverter) ModifySchema(schema *mapper.Schema, schemas *mapper.Schemas) error { +func (t *typeConverter) ModifySchema(schema *mapper.Schema, _ *mapper.Schemas) error { for name, field := range schema.ResourceFields { if field.Type == t.fieldType { t.mappers = append(t.mappers, fieldConverter{ diff --git a/pkg/config/cos_test.go b/pkg/config/cos_test.go index 6a3124079..8fa32d34a 100644 --- a/pkg/config/cos_test.go +++ b/pkg/config/cos_test.go @@ -40,17 +40,17 @@ func TestCalcCosPersistentPartSize(t *testing.T) { { diskSize: 300, partitionSize: "153600Ki", - err: "Partition size must end with 'Mi' or 'Gi'. Decimals and negatives are not allowed.", + err: "Partition size must end with 'Mi' or 'Gi'. Decimals and negatives are not allowed", }, { diskSize: 2000, partitionSize: "1.5Ti", - err: "Partition size must end with 'Mi' or 'Gi'. Decimals and negatives are not allowed.", + err: "Partition size must end with 'Mi' or 'Gi'. Decimals and negatives are not allowed", }, { diskSize: 500, partitionSize: "abcd", - err: "Partition size must end with 'Mi' or 'Gi'. Decimals and negatives are not allowed.", + err: "Partition size must end with 'Mi' or 'Gi'. Decimals and negatives are not allowed", }, } diff --git a/pkg/config/rename.go b/pkg/config/rename.go index 8debc7617..c8b236c1e 100644 --- a/pkg/config/rename.go +++ b/pkg/config/rename.go @@ -28,7 +28,7 @@ func (f *FuzzyNames) addName(name, toName string) { f.names[strings.ToLower(convert.ToYAMLKey(name))] = toName } -func (f *FuzzyNames) ModifySchema(schema *mapper.Schema, schemas *mapper.Schemas) error { +func (f *FuzzyNames) ModifySchema(schema *mapper.Schema, _ *mapper.Schemas) error { f.names = map[string]string{} for name := range schema.ResourceFields { diff --git a/pkg/console/console.go b/pkg/console/console.go index f6eeffdc0..47d0978b8 100644 --- a/pkg/console/console.go +++ b/pkg/console/console.go @@ -174,6 +174,6 @@ func setGlobalKeyBindings(g *gocui.Gui) error { return nil } -func quit(g *gocui.Gui, v *gocui.View) error { +func quit(_ *gocui.Gui, _ *gocui.View) error { return gocui.ErrQuit } diff --git a/pkg/console/dashboard_panels.go b/pkg/console/dashboard_panels.go index 3acf15234..a3fdc3237 100644 --- a/pkg/console/dashboard_panels.go +++ b/pkg/console/dashboard_panels.go @@ -171,7 +171,7 @@ func logoPanel(g *gocui.Gui) error { return nil } -func toShell(g *gocui.Gui, v *gocui.View) error { +func toShell(g *gocui.Gui, _ *gocui.View) error { g.Cursor = true maxX, _ := g.Size() adminPasswordFrameV := widgets.NewPanel(g, "adminPasswordFrame") @@ -191,7 +191,7 @@ func toShell(g *gocui.Gui, v *gocui.View) error { validatorV.Focus = false adminPasswordV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ - gocui.KeyEnter: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEnter: func(_ *gocui.Gui, _ *gocui.View) error { passwd, err := adminPasswordV.GetData() if err != nil { return err @@ -205,7 +205,7 @@ func toShell(g *gocui.Gui, v *gocui.View) error { validatorV.SetContent("Invalid credential or password hash algorithm not supported.") return nil }, - gocui.KeyEsc: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEsc: func(g *gocui.Gui, _ *gocui.View) error { g.Cursor = false if err := adminPasswordFrameV.Close(); err != nil { return err diff --git a/pkg/console/helper.go b/pkg/console/helper.go index 95131f5f9..a9f849be4 100644 --- a/pkg/console/helper.go +++ b/pkg/console/helper.go @@ -13,7 +13,7 @@ type passwordWrapper struct { passwordConfirmV *widgets.Input } -func (p *passwordWrapper) passwordVConfirmKeyBinding(g *gocui.Gui, v *gocui.View) error { +func (p *passwordWrapper) passwordVConfirmKeyBinding(_ *gocui.Gui, _ *gocui.View) error { password1V, err := p.c.GetElement(passwordPanel) if err != nil { return err @@ -28,7 +28,7 @@ func (p *passwordWrapper) passwordVConfirmKeyBinding(g *gocui.Gui, v *gocui.View return showNext(p.c, passwordConfirmPanel) } -func (p *passwordWrapper) passwordVEscapeKeyBinding(g *gocui.Gui, v *gocui.View) error { +func (p *passwordWrapper) passwordVEscapeKeyBinding(_ *gocui.Gui, _ *gocui.View) error { p.passwordV.Close() p.passwordConfirmV.Close() if installModeOnly { @@ -40,7 +40,7 @@ func (p *passwordWrapper) passwordVEscapeKeyBinding(g *gocui.Gui, v *gocui.View) return showNext(p.c, tokenPanel) } -func (p *passwordWrapper) passwordConfirmVArrowUpKeyBinding(g *gocui.Gui, v *gocui.View) error { +func (p *passwordWrapper) passwordConfirmVArrowUpKeyBinding(_ *gocui.Gui, _ *gocui.View) error { var err error userInputData.PasswordConfirm, err = p.passwordConfirmV.GetData() if err != nil { @@ -49,7 +49,7 @@ func (p *passwordWrapper) passwordConfirmVArrowUpKeyBinding(g *gocui.Gui, v *goc return showNext(p.c, passwordPanel) } -func (p *passwordWrapper) passwordConfirmVKeyEnter(g *gocui.Gui, v *gocui.View) error { +func (p *passwordWrapper) passwordConfirmVKeyEnter(_ *gocui.Gui, _ *gocui.View) error { var err error userInputData.PasswordConfirm, err = p.passwordConfirmV.GetData() if err != nil { @@ -72,7 +72,7 @@ func (p *passwordWrapper) passwordConfirmVKeyEnter(g *gocui.Gui, v *gocui.View) return showNext(p.c, ntpServersPanel) } -func (p *passwordWrapper) passwordConfirmVKeyEscape(g *gocui.Gui, v *gocui.View) error { +func (p *passwordWrapper) passwordConfirmVKeyEscape(_ *gocui.Gui, _ *gocui.View) error { p.passwordV.Close() p.passwordConfirmV.Close() if err := p.c.setContentByName(notePanel, ""); err != nil { diff --git a/pkg/console/install_panels.go b/pkg/console/install_panels.go index 1fc061d8f..fd01c6ecf 100644 --- a/pkg/console/install_panels.go +++ b/pkg/console/install_panels.go @@ -56,7 +56,27 @@ var ( diskConfirmed bool ) +<<<<<<< HEAD func (c *Console) layoutInstall(g *gocui.Gui) error { +======= +func (c *Console) doNetworkSpeedCheck(interfaces []config.NetworkInterface) (warnings []string) { + for _, iface := range interfaces { + msg, err := preflight.NetworkSpeedCheck{Dev: iface.Name}.Run() + if err != nil { + // Preflight checks that fail to run at all are + // logged, rather than killing the installer + logrus.Error(err) + continue + } + if len(msg) > 0 { + warnings = append(warnings, msg) + } + } + return +} + +func (c *Console) layoutInstall(_ *gocui.Gui) error { +>>>>>>> dd16a95 (Bump golangci-lint + fix lint issues) var err error once.Do(func() { setPanels(c) @@ -436,12 +456,12 @@ func addDiskPanel(c *Console) error { persistentSizePanel, ) } - gotoPrevPage := func(g *gocui.Gui, v *gocui.View) error { + gotoPrevPage := func(_ *gocui.Gui, _ *gocui.View) error { closeThisPage() diskConfirmed = false return showNext(c, askCreatePanel) } - gotoNextPage := func(g *gocui.Gui, v *gocui.View) error { + gotoNextPage := func(_ *gocui.Gui, _ *gocui.View) error { // Don't proceed to the next page if disk size validation fails if valid, err := validateAllDiskSizes(); !valid || err != nil { return err; @@ -474,7 +494,7 @@ func addDiskPanel(c *Console) error { return showHostnamePage(c) } - diskConfirm := func(g *gocui.Gui, v *gocui.View) error { + diskConfirm := func(_ *gocui.Gui, _ *gocui.View) error { device, err := diskV.GetData() if err != nil { return err @@ -513,7 +533,7 @@ func addDiskPanel(c *Console) error { diskV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ gocui.KeyEnter: diskConfirm, gocui.KeyArrowDown: diskConfirm, - gocui.KeyArrowUp: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyArrowUp: func(_ *gocui.Gui, _ *gocui.View) error { return updateValidatorMessage("") }, gocui.KeyEsc: gotoPrevPage, @@ -557,7 +577,7 @@ func addDiskPanel(c *Console) error { dataDiskV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ gocui.KeyEnter: dataDiskConfirm, gocui.KeyArrowDown: dataDiskConfirm, - gocui.KeyArrowUp: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyArrowUp: func(_ *gocui.Gui, _ *gocui.View) error { if err := updateValidatorMessage(""); err != nil { return err } @@ -584,7 +604,7 @@ func addDiskPanel(c *Console) error { } persistentSizeV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ gocui.KeyEnter: persistentSizeConfirm, - gocui.KeyArrowUp: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyArrowUp: func(_ *gocui.Gui, _ *gocui.View) error { diskConfirmed = false if len(diskOpts) > 1 { if err := updateValidatorMessage(""); err != nil { @@ -627,7 +647,7 @@ func addDiskPanel(c *Console) error { } askForceMBRV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ gocui.KeyEnter: mbrConfirm, - gocui.KeyArrowUp: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyArrowUp: func(_ *gocui.Gui, _ *gocui.View) error { diskConfirmed = false disk, err := diskV.GetData() @@ -653,6 +673,58 @@ func addDiskPanel(c *Console) error { return nil } +<<<<<<< HEAD +======= +func addPreflightCheckPanel(c *Console) error { + ackWarningsFunc := func() ([]widgets.Option, error) { + return []widgets.Option{ + { + Value: "yes", + Text: "Yes", + }, { + Value: "no", + Text: "No", + }, + }, nil + } + preflightCheckV, err := widgets.NewSelect(c.Gui, preflightCheckPanel, "", ackWarningsFunc) + if err != nil { + return err + } + preflightCheckV.FirstPage = true + preflightCheckV.Wrap = true + preflightCheckV.PreShow = func() error { + var warnings string + for _, w := range preflightWarnings { + warnings += w + "\n" + } + preflightCheckV.SetContent(warnings + + "\nDo you wish to proceed?\n") + c.Gui.Cursor = false + return c.setContentByName(titlePanel, "Hardware Checks") + } + preflightCheckV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ + gocui.KeyEnter: func(_ *gocui.Gui, _ *gocui.View) error { + proceed, err := preflightCheckV.GetData() + if err != nil { + return err + } + if proceed == "no" { + preflightCheckV.Close() + c.setContentByName(titlePanel, "") + c.setContentByName(footerPanel, "") + go util.SleepAndReboot() + return c.setContentByName(notePanel, "Installation halted. Rebooting system in 5 seconds") + } + preflightCheckV.Close() + return showNext(c, askCreatePanel) + }, + } + c.AddElement(preflightCheckPanel, preflightCheckV) + return nil +} + +>>>>>>> dd16a95 (Bump golangci-lint + fix lint issues) func addAskCreatePanel(c *Console) error { askOptionsFunc := func() ([]widgets.Option, error) { options := []widgets.Option{ @@ -699,7 +771,7 @@ func addAskCreatePanel(c *Console) error { return c.setContentByName(titlePanel, "Choose installation mode") } askCreateV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ - gocui.KeyEnter: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEnter: func(_ *gocui.Gui, _ *gocui.View) error { selected, err := askCreateV.GetData() if err != nil { return err @@ -742,6 +814,68 @@ func addAskCreatePanel(c *Console) error { return nil } +<<<<<<< HEAD +======= +func showRolePage(c *Console) error { + setLocation := createVerticalLocatorWithName(c) + + if err := setLocation(askRolePanel, 3); err != nil { + return err + } + + return showNext(c, askRolePanel) +} + +func addAskRolePanel(c *Console) error { + askOptionsFunc := func() ([]widgets.Option, error) { + return []widgets.Option{ + { + Value: config.RoleDefault, + Text: "Default Role (Management or Worker)", + }, { + Value: config.RoleMgmt, + Text: "Management Role", + }, { + Value: config.RoleWitness, + Text: "Witness Role", + }, { + Value: config.RoleWorker, + Text: "Worker Role", + }, + }, nil + } + askRoleV, err := widgets.NewSelect(c.Gui, askRolePanel, "", askOptionsFunc) + if err != nil { + return err + } + gotoPrevPage := func(_ *gocui.Gui, _ *gocui.View) error { + c.CloseElements(askRolePanel) + return showNext(c, askCreatePanel) + } + askRoleV.PreShow = func() error { + askRoleV.Value = c.config.Install.Role + return c.setContentByName(titlePanel, "Choose installation role") + } + askRoleV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ + gocui.KeyEnter: func(_ *gocui.Gui, _ *gocui.View) error { + selected, err := askRoleV.GetData() + if err != nil { + return err + } + c.config.Install.Role = selected + askRoleV.Close() + if alreadyInstalled { + return showHostnamePage(c) + } + return showDiskPage(c) + }, + gocui.KeyEsc: gotoPrevPage, + } + c.AddElement(askRolePanel, askRoleV) + return nil +} + +>>>>>>> dd16a95 (Bump golangci-lint + fix lint issues) func addServerURLPanel(c *Console) error { serverURLV, err := widgets.NewInput(c.Gui, serverURLPanel, "Management address", false) if err != nil { @@ -756,7 +890,7 @@ func addServerURLPanel(c *Console) error { return c.setContentByName(notePanel, serverURLNote) } serverURLV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ - gocui.KeyEnter: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEnter: func(_ *gocui.Gui, _ *gocui.View) error { asyncTaskV, err := c.GetElement(spinnerPanel) if err != nil { return err @@ -787,21 +921,21 @@ func addServerURLPanel(c *Console) error { go func(g *gocui.Gui) { if err = validatePingServerURL(pingServerURL); err != nil { spinner.Stop(true, err.Error()) - g.Update(func(g *gocui.Gui) error { + g.Update(func(_ *gocui.Gui) error { return showNext(c, serverURLPanel) }) return } spinner.Stop(false, "") c.config.ServerURL = fmtServerURL - g.Update(func(g *gocui.Gui) error { + g.Update(func(_ *gocui.Gui) error { serverURLV.Close() return showNext(c, tokenPanel) }) }(c.Gui) return nil }, - gocui.KeyEsc: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEsc: func(g *gocui.Gui, _ *gocui.View) error { g.Cursor = false serverURLV.Close() return showNext(c, dnsServersPanel) @@ -888,7 +1022,7 @@ func addSSHKeyPanel(c *Console) error { return showNext(c, cloudInitPanel) } sshKeyV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ - gocui.KeyEnter: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEnter: func(_ *gocui.Gui, _ *gocui.View) error { url, err := sshKeyV.GetData() if err != nil { return err @@ -911,7 +1045,7 @@ func addSSHKeyPanel(c *Console) error { pubKeys, err := getRemoteSSHKeys(url) if err != nil { spinner.Stop(true, err.Error()) - g.Update(func(g *gocui.Gui) error { + g.Update(func(_ *gocui.Gui) error { return showNext(c, sshKeyPanel) }) return @@ -919,7 +1053,7 @@ func addSSHKeyPanel(c *Console) error { spinner.Stop(false, "") logrus.Debug("SSH public keys: ", pubKeys) c.config.SSHAuthorizedKeys = pubKeys - g.Update(func(g *gocui.Gui) error { + g.Update(func(_ *gocui.Gui) error { return gotoNextPage() }) }(c.Gui) @@ -927,7 +1061,7 @@ func addSSHKeyPanel(c *Console) error { } return gotoNextPage() }, - gocui.KeyEsc: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEsc: func(_ *gocui.Gui, _ *gocui.View) error { closeThisPage() return showNext(c, proxyPanel) }, @@ -968,7 +1102,7 @@ func addTokenPanel(c *Console) error { return tokenV.Close() } tokenV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ - gocui.KeyEnter: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEnter: func(_ *gocui.Gui, _ *gocui.View) error { token, err := tokenV.GetData() if err != nil { return err @@ -983,7 +1117,7 @@ func addTokenPanel(c *Console) error { closeThisPage() return showNext(c, passwordConfirmPanel, passwordPanel) }, - gocui.KeyEsc: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEsc: func(g *gocui.Gui, _ *gocui.View) error { closeThisPage() if c.config.Install.Mode == config.ModeCreate { g.Cursor = false @@ -1038,7 +1172,7 @@ func addHostnamePanel(c *Console) error { return showNetworkPage(c) } - prev := func(g *gocui.Gui, v *gocui.View) error { + prev := func(_ *gocui.Gui, _ *gocui.View) error { c.CloseElements(hostnamePanel, hostnameValidatorPanel) if alreadyInstalled { return showNext(c, askCreatePanel) @@ -1070,7 +1204,7 @@ func addHostnamePanel(c *Console) error { } hostnameV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ gocui.KeyArrowUp: prev, - gocui.KeyEnter: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEnter: func(_ *gocui.Gui, _ *gocui.View) error { message, err := validate() if err != nil { return err @@ -1154,7 +1288,7 @@ func addNetworkPanel(c *Console) error { } gotoNextPanel := func(c *Console, name []string, hooks ...func() (string, error)) func(g *gocui.Gui, v *gocui.View) error { - return func(g *gocui.Gui, v *gocui.View) error { + return func(_ *gocui.Gui, _ *gocui.View) error { c.CloseElement(networkValidatorPanel) for _, hook := range hooks { msg, err := hook() @@ -1240,12 +1374,12 @@ func addNetworkPanel(c *Console) error { spinner.Stop(isErr, errMsg) // Go back to the panel that triggered gotoNextPage - g.Update(func(g *gocui.Gui) error { + g.Update(func(_ *gocui.Gui) error { return showNext(c, fromPanel) }) } else { spinner.Stop(false, "") - g.Update(func(g *gocui.Gui) error { + g.Update(func(_ *gocui.Gui) error { closeThisPage() return showNext(c, getNextPagePanel()...) }) @@ -1254,7 +1388,7 @@ func addNetworkPanel(c *Console) error { return nil } - gotoPrevPage := func(g *gocui.Gui, v *gocui.View) error { + gotoPrevPage := func(_ *gocui.Gui, _ *gocui.View) error { closeThisPage() return showHostnamePage(c) } @@ -1352,7 +1486,7 @@ func addNetworkPanel(c *Console) error { } return nil } - askBondModeVConfirm := func(g *gocui.Gui, v *gocui.View) error { + askBondModeVConfirm := func(_ *gocui.Gui, _ *gocui.View) error { mode, err := askBondModeV.GetData() mgmtNetwork.BondOptions = map[string]string{ "mode": mode, @@ -1379,7 +1513,7 @@ func addNetworkPanel(c *Console) error { c.AddElement(askBondModePanel, askBondModeV) // askNetworkMethodV - askNetworkMethodVConfirm := func(g *gocui.Gui, _ *gocui.View) error { + askNetworkMethodVConfirm := func(_ *gocui.Gui, _ *gocui.View) error { selected, err := askNetworkMethodV.GetData() if err != nil { return err @@ -1503,7 +1637,7 @@ func addNetworkPanel(c *Console) error { mgmtNetwork.MTU = mtu return "", nil } - mtuVConfirm := func(g *gocui.Gui, v *gocui.View) error { + mtuVConfirm := func(_ *gocui.Gui, _ *gocui.View) error { msg, err := validateMTU() if err != nil { return err @@ -1615,7 +1749,7 @@ func addProxyPanel(c *Console) error { return c.setContentByName(notePanel, proxyNote) } proxyV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ - gocui.KeyEnter: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEnter: func(_ *gocui.Gui, _ *gocui.View) error { proxy, err := proxyV.GetData() if err != nil { return err @@ -1635,7 +1769,7 @@ func addProxyPanel(c *Console) error { noteV.Close() return showNext(c, sshKeyPanel) }, - gocui.KeyEsc: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEsc: func(_ *gocui.Gui, _ *gocui.View) error { proxyV.Close() c.CloseElement(notePanel) return showNext(c, ntpServersPanel) @@ -1660,7 +1794,7 @@ func addCloudInitPanel(c *Console) error { return showNext(c, confirmInstallPanel) } cloudInitV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ - gocui.KeyEnter: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEnter: func(_ *gocui.Gui, _ *gocui.View) error { configURL, err := cloudInitV.GetData() if err != nil { return err @@ -1680,13 +1814,13 @@ func addCloudInitPanel(c *Console) error { go func(g *gocui.Gui) { if _, err = getRemoteConfig(configURL); err != nil { spinner.Stop(true, err.Error()) - g.Update(func(g *gocui.Gui) error { + g.Update(func(_ *gocui.Gui) error { return showNext(c, cloudInitPanel) }) return } spinner.Stop(false, "") - g.Update(func(g *gocui.Gui) error { + g.Update(func(_ *gocui.Gui) error { return gotoNextPage() }) }(c.Gui) @@ -1694,7 +1828,7 @@ func addCloudInitPanel(c *Console) error { } return gotoNextPage() }, - gocui.KeyEsc: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEsc: func(_ *gocui.Gui, _ *gocui.View) error { cloudInitV.Close() return showNext(c, sshKeyPanel) }, @@ -1760,7 +1894,7 @@ func addConfirmInstallPanel(c *Console) error { return c.setContentByName(titlePanel, "Confirm installation options") } confirmV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ - gocui.KeyEnter: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEnter: func(_ *gocui.Gui, _ *gocui.View) error { confirmed, err := confirmV.GetData() if err != nil { return err @@ -1775,7 +1909,7 @@ func addConfirmInstallPanel(c *Console) error { confirmV.Close() return showNext(c, installPanel) }, - gocui.KeyEsc: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEsc: func(_ *gocui.Gui, _ *gocui.View) error { confirmV.Close() if installModeOnly { return showNext(c, passwordConfirmPanel, passwordPanel) @@ -1807,7 +1941,7 @@ func addConfirmUpgradePanel(c *Console) error { return c.setContentByName(titlePanel, fmt.Sprintf("Confirm upgrading Harvester to %s?", version.Version)) } confirmV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ - gocui.KeyEnter: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEnter: func(_ *gocui.Gui, _ *gocui.View) error { confirmed, err := confirmV.GetData() if err != nil { return err @@ -1818,7 +1952,7 @@ func addConfirmUpgradePanel(c *Console) error { } return showNext(c, upgradePanel) }, - gocui.KeyEsc: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEsc: func(_ *gocui.Gui, _ *gocui.View) error { confirmV.Close() return showNext(c, askCreatePanel) }, @@ -1980,15 +2114,15 @@ func addVIPPanel(c *Console) error { vipTextPanel) } - gotoPrevPage := func(g *gocui.Gui, v *gocui.View) error { + gotoPrevPage := func(_ *gocui.Gui, _ *gocui.View) error { closeThisPage() return showNext(c, dnsServersPanel) } - gotoNextPage := func(g *gocui.Gui, v *gocui.View) error { + gotoNextPage := func(_ *gocui.Gui, _ *gocui.View) error { closeThisPage() return showNext(c, tokenPanel) } - gotoVipPanel := func(g *gocui.Gui, v *gocui.View) error { + gotoVipPanel := func(g *gocui.Gui, _ *gocui.View) error { selected, err := askVipMethodV.GetData() if err != nil { return err @@ -2001,7 +2135,7 @@ func addVIPPanel(c *Console) error { vip, err := getVipThroughDHCP(mgmtName) if err != nil { spinner.Stop(true, err.Error()) - g.Update(func(g *gocui.Gui) error { + g.Update(func(_ *gocui.Gui) error { return showNext(c, askVipMethodPanel) }) return @@ -2010,13 +2144,13 @@ func addVIPPanel(c *Console) error { c.config.Vip = vip.ipv4Addr c.config.VipMode = selected c.config.VipHwAddr = vip.hwAddr - g.Update(func(g *gocui.Gui) error { + g.Update(func(_ *gocui.Gui) error { return vipV.SetData(vip.ipv4Addr) }) }(c.Gui) } else { vipTextV.SetContent("") - g.Update(func(gui *gocui.Gui) error { + g.Update(func(_ *gocui.Gui) error { return vipV.SetData("") }) c.config.VipMode = config.NetworkMethodStatic @@ -2054,7 +2188,7 @@ func addVIPPanel(c *Console) error { return gotoNextPage(g, v) } - gotoAskVipMethodPanel := func(g *gocui.Gui, v *gocui.View) error { + gotoAskVipMethodPanel := func(_ *gocui.Gui, _ *gocui.View) error { return showNext(c, askVipMethodPanel) } askVipMethodV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ @@ -2109,7 +2243,7 @@ func addNTPServersPanel(c *Console) error { c.CloseElement(notePanel) return ntpServersV.Close() } - gotoPrevPage := func(g *gocui.Gui, v *gocui.View) error { + gotoPrevPage := func(_ *gocui.Gui, _ *gocui.View) error { userInputData.HasCheckedNTPServers = false closeThisPage() return showNext(c, passwordConfirmPanel, passwordPanel) @@ -2121,13 +2255,13 @@ func addNTPServersPanel(c *Console) error { } gotoSpinnerErrorPage := func(g *gocui.Gui, spinner *Spinner, msg string) { spinner.Stop(true, msg) - g.Update(func(g *gocui.Gui) error { + g.Update(func(_ *gocui.Gui) error { return showNext(c, ntpServersPanel) }) } ntpServersV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ - gocui.KeyEnter: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEnter: func(_ *gocui.Gui, _ *gocui.View) error { // get ntp servers input ntpServers, err := ntpServersV.GetData() if err != nil { @@ -2182,7 +2316,7 @@ func addNTPServersPanel(c *Console) error { } spinner.Stop(false, "") - g.Update(func(g *gocui.Gui) error { + g.Update(func(_ *gocui.Gui) error { return gotoNextPage() }) }(c.Gui) @@ -2224,7 +2358,7 @@ func addDNSServersPanel(c *Console) error { c.CloseElement(notePanel) return dnsServersV.Close() } - gotoPrevPage := func(g *gocui.Gui, v *gocui.View) error { + gotoPrevPage := func(_ *gocui.Gui, _ *gocui.View) error { closeThisPage() return showNetworkPage(c) } @@ -2237,13 +2371,13 @@ func addDNSServersPanel(c *Console) error { } gotoSpinnerErrorPage := func(g *gocui.Gui, spinner *Spinner, msg string) { spinner.Stop(true, msg) - g.Update(func(g *gocui.Gui) error { + g.Update(func(_ *gocui.Gui) error { return showNext(c, dnsServersPanel) }) } dnsServersV.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ - gocui.KeyEnter: func(g *gocui.Gui, v *gocui.View) error { + gocui.KeyEnter: func(_ *gocui.Gui, _ *gocui.View) error { // init asyncTaskV asyncTaskV, err := c.GetElement(spinnerPanel) if err != nil { @@ -2287,7 +2421,7 @@ func addDNSServersPanel(c *Console) error { c.config.OS.DNSNameservers = dnsServerList } spinner.Stop(false, "") - g.Update(func(g *gocui.Gui) error { + g.Update(func(_ *gocui.Gui) error { return gotoNextPage() }) }(c.Gui) diff --git a/pkg/console/util.go b/pkg/console/util.go index 567e7bec7..6799b0c70 100644 --- a/pkg/console/util.go +++ b/pkg/console/util.go @@ -447,6 +447,31 @@ func saveTemp(obj interface{}, prefix string) (string, error) { return tempFile.Name(), nil } +<<<<<<< HEAD +======= +func roleSetup(c *config.HarvesterConfig) error { + if c.Role == "" { + return nil + } + if c.Labels == nil { + c.Labels = make(map[string]string) + } + switch c.Role { + case config.RoleMgmt: + c.Labels[util.HarvesterMgmtNodeLabelKey] = "true" + case config.RoleWorker: + c.Labels[util.HarvesterWorkerNodeLabelKey] = "true" + case config.RoleWitness: + c.Labels[util.HarvesterWitnessNodeLabelKey] = "true" + case config.RoleDefault: + // do not set any label + default: + return fmt.Errorf("unknown role %s, please correct it", c.Role) + } + return nil +} + +>>>>>>> dd16a95 (Bump golangci-lint + fix lint issues) func doInstall(g *gocui.Gui, hvstConfig *config.HarvesterConfig, webhooks RendererWebhooks) error { ctx := context.TODO() webhooks.Handle(EventInstallStarted) diff --git a/pkg/console/util_test.go b/pkg/console/util_test.go index 71256ec3b..b212c5039 100644 --- a/pkg/console/util_test.go +++ b/pkg/console/util_test.go @@ -39,7 +39,7 @@ func TestGetSSHKeysFromURL(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { fmt.Fprintln(w, testCase.httpResp) })) defer ts.Close() diff --git a/pkg/console/webhooks_test.go b/pkg/console/webhooks_test.go index bfeb48da4..dc6ff1db4 100644 --- a/pkg/console/webhooks_test.go +++ b/pkg/console/webhooks_test.go @@ -150,7 +150,7 @@ func TestParsedWebhook_Send(t *testing.T) { if tt.fields.TLS { newTestServer = httptest.NewTLSServer } - ts := newTestServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ts := newTestServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { recorder.Method = r.Method recorder.Headers = dupHeaders(r.Header) defer r.Body.Close() diff --git a/pkg/preflight/checks.go b/pkg/preflight/checks.go new file mode 100644 index 000000000..d700a1247 --- /dev/null +++ b/pkg/preflight/checks.go @@ -0,0 +1,166 @@ +package preflight + +import ( + "bufio" + "errors" + "fmt" + "io/fs" + "os" + "os/exec" + "strconv" + "strings" +) + +const ( + // Constants here from Hardware Requirements in the documentation + // https://docs.harvesterhci.io/v1.3/install/requirements/#hardware-requirements + MinCPUTest = 8 + MinCPUProd = 16 + MinMemoryTest = 32 + MinMemoryProd = 64 + MinNetworkGbpsTest = 1 + MinNetworkGbpsProd = 10 +) + +var ( + // So that we can fake this stuff up for unit tests + execCommand = exec.Command + procMemInfo = "/proc/meminfo" + devKvm = "/dev/kvm" + sysClassNetDevSpeed = "/sys/class/net/%s/speed" +) + +// The Run() method of a preflight.Check returns a string. If the string +// is empty, it means the check passed. Otherwise, the string contains +// some text explaining why the check failed. The error value will be set +// if the check itself failed to run at all for some reason. +type Check interface { + Run() (string, error) +} + +type CPUCheck struct{} +type MemoryCheck struct{} +type VirtCheck struct{} +type KVMHostCheck struct{} +type NetworkSpeedCheck struct { + Dev string +} + +func (c CPUCheck) Run() (msg string, err error) { + out, err := execCommand("/usr/bin/nproc", "--all").Output() + if err != nil { + return + } + nproc, _ := strconv.Atoi(strings.TrimSpace(string(out))) + if nproc < MinCPUTest { + msg = fmt.Sprintf("Only %d CPU cores detected. Harvester requires at least %d cores for testing and %d for production use.", + nproc, MinCPUTest, MinCPUProd) + } else if nproc < MinCPUProd { + msg = fmt.Sprintf("%d CPU cores detected. Harvester requires at least %d cores for production use.", + nproc, MinCPUProd) + } + return +} + +func (c MemoryCheck) Run() (msg string, err error) { + meminfo, err := os.Open(procMemInfo) + if err != nil { + return + } + defer meminfo.Close() + scanner := bufio.NewScanner(meminfo) + var memTotalKiB int + for scanner.Scan() { + if n, _ := fmt.Sscanf(scanner.Text(), "MemTotal: %d kB", &memTotalKiB); n == 1 { + break + } + } + if memTotalKiB == 0 { + err = errors.New("unable to extract MemTotal from /proc/cpuinfo") + return + } + // MemTotal from /proc/cpuinfo is a bit less than the actual physical + // memory in the system, due to reserved RAM not being included, so + // we can't actually do a trivial check of MemTotalGiB < MinMemoryTest, + // because it will fail. For example: + // - A host with 32GiB RAM may report MemTotal 32856636 = 31.11GiB + // - A host with 64GiB RAM may report MemTotal 65758888 = 62.71GiB + // - A host with 128GiB RAM may report MemTotal 131841120 = 125.73GiB + // This means we have to test against a slightly lower number. Knocking + // 5% off is somewhat arbitrary but probably not unreasonable (e.g. for + // 32GB we're actually allowing anything over 30.4GB, and for 64GB we're + // allowing anything over 60.8GB). + // Note that the above also means the warning messages below will be a + // bit off (e.g. something like "System reports 31GiB RAM" on a 32GiB + // system). + memTotalGiB := memTotalKiB / (1 << 20) + memReported := fmt.Sprintf("%dGiB", memTotalGiB) + if memTotalGiB < 1 { + // Just in case someone runs it on a really tiny VM... + memReported = fmt.Sprintf("%dKiB", memTotalKiB) + } + if float32(memTotalGiB) < (MinMemoryTest * 0.95) { + msg = fmt.Sprintf("Only %s RAM detected. Harvester requires at least %dGiB for testing and %dGiB for production use.", + memReported, MinMemoryTest, MinMemoryProd) + } else if float32(memTotalGiB) < (MinMemoryProd * 0.95) { + msg = fmt.Sprintf("%s RAM detected. Harvester requires at least %dGiB for production use.", + memReported, MinMemoryProd) + } + return +} + +func (c VirtCheck) Run() (msg string, err error) { + out, err := execCommand("/usr/bin/systemd-detect-virt", "--vm").Output() + virt := strings.TrimSpace(string(out)) + if err != nil { + // systemd-detect-virt will return a non-zero exit code + // and print "none" if it doesn't detect a virtualization + // environment. The non-zero exit code manifests as a + // non nil err here, so we have to handle that case and + // return success from this check, because we're not + // running virtualized. + if virt == "none" { + err = nil + } + return + } + msg = fmt.Sprintf("System is virtualized (%s) which is not supported in production.", virt) + return +} + +func (c KVMHostCheck) Run() (msg string, err error) { + if _, err = os.Stat(devKvm); errors.Is(err, fs.ErrNotExist) { + msg = "Harvester requires hardware-assisted virtualization, but /dev/kvm does not exist." + err = nil + } + return +} + +func (c NetworkSpeedCheck) Run() (msg string, err error) { + speedPath := fmt.Sprintf(sysClassNetDevSpeed, c.Dev) + out, err := os.ReadFile(speedPath) + if err != nil { + return + } + speedMbps, _ := strconv.Atoi(strings.TrimSpace(string(out))) + if speedMbps < 1 { + // speedMbps will be 0 if strconv.Atoi fails for some reason, + // or -1 (if you can believe that) when using virtio NICs when + // testing under virtualization. + err = fmt.Errorf("unable to determine NIC speed from %s (got %d)", speedPath, speedMbps) + return + } + // We need floats because 2.5Gbps ethernet is a thing. + var speedGbps = float32(speedMbps) / 1000 + if speedGbps < MinNetworkGbpsTest { + // Does anyone even _have_ < 1Gbps networking kit anymore? + // Still, it's theoretically possible someone could have messed + // up their switch config and be running 100Mbps... + msg = fmt.Sprintf("Link speed of %s is only %dMpbs. Harvester requires at least %dGbps for testing and %dGbps for production use.", + c.Dev, speedMbps, MinNetworkGbpsTest, MinNetworkGbpsProd) + } else if speedGbps < MinNetworkGbpsProd { + msg = fmt.Sprintf("Link speed of %s is %gGbps. Harvester requires at least %dGbps for production use.", + c.Dev, speedGbps, MinNetworkGbpsProd) + } + return +} diff --git a/pkg/preflight/checks_test.go b/pkg/preflight/checks_test.go new file mode 100644 index 000000000..51803c3a0 --- /dev/null +++ b/pkg/preflight/checks_test.go @@ -0,0 +1,169 @@ +package preflight + +import ( + "fmt" + "os" + "os/exec" + "testing" + + "github.com/stretchr/testify/assert" +) + +type fakeOutput struct { + output string + rc int +} + +var ( + execOutputs = map[string]fakeOutput{ + "nproc 4": {"4\n", 0}, + "nproc 8": {"8\n", 0}, + "nproc 16": {"16\n", 0}, + "kvm": {"kvm\n", 0}, + "metal": {"none\n", 1}, + } +) + +// It turns out to be really irritating to mock exec.Command(). +// The trick here is: in normal execution (i.e. not under test), +// execCommand is a pointer to exec.Command(), so it just runs as +// usual. When under test, we replace execCommand with a function +// that in turn calls _this_ function, with one of the keys in the +// execOutputs above. This function then runs the TestHelperProcess +// test, passing through that key... +func fakeExecCommand(command string) *exec.Cmd { + cs := []string{"-test.run=TestHelperProcess", "--", command} + cmd := exec.Command(os.Args[0], cs...) + cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} + return cmd +} + +// ...and TestHelperProcess looks up the key in execOutputs, and +// behaves as mocked, i.e. it will print the fake output to STDOUT, +// and will exit with the fake return code. +// One irritating subtlety here is that the `go test` framework +// may emit junk to STDERR, so this mocking technique only really +// works for mocking return codes and content on STDOUT. Still, +// that's OK for our purposes here. +func TestHelperProcess(_ *testing.T) { + if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { + return + } + + args := os.Args + for len(args) > 0 { + if args[0] == "--" { + args = args[1:] + break + } + args = args[1:] + } + if len(args) < 1 { + os.Exit(1) + } + + output, ok := execOutputs[args[0]] + if !ok { + os.Exit(1) + } + + fmt.Print(output.output) + os.Exit(output.rc) +} + +func TestCPUCheck(t *testing.T) { + defer func() { execCommand = exec.Command }() + + expectedOutputs := map[string]string{ + "nproc 4": "Only 4 CPU cores detected. Harvester requires at least 8 cores for testing and 16 for production use.", + "nproc 8": "8 CPU cores detected. Harvester requires at least 16 cores for production use.", + "nproc 16": "", + } + + check := CPUCheck{} + for key, expectedOutput := range expectedOutputs { + execCommand = func(_ string, _ ...string) *exec.Cmd { + return fakeExecCommand(key) + } + msg, err := check.Run() + assert.Nil(t, err) + assert.Equal(t, expectedOutput, msg) + } +} + +func TestVirtCheck(t *testing.T) { + defer func() { execCommand = exec.Command }() + + expectedOutputs := map[string]string{ + "kvm": "System is virtualized (kvm) which is not supported in production.", + "metal": "", + } + + check := VirtCheck{} + for key, expectedOutput := range expectedOutputs { + execCommand = func(_ string, _ ...string) *exec.Cmd { + return fakeExecCommand(key) + } + msg, err := check.Run() + assert.Nil(t, err) + assert.Equal(t, expectedOutput, msg) + } + +} + +func TestMemoryCheck(t *testing.T) { + defaultMemInfo := procMemInfo + defer func() { procMemInfo = defaultMemInfo }() + + expectedOutputs := map[string]string{ + "./testdata/meminfo-512MiB": "Only 458112KiB RAM detected. Harvester requires at least 32GiB for testing and 64GiB for production use.", + "./testdata/meminfo-32GiB": "31GiB RAM detected. Harvester requires at least 64GiB for production use.", + "./testdata/meminfo-64GiB": "", + } + + check := MemoryCheck{} + for file, expectedOutput := range expectedOutputs { + procMemInfo = file + msg, err := check.Run() + assert.Nil(t, err) + assert.Equal(t, expectedOutput, msg) + } +} + +func TestKVMHostCheck(t *testing.T) { + defaultDevKvm := devKvm + defer func() { devKvm = defaultDevKvm }() + + expectedOutputs := map[string]string{ + "./testdata/dev-kvm-does-not-exist": "Harvester requires hardware-assisted virtualization, but /dev/kvm does not exist.", + "./testdata/dev-kvm": "", + } + + check := KVMHostCheck{} + for file, expectedOutput := range expectedOutputs { + devKvm = file + msg, err := check.Run() + assert.Nil(t, err) + assert.Equal(t, expectedOutput, msg) + } +} + +func TestNetworkSpeedCheck(t *testing.T) { + defaultSysClassNetDevSpeed := sysClassNetDevSpeed + defer func() { sysClassNetDevSpeed = defaultSysClassNetDevSpeed }() + + expectedOutputs := map[string]string{ + "./testdata/%s-speed-100": "Link speed of eth0 is only 100Mpbs. Harvester requires at least 1Gbps for testing and 10Gbps for production use.", + "./testdata/%s-speed-1000": "Link speed of eth0 is 1Gbps. Harvester requires at least 10Gbps for production use.", + "./testdata/%s-speed-2500": "Link speed of eth0 is 2.5Gbps. Harvester requires at least 10Gbps for production use.", + "./testdata/%s-speed-10000": "", + } + + check := NetworkSpeedCheck{"eth0"} + for file, expectedOutput := range expectedOutputs { + sysClassNetDevSpeed = file + msg, err := check.Run() + assert.Nil(t, err) + assert.Equal(t, expectedOutput, msg) + } +} diff --git a/pkg/util/disk.go b/pkg/util/disk.go index d5e9f417e..a2cad4647 100644 --- a/pkg/util/disk.go +++ b/pkg/util/disk.go @@ -22,7 +22,7 @@ func ParsePartitionSize(diskSizeBytes uint64, partitionSize string) (uint64, err actualDiskSizeBytes := diskSizeBytes - fixedOccupiedSize if !sizeRegexp.MatchString(partitionSize) { - return 0, fmt.Errorf("Partition size must end with 'Mi' or 'Gi'. Decimals and negatives are not allowed.") + return 0, fmt.Errorf("Partition size must end with 'Mi' or 'Gi'. Decimals and negatives are not allowed") } size, err := strconv.ParseUint(partitionSize[:len(partitionSize)-2], 10, 64) diff --git a/pkg/util/disk_test.go b/pkg/util/disk_test.go index 0099cd905..7f368722a 100644 --- a/pkg/util/disk_test.go +++ b/pkg/util/disk_test.go @@ -51,22 +51,22 @@ func TestParsePartitionSize(t *testing.T) { { diskSize: 2000 * GiByteMultiplier, partitionSize: "abcd", - err: "Partition size must end with 'Mi' or 'Gi'. Decimals and negatives are not allowed.", + err: "Partition size must end with 'Mi' or 'Gi'. Decimals and negatives are not allowed", }, { diskSize: 2000 * GiByteMultiplier, partitionSize: "1Ti", - err: "Partition size must end with 'Mi' or 'Gi'. Decimals and negatives are not allowed.", + err: "Partition size must end with 'Mi' or 'Gi'. Decimals and negatives are not allowed", }, { diskSize: 2000 * GiByteMultiplier, partitionSize: "50Ki", - err: "Partition size must end with 'Mi' or 'Gi'. Decimals and negatives are not allowed.", + err: "Partition size must end with 'Mi' or 'Gi'. Decimals and negatives are not allowed", }, { diskSize: 2000 * GiByteMultiplier, partitionSize: "5.5", - err: "Partition size must end with 'Mi' or 'Gi'. Decimals and negatives are not allowed.", + err: "Partition size must end with 'Mi' or 'Gi'. Decimals and negatives are not allowed", }, { diskSize: 400 * GiByteMultiplier, diff --git a/pkg/widgets/dropdown.go b/pkg/widgets/dropdown.go index d1902589e..07643d611 100644 --- a/pkg/widgets/dropdown.go +++ b/pkg/widgets/dropdown.go @@ -88,7 +88,7 @@ func (d *DropDown) Show() error { d.Text = d.Select.options[0].Text } } - err = d.g.SetKeybinding(d.ViewName, gocui.KeyTab, gocui.ModNone, func(g *gocui.Gui, v *gocui.View) error { + err = d.g.SetKeybinding(d.ViewName, gocui.KeyTab, gocui.ModNone, func(_ *gocui.Gui, _ *gocui.View) error { d.Select.Value = d.Value d.Select.Panel.SetLocation(x0, y0, x1, y0+1) return d.Select.Show() @@ -122,7 +122,7 @@ func (d *DropDown) Show() error { } return d.KeyBindings[gocui.KeyEnter](g, v) } - d.Select.KeyBindings[gocui.KeyEsc] = func(g *gocui.Gui, v *gocui.View) error { + d.Select.KeyBindings[gocui.KeyEsc] = func(_ *gocui.Gui, _ *gocui.View) error { if err = d.Select.Close(); err != nil { return err } diff --git a/pkg/widgets/panel.go b/pkg/widgets/panel.go index 2e065e3f4..aa5806748 100644 --- a/pkg/widgets/panel.go +++ b/pkg/widgets/panel.go @@ -137,8 +137,32 @@ func (p *Panel) SetLocation(x0, y0, x1, y1 int) { } func (p *Panel) SetContent(content string) { +<<<<<<< HEAD p.Content = content p.g.Update(func(g *gocui.Gui) error { +======= + // Need to subtract 1 from panelWidth here to match the view.Size() + // calculation done in gocui's view.Draw() function + panelWidth := p.X1 - p.X0 - 1 + if panelWidth > 0 { + p.Content = "" + lines := strings.Split(content, "\n") + for i, line := range lines { + if i > 0 { + p.Content += "\n" + } + if len(line) < panelWidth { + p.Content += line + } else { + p.Content += wrapText(line, panelWidth) + } + } + } else { + // Just in case we somehow get here without valid dimensions + p.Content = content + } + p.g.Update(func(_ *gocui.Gui) error { +>>>>>>> dd16a95 (Bump golangci-lint + fix lint issues) v, err := p.g.View(p.Name) if err != nil { if err != gocui.ErrUnknownView { diff --git a/pkg/widgets/select.go b/pkg/widgets/select.go index 006b75e8a..d63708785 100644 --- a/pkg/widgets/select.go +++ b/pkg/widgets/select.go @@ -201,7 +201,7 @@ func (s *Select) updateSelectedStatus(v *gocui.View) error { func (s *Select) setOptionsKeyBindings(viewName string) error { setOptionsKeyBindings(s.g, viewName) if s.multi { - handler := func(g *gocui.Gui, v *gocui.View) error { + handler := func(_ *gocui.Gui, v *gocui.View) error { _, cy := v.Cursor() if len(s.options) >= cy+1 { s.selectedIndexes[cy] = !s.selectedIndexes[cy] diff --git a/pkg/widgets/util.go b/pkg/widgets/util.go index e25d82325..1ae90b6c2 100644 --- a/pkg/widgets/util.go +++ b/pkg/widgets/util.go @@ -4,7 +4,7 @@ import ( "github.com/jroimartin/gocui" ) -func ArrowUp(g *gocui.Gui, v *gocui.View) error { +func ArrowUp(_ *gocui.Gui, v *gocui.View) error { if v == nil || isAtTop(v) { return nil } @@ -19,7 +19,7 @@ func ArrowUp(g *gocui.Gui, v *gocui.View) error { return nil } -func ArrowDown(g *gocui.Gui, v *gocui.View) error { +func ArrowDown(_ *gocui.Gui, v *gocui.View) error { if v == nil || isAtEnd(v) { return nil }