diff --git a/src/lib/y2caasp/cfa/mirror_conf.rb b/src/lib/y2caasp/cfa/mirror_conf.rb new file mode 100644 index 0000000..78487af --- /dev/null +++ b/src/lib/y2caasp/cfa/mirror_conf.rb @@ -0,0 +1,44 @@ +require "json" +require "yaml" + +module Y2Caasp + module CFA + # Represents a Salt Minion master configuration file. + # + # Does not use CFA, as the Augeas JSON module seems terribly complex for + # this use case. + class MirrorConf + attr_accessor :data + DOCKER_CONF_DIR = "/etc/docker/".freeze + PATH = "#{DOCKER_CONF_DIR}daemon.json".freeze + + def initialize + @data = if File.exist?(PATH) + JSON.load(File.read(PATH)) + else + { "iptables".freeze => false, + "log-level".freeze => "warn" } + end + end + + def mirror_url=(mirror_url) + registries_data = [{ "Mirrors" => [{ "URL" => mirror_url }], + "Prefix" => "https://registry.suse.com" }] + if @data["registrries"].nil? + @data["registries"] = registries_data + else + existing = @data["registries"][0] + existing.merge(registries_data[0]) + @data["registries"] = [existing] + end + end + + def save + inst_conf_dir = File.join(Yast::Installation.destdir, DOCKER_CONF_DIR) + inst_path = File.join(Yast::Installation.destdir, PATH) + ::FileUtils.mkdir_p(inst_conf_dir) unless File.exist?(inst_conf_dir) + File.open(inst_path, "w") { |file| file.write(@data.to_json) } + end + end + end +end diff --git a/src/lib/y2caasp/cfa/mirror_yaml_conf.rb b/src/lib/y2caasp/cfa/mirror_yaml_conf.rb new file mode 100644 index 0000000..0183f0f --- /dev/null +++ b/src/lib/y2caasp/cfa/mirror_yaml_conf.rb @@ -0,0 +1,54 @@ +require "json" +require "yaml" + +module Y2Caasp + module CFA + # Represents a Salt Minion master configuration file. + + # This class stores the mirror configuration in a yaml file for use in + # Velum. This file is required, as Velum needs to copy these mirrors into + # the database, so they can be distributed throughout the cluster, but the + # CaaSP team doesn't want to be bound to the docker specific configuration + # file + class MirrorConfYaml + DOCKER_CONF_DIR = "/etc/docker/".freeze + # The docker file will be read from the installed system, so it should have + # been configured before calling this configuration module. + DOCKER_CONF_PATH = File.join(Yast::Installation.destdir, + "#{DOCKER_CONF_DIR}daemon.json".freeze) + PATH = "#{DOCKER_CONF_DIR}mirrors.yaml".freeze + + # This class needs to be initialized with a valid docker config + # (MirrorConf.data) that contains a configured mirror + def initialize + @data = if File.exist?(DOCKER_CONF_PATH) + JSON.load(File.read(DOCKER_CONF_PATH)) + else + {} + end + end + + def as_yaml + return "" if @data["registries"].nil? || @data["registries"][0].nil? + yaml_data = { + "SUSE registry" => { + "url" => @data["registries"][0]["Prefix"], + "mirrors" => { + "YaST Mirror" => { + "url" => @data["registries"][0]["Mirrors"][0]["URL"] + } + } + } + } + yaml_data.to_yaml + end + + def save + inst_conf_dir = File.join(Yast::Installation.destdir, DOCKER_CONF_DIR) + inst_path = File.join(Yast::Installation.destdir, PATH) + ::FileUtils.mkdir_p(inst_conf_dir) unless File.exist?(inst_conf_dir) + File.open(inst_path, "w") { |file| file.write(as_yaml) } + end + end + end +end diff --git a/src/lib/y2caasp/clients/admin_role_dialog.rb b/src/lib/y2caasp/clients/admin_role_dialog.rb index 1f4866f..da1f164 100644 --- a/src/lib/y2caasp/clients/admin_role_dialog.rb +++ b/src/lib/y2caasp/clients/admin_role_dialog.rb @@ -22,6 +22,7 @@ require "yast" require "cwm/dialog" require "y2caasp/widgets/ntp_server" +require "y2caasp/widgets/container_registry_mirror" require "y2caasp/dhcp_ntp_servers" module Y2Caasp @@ -53,9 +54,15 @@ def contents return @content if @content @content = HSquash( - MinWidth(50, + MinWidth( + 50, # preselect the servers from the DHCP response - Y2Caasp::Widgets::NtpServer.new(ntp_servers)) + VBox( + Y2Caasp::Widgets::NtpServer.new(ntp_servers), + VSpacing(2), + Y2Caasp::Widgets::ContainerRegistryMirror.new("registry.suse.com") + ) + ) ) end diff --git a/src/lib/y2caasp/widgets/container_registry_mirror.rb b/src/lib/y2caasp/widgets/container_registry_mirror.rb new file mode 100644 index 0000000..0207e18 --- /dev/null +++ b/src/lib/y2caasp/widgets/container_registry_mirror.rb @@ -0,0 +1,91 @@ +# encoding: utf-8 + +# ------------------------------------------------------------------------------ +# Copyright (c) 2018 SUSE LLC +# +# +# 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. +# +# To contact SUSE about this file by physical or electronic mail, you may find +# current contact information at www.suse.com. +# ------------------------------------------------------------------------------ + +require "yast" +require "cwm/widget" +require "installation/system_role" + +module Y2Caasp + module Widgets + # This widget is responsible to validate and store the registry host. + # The host must be a valid IP of FQDN. + class ContainerRegistryMirror < CWM::InputField + attr_reader :default_host + + def initialize(default_host = "") + @default_host = default_host + textdomain "caasp" + end + + def label + _("Container registry mirror") + end + + def help + # TRANSLATORS: a help text for the controller node input field + _("

The Container Registry Mirror

") + + # TRANSLATORS: a help text for the controller node input field + _("

Enter the host, that runs the registry mirror." \ + "It must have all container-images required to run CaaS Platform available.

") + end + + # It stores the value of the input field if validates + # + # @see #validate + def store + # this is a role widget so a role must be selected before saving + raise("No role selected") unless role + role["registry_mirror"] = value + end + + # The input field is initialized with previous stored value + def init + self.value = if role && role["registry_mirror"] + role["registry_mirror"] + else + @default_host + end + end + + # It returns true if the value is a valid IP or a valid FQDN, if not it + # displays a popup error. + # + # @return [Boolean] true if valid IP or FQDN + def validate + return true if Yast::IP.Check(value) || Yast::Hostname.CheckFQ(value) + + Yast::Popup.Error( + # TRANSLATORS: error message for invalid administration node location + _("Not a valid location for the mirror, please enter a valid IP or Hostname") + ) + + false + end + + private + + # All other widgets have this + def role + ::Installation::SystemRole.current_role + end + end + end +end diff --git a/src/lib/y2system_role_handlers/dashboard_role_finish.rb b/src/lib/y2system_role_handlers/dashboard_role_finish.rb index 67c7107..4cd64b3 100644 --- a/src/lib/y2system_role_handlers/dashboard_role_finish.rb +++ b/src/lib/y2system_role_handlers/dashboard_role_finish.rb @@ -24,6 +24,8 @@ require "installation/system_role" require "installation/services" require "cfa/chrony_conf" +require "y2caasp/cfa/mirror_conf" +require "y2caasp/cfa/mirror_yaml_conf" module Y2SystemRoleHandlers # Implement finish handler for the "dashboard" role @@ -36,6 +38,7 @@ class DashboardRoleFinish def run run_activation_script setup_ntp + update_registry_conf end protected @@ -70,6 +73,17 @@ def update_chrony_conf chrony_conf.save end + def update_registry_conf + log.info "Updating registry mirror: " \ + "host #{role["registry_mirror"]}" + return unless role["registry_mirror"] + registry_conf = ::Y2Caasp::CFA::MirrorConf.new + registry_conf.mirror_url = role["registry_mirror"] + registry_conf.save + yaml_conf = ::Y2Caasp::CFA::MirrorConfYaml.new + yaml_conf.save + end + # Add the ntpd service to the list of services to enable def enable_service enabled = ::Installation::Services.enabled diff --git a/test/fixtures/mirror-conf.json b/test/fixtures/mirror-conf.json new file mode 100644 index 0000000..2811689 --- /dev/null +++ b/test/fixtures/mirror-conf.json @@ -0,0 +1,14 @@ +{ + "registries": [ + { + "Mirrors": [ + { + "URL": "https://registry.suse.com/" + } + ], + "Prefix": "https://none.none" + } + ], + "iptables":false, + "log-level": "warn" +} diff --git a/test/fixtures/registry-conf.yaml b/test/fixtures/registry-conf.yaml new file mode 100644 index 0000000..9100d51 --- /dev/null +++ b/test/fixtures/registry-conf.yaml @@ -0,0 +1,10 @@ +# This file defines whether to use container-feeder packaged images or a +# registry. +# +# If use_registry is set to false, the system should use container-feeder images +# and ignore the host and namespace. +# +# namespace: denotes the root namespace under which all images can be found. +use_registry: false +host: registry.suse.de +namespace: devel/casp/3.0/controllernode/images_container_base/sles12 diff --git a/test/lib/y2system_role_handlers/dashboard_role_finish_test.rb b/test/lib/y2system_role_handlers/dashboard_role_finish_test.rb index 2fb8466..2b9563e 100755 --- a/test/lib/y2system_role_handlers/dashboard_role_finish_test.rb +++ b/test/lib/y2system_role_handlers/dashboard_role_finish_test.rb @@ -8,15 +8,25 @@ let(:ntp_server) { "ntp.suse.de" } let(:ntp_servers) { [ntp_server] } + let(:registry_mirror) { "registry.suse.de" } before do stub_const("CFA::ChronyConf::PATH", FIXTURES_PATH.join("chrony.conf").to_s) allow(CFA::ChronyConf).to receive(:new).and_return(ntp_conf) + stub_const("::Y2Caasp::CFA::MirrorConf::DOCKER_CONF_DIR", Dir.mktmpdir) + stub_const("::Y2Caasp::CFA::MirrorConf::PATH", FIXTURES_PATH.join("mirror-conf.json").to_s) + allow(::Y2Caasp::CFA::MirrorConf).to receive(:new).and_return(mirror_conf) + stub_const("::Y2Caasp::CFA::MirrorConfYaml::DOCKER_CONF_DIR", Dir.mktmpdir) + stub_const("::Y2Caasp::CFA::MirrorConfYaml::DOCKER_CONF_PATH", + FIXTURES_PATH.join("mirror-conf.json").to_s) + stub_const("::Y2Caasp::CFA::MirrorConfYaml::PATH", FIXTURES_PATH.join("mirror-conf.yaml").to_s) + allow(::Y2Caasp::CFA::MirrorConfYaml).to receive(:new).and_return(mirror_yaml_conf) end let(:role) do ::Installation::SystemRole.new(id: "dashboard_role", order: "100").tap do |role| role["ntp_servers"] = ntp_servers + role["registry_mirror"] = registry_mirror end end @@ -28,9 +38,12 @@ describe "#run" do let(:ntp_conf) { CFA::ChronyConf.new } + let(:mirror_conf) { ::Y2Caasp::CFA::MirrorConf.new } + let(:mirror_yaml_conf) { ::Y2Caasp::CFA::MirrorConfYaml.new } before do allow(ntp_conf).to receive(:save) + allow(mirror_conf).to receive(:save) end it "runs the activation script" do @@ -65,5 +78,22 @@ handler.run end end + + context "when a registry mirror is specified" do + it "saves the registry mirror to the configurations" do + expect(mirror_conf).to receive(:save) + expect(mirror_yaml_conf).to receive(:save) + handler.run + end + end + + context "when neither namespace nor host is specified" do + let(:registry_mirror) { nil } + + it "it leaves the configuration untouched" do + expect(mirror_conf).not_to receive(:save) + handler.run + end + end end end