diff --git a/pkg/config/config.go b/pkg/config/config.go index 1086ac99d..39279c4f5 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -150,6 +150,10 @@ type Install struct { VipHwAddr string `json:"vipHwAddr,omitempty"` VipMode string `json:"vipMode,omitempty"` + ClusterDNS string `json:"clusterDns,omitempty"` + ClusterPodCIDR string `json:"clusterPodCidr,omitempty"` + ClusterServiceCIDR string `json:"clusterServiceCidr,omitempty"` + ForceEFI bool `json:"forceEfi,omitempty"` Device string `json:"device,omitempty"` ConfigURL string `json:"configUrl,omitempty"` diff --git a/pkg/config/templates/rancherd-10-harvester.yaml b/pkg/config/templates/rancherd-10-harvester.yaml index 6c8e5ddc1..3d99eda3c 100644 --- a/pkg/config/templates/rancherd-10-harvester.yaml +++ b/pkg/config/templates/rancherd-10-harvester.yaml @@ -202,6 +202,10 @@ resources: enabled: true kube-vip-cloud-provider: enabled: true + promote: + clusterPodCIDR: {{ or .ClusterPodCIDR "10.52.0.0/16" }} + clusterServiceCIDR: {{ or .ClusterServiceCIDR "10.53.0.0/16" }} + clusterDNS: {{ or .ClusterDNS "10.53.0.10" }} - apiVersion: management.cattle.io/v3 kind: ManagedChart metadata: diff --git a/pkg/config/templates/rke2-90-harvester-server.yaml b/pkg/config/templates/rke2-90-harvester-server.yaml index 39077a406..01dcee0cb 100644 --- a/pkg/config/templates/rke2-90-harvester-server.yaml +++ b/pkg/config/templates/rke2-90-harvester-server.yaml @@ -1,7 +1,7 @@ cni: multus,canal -cluster-cidr: 10.52.0.0/16 -service-cidr: 10.53.0.0/16 -cluster-dns: 10.53.0.10 +cluster-cidr: {{ or .ClusterPodCIDR "10.52.0.0/16" }} +service-cidr: {{ or .ClusterServiceCIDR "10.53.0.0/16" }} +cluster-dns: {{ or .ClusterDNS "10.53.0.10" }} tls-san: - {{ .Vip }} {{- with $args := .GetKubeletArgs }} diff --git a/pkg/console/constant.go b/pkg/console/constant.go index 64368668f..a1ea13d0f 100644 --- a/pkg/console/constant.go +++ b/pkg/console/constant.go @@ -71,6 +71,18 @@ const ( vipLabel = "VIP" askVipMethodLabel = "VIP Mode" + clusterNetworkTitle = "Configure cluster network" + clusterPodCIDRLabel = "Pod CIDR" + clusterServiceCIDRLabel = "Service CIDR" + clusterDNSLabel = "Cluster DNS IP" + clusterPodCIDRPanel = "podCIDRPanel" + clusterServiceCIDRPanel = "serviceCIDRPanel" + clusterDNSPanel = "clusterDNSPanel" + clusterNetworkNotePanel = "clusterNetworkNotePanel" + clusterNetworkDNSNotePanel = "clusterNetworkDNSNotePanel" + clusterNetworkValidatorPanel = "clusterNetworkValidatorPanel" + clusterNetworkNote = "Note: Leave blank to use the default pod CIDR 10.52.0.0/16, service CIDR 10.53.0.0/16 and cluster DNS 10.53.0.10. If the service CIDR is changed, the DNS IP must be updated to be within the service CIDR." + clusterTokenCreateNote = "Note: The token is used for adding nodes to the cluster" clusterTokenJoinNote = "Note: Input the token of the existing cluster" serverURLNote = "Note: Input VIP/domain name of the management node" diff --git a/pkg/console/install_panels.go b/pkg/console/install_panels.go index cd3dbc9bc..a77639e31 100644 --- a/pkg/console/install_panels.go +++ b/pkg/console/install_panels.go @@ -3,6 +3,7 @@ package console import ( "fmt" "net" + "net/netip" "os" "os/exec" "strconv" @@ -157,6 +158,7 @@ func setPanels(c *Console) error { addDiskPanel, addHostnamePanel, addNetworkPanel, + addClusterNetworkPanel, addVIPPanel, addDNSServersPanel, addNTPServersPanel, @@ -1230,6 +1232,9 @@ func addHostnamePanel(c *Console) error { prev := func(_ *gocui.Gui, _ *gocui.View) error { c.CloseElements(hostnamePanel, hostnameValidatorPanel) + if c.config.Install.Mode == config.ModeCreate { + return showClusterNetworkPage(c) + } return showNetworkPage(c) } @@ -1448,6 +1453,9 @@ func addNetworkPanel(c *Console) error { spinner.Stop(false, "") g.Update(func(_ *gocui.Gui) error { closeThisPage() + if c.config.Install.Mode == config.ModeCreate { + return showClusterNetworkPage(c) + } return showHostnamePage(c) }) } @@ -1744,6 +1752,249 @@ func addNetworkPanel(c *Console) error { return nil } +func showClusterNetworkPage(c *Console) error { + return showNext( + c, + clusterServiceCIDRPanel, + clusterDNSPanel, + clusterNetworkNotePanel, + clusterNetworkValidatorPanel, + clusterPodCIDRPanel) +} + +func addClusterNetworkPanel(c *Console) error { + // define page navigation + closePage := func() { + c.CloseElements( + clusterPodCIDRPanel, + clusterServiceCIDRPanel, + clusterDNSPanel, + clusterNetworkNotePanel, + clusterNetworkValidatorPanel) + } + + prevPage := func(_ *gocui.Gui, _ *gocui.View) error { + closePage() + return showNetworkPage(c) + } + + nextPage := func() error { + closePage() + return showHostnamePage(c) + } + + setLocation := createVerticalLocator(c) + + // set up the pod CIDR input panel + podCIDRInput, err := widgets.NewInput( + c.Gui, + clusterPodCIDRPanel, + clusterPodCIDRLabel, + false) + if err != nil { + return err + } + podCIDRInput.PreShow = func() error { + c.Cursor = true + podCIDRInput.Value = c.config.ClusterPodCIDR + + if err := c.setContentByName( + titlePanel, + clusterNetworkTitle); err != nil { + return err + } + + if err := c.setContentByName( + clusterNetworkNotePanel, + clusterNetworkNote); err != nil { + return err + } + + // reset any previous error in the validator panel before + // showing the rest of the page + return c.setContentByName(clusterNetworkValidatorPanel, "") + } + + // set up the service CIDR input panel + serviceCIDRInput, err := widgets.NewInput( + c.Gui, + clusterServiceCIDRPanel, + clusterServiceCIDRLabel, + false) + if err != nil { + return err + } + + serviceCIDRInput.PreShow = func() error { + c.Cursor = true + serviceCIDRInput.Value = c.config.ClusterServiceCIDR + return nil + } + + // set up the cluster DNS input panel + dnsInput, err := widgets.NewInput( + c.Gui, + clusterDNSPanel, + clusterDNSLabel, + false) + if err != nil { + return err + } + + dnsInput.PreShow = func() error { + c.Cursor = true + dnsInput.Value = c.config.ClusterDNS + return nil + } + + // define inputs validators + validateCIDR := func(cidr string) error { + cidr = strings.TrimSpace(cidr) + if cidr == "" { + return nil + } + + _, err := netip.ParsePrefix(cidr) + return err + } + + validateDNSIP := func(ip string) error { + ip = strings.TrimSpace(ip) + serviceCIDR, err := serviceCIDRInput.GetData() + if err != nil { + return err + } + if ip == "" && serviceCIDR == "" { + return nil + } + + // the DNS IP address must be well-formed and within the + // service CIDR + ipAddr, err := netip.ParseAddr(ip) + if err != nil { + return fmt.Errorf("Invalid cluster DNS IP: %w", err) + } + + svcNet, err := netip.ParsePrefix(serviceCIDR) + if err != nil { + return fmt.Errorf("To override the cluster DNS IP, the service CIDR must be valid: %w", err) + } + + if !svcNet.Contains(ipAddr) { + return fmt.Errorf("Invalid cluster DNS IP: %s is not in the service CIDR %s", ip, serviceCIDR) + } + + return nil + } + + // define input confirm actions + podCIDRConfirm := func(_ *gocui.Gui, _ *gocui.View) error { + podCIDR, err := podCIDRInput.GetData() + if err != nil { + return err + } + + if err := validateCIDR(podCIDR); err != nil { + c.setContentByName( + clusterNetworkValidatorPanel, + fmt.Sprintf("Invalid pod CIDR: %s", err)) + return nil + } + c.config.ClusterPodCIDR = podCIDR + + // reset any previous error in the validator panel before + // moving to the next panel + c.setContentByName(clusterNetworkValidatorPanel, "") + return showNext(c, clusterServiceCIDRPanel) + } + + serviceCIDRConfirm := func(_ *gocui.Gui, _ *gocui.View) error { + serviceCIDR, err := serviceCIDRInput.GetData() + if err != nil { + return err + } + + if err := validateCIDR(serviceCIDR); err != nil { + c.setContentByName( + clusterNetworkValidatorPanel, + fmt.Sprintf("Invalid service CIDR: %s", err)) + return nil + } + c.config.ClusterServiceCIDR = serviceCIDR + + // reset any previous error in the validator panel before + // moving to the next panel + c.setContentByName(clusterNetworkValidatorPanel, "") + return showNext(c, clusterDNSPanel) + } + + dnsConfirm := func(_ *gocui.Gui, _ *gocui.View) error { + dns, err := dnsInput.GetData() + if err != nil { + return err + } + if err := validateDNSIP(dns); err != nil { + c.setContentByName(clusterNetworkValidatorPanel, err.Error()) + return nil + } + c.config.ClusterDNS = dns + + // reset the validator panel before moving to the next page + c.setContentByName(clusterNetworkValidatorPanel, "") + return nextPage() + } + + // configure key bindings and element locations + podCIDRInput.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ + gocui.KeyEsc: prevPage, + gocui.KeyArrowUp: prevPage, + gocui.KeyArrowDown: podCIDRConfirm, + gocui.KeyEnter: podCIDRConfirm, + } + setLocation(podCIDRInput, 3) + c.AddElement(clusterPodCIDRPanel, podCIDRInput) + + serviceCIDRInput.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ + gocui.KeyEsc: prevPage, + gocui.KeyArrowUp: func(_ *gocui.Gui, _ *gocui.View) error { + return showNext(c, clusterPodCIDRPanel) + }, + gocui.KeyArrowDown: serviceCIDRConfirm, + gocui.KeyEnter: serviceCIDRConfirm, + } + setLocation(serviceCIDRInput, 3) + c.AddElement(clusterServiceCIDRPanel, serviceCIDRInput) + + dnsInput.KeyBindings = map[gocui.Key]func(*gocui.Gui, *gocui.View) error{ + gocui.KeyEsc: prevPage, + gocui.KeyArrowUp: func(_ *gocui.Gui, _ *gocui.View) error { + return showNext(c, clusterServiceCIDRPanel) + }, + gocui.KeyArrowDown: dnsConfirm, + gocui.KeyEnter: dnsConfirm, + } + setLocation(dnsInput, 3) + c.AddElement(clusterDNSPanel, dnsInput) + + // set up notes panels + notePanel := widgets.NewPanel(c.Gui, clusterNetworkNotePanel) + notePanel.Focus = false + notePanel.Wrap = true + setLocation(notePanel, 4) + c.AddElement(clusterNetworkNotePanel, notePanel) + + // set up validator panel for warning and error messages + validatorPanel := widgets.NewPanel(c.Gui, clusterNetworkValidatorPanel) + validatorPanel.FgColor = gocui.ColorRed + validatorPanel.Focus = false + maxX, _ := c.Gui.Size() + validatorPanel.X1 = maxX / 8 * 6 + setLocation(validatorPanel, 3) + c.AddElement(clusterNetworkValidatorPanel, validatorPanel) + + return nil +} + func getBondModeOptions() ([]widgets.Option, error) { return []widgets.Option{ {