yast/yast-registration

View on GitHub
src/lib/registration/clients/scc_auto.rb

Summary

Maintainability
B
6 hrs
Test Coverage
# encoding: utf-8

# ***************************************************************************
#
# 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 Novell, Inc.
#
# To contact Novell about this file by physical or electronic mail,
# you may find current contact information at www.novell.com
#
# ***************************************************************************

require "fileutils"
require "yast/suse_connect"

require "installation/auto_client"
require "registration/storage"
require "registration/sw_mgmt"
require "registration/autoyast_addons"
require "registration/registration"
require "registration/registration_ui"
require "registration/helpers"
require "registration/connect_helpers"
require "registration/ssl_certificate"
require "registration/url_helpers"
require "registration/ui/autoyast_config_workflow"
require "registration/ui/offline_migration_workflow"
require "registration/erb_renderer.rb"
require "y2packager/product_spec"
require "y2packager/medium_type"

Yast.import "UI"
Yast.import "Pkg"
Yast.import "Wizard"
Yast.import "Label"
Yast.import "Report"
Yast.import "Popup"
Yast.import "Packages"
Yast.import "Report"
Yast.import "Installation"

module Registration
  module Clients
    class SCCAuto < ::Installation::AutoClient
      include Yast::Logger
      extend Yast::I18n

      # popup message
      CONTACTING_MESSAGE = N_("Contacting the Registration Server")

      def initialize
        textdomain "registration"

        @config = ::Registration::Storage::Config.instance
      end

      # Create a textual summary
      # @return [String] summary of the current configuration
      def summary
        ::Registration::ErbRenderer.new(@config).render_erb_template("autoyast_summary.erb")
      end

      def reset
        @config.reset
        {}
      end

      # UI workflow definition
      def change
        ::Registration::UI::AutoyastConfigWorkflow.run(@config)
      end

      # Get all settings from the first parameter
      # (For use by autoinstallation.)
      # param [Hash] settings The structure to be imported.
      def import(settings)
        # if there is no registration section like can happen during auto upgrade
        return unless settings

        # Lazy load it as registration does not depend on ay, but scc_auto is run only in ay context
        Yast.import "AutoinstFunctions"

        # merge reg code if not defined in the profile but
        # available from other sources
        product = Yast::AutoinstFunctions.selected_product
        # If the real product (an instance from the Product class) is not
        # available (e.g. Online medium), just skip reading the regcode because
        # the short_name (which is required to find the regcode) is unknown at
        # this point. See bsc#1194440.
        if product&.respond_to?(:short_name) && !settings["reg_code"]
          reg_codes_loader = ::Registration::Storage::RegCodes.instance
          settings["reg_code"] = reg_codes_loader.reg_codes[product.short_name] || ""
        end

        log.debug "Importing config: #{settings}"
        @config.import(settings)
      end

      # Export the settings to a single Hash
      # (For use by autoinstallation.)
      # @return [Hash] AutoYast configuration
      def export
        ret = @config.export
        log.debug "Exported config: #{ret}"
        ret
      end

      # register the system, base product and optional addons
      # return true on success
      def write
        # registration disabled, nothing to do
        return true if !@config.do_registration && !Yast::Mode.update

        # initialize libzypp if applying settings in installed system or
        # in AutoYast configuration mode ("Apply to System")
        ::Registration::SwMgmt.init if Yast::Mode.normal || Yast::Mode.config

        return false unless set_registration_url

        # update the registration in AutoUpgrade mode if the old system was registered
        return migrate_reg if Yast::Mode.update

        # special handling for the online installation medium,
        # we need to evaluate the base products defined in the control.xml
        if Yast::Stage.initial && Y2Packager::MediumType.online?
          return false unless online_medium_config
        end

        ret = ::Registration::ConnectHelpers.catch_registration_errors do
          import_certificate(@config.reg_server_cert)
          register_base_product && register_addons
        end

        return false unless ret

        finish_registration

        true
      end

      def read
        ::Registration::ConnectHelpers.catch_registration_errors do
          @config.read
        end
      end

      # return extra packages needed by this module (none so far)
      # @return [Hash] required packages
      def packages
        ret = { "install" => [], "remove" => [] }
        log.info "Registration needs these packages: #{ret}"
        ret
      end

      def modified?
        @config.modified
      end

      def modified
        @config.modified = true
        true
      end

    private

      # set the registration URL from the profile or use the default
      # @return [Boolean] true on success
      def set_registration_url
        # set the registration URL
        url = @config.reg_server if @config.reg_server && !@config.reg_server.empty?

        # use SLP discovery
        if !url && @config.slp_discovery
          url = find_slp_server
          return false unless url
        end

        url ||= ::Registration::UrlHelpers.registration_url
        log.info "Registration URL: #{url}"

        # nil = use the default URL
        switch_registration(url)

        true
      end

      # Select the product from the control.xml (for the online installation medium)
      # @return [Boolean] true on success, false on failure
      def online_medium_config
        # import the GPG keys before refreshing the repositories
        Yast::Packages.ImportGPGKeys

        products = Y2Packager::ProductSpec.base_products

        # Lazy load it as registration does not depend on ay, but scc_auto is run only in ay context
        Yast.import "AutoinstFunctions"

        selected_product = Yast::AutoinstFunctions.selected_product
        log.info "selected product #{selected_product.inspect}"

        if !selected_product
          # TRANSLATORS: error message, %s is the XML path, e.g. "software/products"
          Yast::Report.Error(
            _("Missing product specification in the %s section") % "software/products"
          )
          return false
        end

        ay_product = selected_product.name
        control_product = products.find { |p| p.name == ay_product }

        if !control_product
          # TRANSLATORS: error message, %s is a product ID, e.g. "SLES"
          Yast::Report.Error(_("Product %s not found") % ay_product)
          return false
        end

        # mark the control file product as selected
        control_product.select

        true
      end

      # delete all previous services and repositories
      def repo_cleanup
        # we cannot use pkg-bindings here because loading services would trigger
        # service and repository refresh which we want to avoid (it might easily fail)
        old = Dir[File.join(Installation.destdir, "/etc/zypp/repos.d/*")] +
          Dir[File.join(Installation.destdir, "/etc/zypp/services.d/*")] +
          Dir[File.join(Installation.destdir, "/var/cache/zypp/*")]

        log.info "Removing #{old}"
        ::FileUtils.rm_rf(old)
      end

      # finish the registration process
      def finish_registration
        # save the registered repositories
        Yast::Pkg.SourceSaveAll

        if Yast::Mode.normal || Yast::Mode.config
          # popup message: registration finished properly
          Yast::Popup.Message(_("Registration was successfull."))
        elsif Yast::Stage.initial
          # copy the SSL certificate to the target system
          ::Registration::Helpers.copy_certificate_to_target
        end
      end

      # find registration server via SLP
      # @return [String,nil] URL of the server, nil on error
      def find_slp_server
        # do SLP query
        slp_services = ::Registration::UrlHelpers.slp_discovery_feedback
        slp_urls = slp_services.map(&:slp_url)

        # remove possible duplicates
        slp_urls.uniq!
        log.info "Found #{slp_urls.size} SLP servers"

        case slp_urls.size
        when 0
          Yast::Report.Error(_("SLP discovery failed, no server found"))
          return nil
        when 1
          return slp_urls.first
        else
          # more than one server found: let the user select, we cannot automatically
          # decide which one to use, asking user in AutoYast mode is not nice
          # but better than aborting the installation...
          return ::Registration::UrlHelpers.slp_service_url
        end
      end

      # download and install the specified SSL certificate to the system
      # @param url [String] URL of the certificate
      def import_certificate(url)
        return unless url && !url.empty?
        log.info "Importing certificate from #{url}..."

        cert = Yast::Popup.Feedback(_("Downloading SSL Certificate"), url) do
          ::Registration::SslCertificate.download(url)
        end

        Yast::Popup.Feedback(_("Importing SSL Certificate"), cert.subject_name) do
          cert.import
        end
      end

      # update the internal Registration object after changing the registration URL
      def switch_registration(url = nil)
        @registration = ::Registration::Registration.new(url)
        # reset registration ui as it depends on registration
        @registration_ui = nil
        @registration
      end

      # returns the internal Registration object
      def registration
        if !@registration
          url = ::Registration::UrlHelpers.registration_url
          log.info "Updating registration using URL: #{url}"
          @registration = switch_registration(url)
        end

        @registration
      end

      # returns the internal RegistrationUI object
      def registration_ui
        @registration_ui ||= ::Registration::RegistrationUI.new(registration)
      end

      # update the registration (system, the base product, the installed extensions)
      def update_registration
        return false unless update_system_registration

        # register additional addons (e.g. originally not present in SLE11/SLE12)
        register_addons
      end

      # FIXME: share these methods with inst_scc.rb

      def register_base_product
        handle_product_service do
          options = ::Registration::Storage::InstallationOptions.instance
          options.email = @config.email
          options.reg_code = @config.reg_code

          registration_ui.register_system_and_base_product
        end
      end

      # register the addons specified in the profile
      def register_addons
        # set the option for installing the updates for addons
        options = ::Registration::Storage::InstallationOptions.instance
        options.install_updates = @config.install_updates

        ay_addons_handler = ::Registration::AutoyastAddons.new(@config.addons, registration)
        ay_addons_handler.select
        ay_addons_handler.register

        # select the new products to install
        ::Registration::SwMgmt.select_addon_products
      end

      # was the system already registered?
      # @return [Boolean] true if the system was alreay registered
      def old_system_registered?
        ::Registration::SwMgmt.copy_old_credentials(Yast::Installation.destdir)

        # update the registration using the old credentials
        ::File.exist?(SUSE::Connect::YaST::GLOBAL_CREDENTIALS_FILE)
      end

      # update the system registration
      # @return [Boolean] true on success
      def update_system_registration
        registration_ui.update_system
      end

      # @yieldreturn [Boolean, SUSE::Connect::Remote::Product] success flag and
      #   remote product pair
      # @return [Boolean] true on success
      def handle_product_service(&block)
        success, product_service = block.call
        return false unless success

        # keep updates enabled?
        return true if @config.install_updates || !product_service

        registration_ui.disable_update_repos(product_service)
      end

      # migrate registration if applicable or skip or report issue.
      def migrate_reg
        if old_system_registered?
          # act always like we have online only repo for registered system
          Y2Packager::MediumType.type = :online

          # drop all obsolete repositories and services (manual upgrade contains a dialog
          # where the old repositories are deleted, in AY we need to do it automatically here)
          # Note: the Update module creates automatically a backup which is restored
          # when upgrade is aborted or crashes.
          repo_cleanup

          ret = ::Registration::UI::OfflineMigrationWorkflow.new.main
          log.info "Migration result: #{ret}"
          ret == :next
        # Full medium we can upgrade without registration
        elsif Y2Packager::MediumType.offline?
          true
        else
          # Intentionally use blocking popup as it is fatal error that stops installation.
          Yast::Popup.Error(
            # TRANSLATORS: profile wants to do registration, but old system is not registered.
            _("The old system is not registered and the AutoYaST profile requires registration.\n" \
              "Either register the old system before running the upgrade or \n" \
              "remove the registration section from the AutoYaST profile \n" \
              "and use full medium.")
          )
          false
        end
      end
    end
  end
end