yast/yast-network

View on GitHub
src/lib/y2network/widgets/boot_protocol.rb

Summary

Maintainability
D
1 day
Test Coverage
# Copyright (c) [2019] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "yast"
require "cwm/custom_widget"

require "y2network/boot_protocol"

Yast.import "DNS"
Yast.import "Hostname"
Yast.import "IP"
Yast.import "Label"
Yast.import "Netmask"
Yast.import "NetHwDetection"
Yast.import "Popup"
Yast.import "ProductFeatures"
Yast.import "UI"

module Y2Network
  module Widgets
    class BootProtocol < CWM::CustomWidget
      def initialize(settings)
        super()
        textdomain "network"
        @settings = settings
      end

      def contents
        RadioButtonGroup(
          Id(:bootproto),
          VBox(
            Left(
              HBox(
                RadioButton(
                  Id(:bootproto_none),
                  Opt(:notify),
                  _("No Link and IP Setup (Bond Ports)")
                )
              )
            ),
            Left(
              HBox(
                RadioButton(Id(:bootproto_dynamic), Opt(:notify), _("Dynamic Address")),
                HSpacing(2),
                ComboBox(
                  Id(:bootproto_dyn),
                  Opt(:notify),
                  "",
                  [
                    Item(Id(:bootproto_dhcp), "DHCP"),
                    Item(Id(:bootproto_dhcp_auto), "DHCP+Zeroconf"),
                    Item(Id(:bootproto_auto), "Zeroconf")
                  ]
                ),
                HSpacing(2),
                ComboBox(
                  Id(:bootproto_dhcp_mode),
                  "",
                  [
                    Item(Id(:bootproto_dhcp_both), _("DHCP both version 4 and 6")),
                    Item(Id(:bootproto_dhcp_v4), _("DHCP version 4 only")),
                    Item(Id(:bootproto_dhcp_v6), _("DHCP version 6 only"))
                  ]
                )
              )
            ),
            VBox(
              Left(
                RadioButton(
                  Id(:bootproto_static),
                  Opt(:notify),
                  _("Statically Assigned IP Address")
                )
              ),
              HBox(
                # TODO: When object CWM top level is used, then use here IPAddress object
                InputField(Id(:bootproto_ipaddr), Opt(:hstretch), _("&IP Address")),
                HSpacing(1),
                InputField(Id(:bootproto_netmask), Opt(:hstretch), _("&Subnet Mask")),
                HSpacing(1),
                InputField(Id(:bootproto_hostname), Opt(:hstretch), _("&Hostname")),
                HStretch()
              )
            )
          )
        )
      end

      def init
        case @settings.boot_protocol
        when Y2Network::BootProtocol::STATIC
          Yast::UI.ChangeWidget(Id(:bootproto), :CurrentButton, :bootproto_static)
          Yast::UI.ChangeWidget(
            Id(:bootproto_ipaddr),
            :Value,
            @settings.ip_address
          )
          Yast::UI.ChangeWidget(
            Id(:bootproto_netmask),
            :Value,
            @settings.subnet_prefix
          )
          Yast::UI.ChangeWidget(
            Id(:bootproto_hostname),
            :Value,
            @settings.hostname
          )
        when Y2Network::BootProtocol::DHCP
          Yast::UI.ChangeWidget(Id(:bootproto), :CurrentButton, :bootproto_dynamic)
          Yast::UI.ChangeWidget(Id(:bootproto_dhcp_mode), :Value, :bootproto_dhcp_both)
          Yast::UI.ChangeWidget(Id(:bootproto_dyn), :Value, :bootproto_dhcp)
        when Y2Network::BootProtocol::DHCP4
          Yast::UI.ChangeWidget(Id(:bootproto), :CurrentButton, :bootproto_dynamic)
          Yast::UI.ChangeWidget(Id(:bootproto_dhcp_mode), :Value, :bootproto_dhcp_v4)
          Yast::UI.ChangeWidget(Id(:bootproto_dyn), :Value, :bootproto_dhcp)
        when Y2Network::BootProtocol::DHCP6
          Yast::UI.ChangeWidget(Id(:bootproto), :CurrentButton, :bootproto_dynamic)
          Yast::UI.ChangeWidget(Id(:bootproto_dhcp_mode), :Value, :bootproto_dhcp_v6)
          Yast::UI.ChangeWidget(Id(:bootproto_dyn), :Value, :bootproto_dhcp)
        when Y2Network::BootProtocol::DHCP_AUTOIP
          Yast::UI.ChangeWidget(Id(:bootproto), :CurrentButton, :bootproto_dynamic)
          Yast::UI.ChangeWidget(Id(:bootproto_dyn), :Value, :bootproto_dhcp_auto)
        when Y2Network::BootProtocol::AUTOIP
          Yast::UI.ChangeWidget(Id(:bootproto), :CurrentButton, :bootproto_dynamic)
          Yast::UI.ChangeWidget(Id(:bootproto_dyn), :Value, :bootproto_auto)
        when Y2Network::BootProtocol::NONE, Y2Network::BootProtocol::IBFT
          Yast::UI.ChangeWidget(Id(:bootproto), :CurrentButton, :bootproto_none)
        end

        handle
      end

      def handle
        case value
        when :bootproto_static
          static_enabled(true)
          dynamic_enabled(false)
          one_ip = Yast::UI.QueryWidget(Id(:bootproto_ipaddr), :Value)
          if one_ip.empty?
            current_hostname = Yast::Hostname.MergeFQ(Yast::DNS.hostname, Yast::DNS.domain)
            if ![nil, "", "localhost"].include?(current_hostname)
              log.info "Presetting global hostname"
              Yast::UI.ChangeWidget(Id(:bootproto_hostname), :Value, current_hostname)
            end
          end
        when :bootproto_dynamic
          static_enabled(false)
          dynamic_enabled(true)
        when :bootproto_none
          static_enabled(false)
          dynamic_enabled(false)
        else
          raise "Unexpected value for boot protocol #{value.inspect}"
        end

        nil
      end

      def store
        # FIXME: this value reset should be in backend in general not Yast::UI responsibility
        @settings.ip_address = ""
        @settings.subnet_prefix = ""
        case value
        when :bootproto_none
          bootproto = "none"
          @settings.boot_protocol = bootproto
        when :bootproto_static
          @settings.boot_protocol = "static"
          @settings.ip_address = Yast::UI.QueryWidget(:bootproto_ipaddr, :Value)
          @settings.subnet_prefix = Yast::UI.QueryWidget(:bootproto_netmask, :Value)
          @settings.hostname = Yast::UI.QueryWidget(:bootproto_hostname, :Value)
        when :bootproto_dynamic
          case Yast::UI.QueryWidget(:bootproto_dyn, :Value)
          when :bootproto_dhcp
            case Yast::UI.QueryWidget(:bootproto_dhcp_mode, :Value)
            when :bootproto_dhcp_both
              @settings.boot_protocol = "dhcp"
            when :bootproto_dhcp_v4
              @settings.boot_protocol = "dhcp4"
            when :bootproto_dhcp_v6
              @settings.boot_protocol = "dhcp6"
            else
              raise "Unexpected dhcp mode value " \
                    "#{Yast::UI.QueryWidget(:bootproto_dhcp_mode, :Value).inspect}"
            end
          when :bootproto_dhcp_auto
            @settings.boot_protocol = "dhcp+autoip"
          when :bootproto_auto
            @settings.boot_protocol = "autoip"
          else
            raise "Unexpected dynamic mode value " \
                  "#{Yast::UI.QueryWidget(:bootproto_dyn, :Value).inspect}"
          end
        else
          raise "Unexpected boot protocol value #{Yast::UI.QueryWidget(:bootproto, :Value).inspect}"
        end
      end

      def validate
        return true if value != :bootproto_static

        ipa = Yast::UI.QueryWidget(:bootproto_ipaddr, :Value)
        if !Yast::IP.Check(ipa)
          Yast::Popup.Error(_("No valid IP address."))
          Yast::UI.SetFocus(:bootproto_ipaddr)
          return false
        end

        mask = Yast::UI.QueryWidget(:bootproto_netmask, :Value)
        if mask != "" && !valid_netmask(ipa, mask)
          Yast::Popup.Error(_("No valid netmask or prefix length."))
          Yast::UI.SetFocus(:bootproto_netmask)
          return false
        end

        hname = Yast::UI.QueryWidget(:bootproto_hostname, :Value)
        if !hname.empty?
          if !Yast::Hostname.CheckFQ(hname)
            Yast::Popup.Error(_("Invalid hostname."))
            Yast::UI.SetFocus(:bootproto_hostname)
            return false
          end
        # There'll be no 127.0.0.2 -> remind user to define some hostname
        elsif !Yast::Popup.YesNo(
          _(
            "No hostname has been specified. We recommend to associate \n" \
            "a hostname with a static IP, otherwise the machine name will \n" \
            "not be resolvable without an active network connection.\n" \
            "\n" \
            "Really leave the hostname blank?\n"
          )
        )
          Yast::UI.SetFocus(:bootproto_hostname)
          return false
        end

        # validate duplication
        if Yast::NetHwDetection.DuplicateIP(ipa)
          Yast::UI.SetFocus(:bootproto_ipaddr)
          # Popup text
          if !Yast::Popup.YesNoHeadline(
            Yast::Label.WarningMsg,
            _("Duplicate IP address detected.\nReally continue?\n")
          )
            return false
          end
        end

        true
      end

      def help
        res = _(
          "<p><b><big>Address Setup</big></b></p>\n" \
          "<p>Select <b>No Address Setup</b> if you do not want " \
          "to assign an IP address to this device.\n" \
          "This is particularly useful for bonding ethernet devices.</p>\n"
        ) +
          # Address dialog help 2/8
          _(
            "<p>Select <b>Dynamic Address</b> if you do not have a static IP address \n" \
            "assigned by the system administrator or your Internet provider.</p>\n"
          ) +
          # Address dialog help 3/8
          _(
            "<p>Choose one of the dynamic address assignment methods. Select <b>DHCP</b>\n" \
            "if you have a DHCP server running on your local network. Network addresses \n" \
            "are then automatically obtained from the server.</p>\n"
          ) +
          # Address dialog help 4/8
          _(
            "<p>To search for an IP address and assign it statically, select \n" \
            "<b>Zeroconf</b>. To use DHCP and fall back to zeroconf, " \
            "select <b>DHCP + Zeroconf\n" \
            "</b>. Otherwise, the network addresses must be assigned <b>Statically</b>.</p>\n"
          )

        if Yast::ProductFeatures.GetBooleanFeature("network", "force_static_ip")
          res += _(
            "<p>DHCP configuration is not recommended for this product.\n" \
            "Components of this product might not work with DHCP.</p>"
          )
        end

        res
      end

      def dynamic_enabled(value)
        Yast::UI.ChangeWidget(Id(:bootproto_dyn), :Enabled, value)
        if value
          # dhcp mode works only with plain dhcp
          if :bootproto_dhcp == Yast::UI.QueryWidget(Id(:bootproto_dyn), :Value)
            Yast::UI.ChangeWidget(Id(:bootproto_dhcp_mode), :Enabled, value)
          else
            Yast::UI.ChangeWidget(Id(:bootproto_dhcp_mode), :Enabled, false)
          end
        else
          Yast::UI.ChangeWidget(Id(:bootproto_dhcp_mode), :Enabled, value)
        end
      end

      def static_enabled(value)
        Yast::UI.ChangeWidget(Id(:bootproto_ipaddr), :Enabled, value)
        Yast::UI.ChangeWidget(Id(:bootproto_netmask), :Enabled, value)
        Yast::UI.ChangeWidget(Id(:bootproto_hostname), :Enabled, value)
      end

      def value
        Yast::UI.QueryWidget(Id(:bootproto), :CurrentButton)
      end

      def valid_netmask(ip, mask)
        mask = mask[1..-1] if mask.start_with?("/")

        if Yast::IP.Check4(ip) && (Yast::Netmask.Check4(mask) || Yast::Netmask.CheckPrefix4(mask))
          return true
        end

        return true if Yast::IP.Check6(ip) && Yast::Netmask.Check6(mask)

        log.warn "IP address #{ip} is not valid"
        false
      end
    end
  end
end