Skip to content

Commit

Permalink
Allow users to set a mirror for the default SUSE registry.
Browse files Browse the repository at this point in the history
This feature is only applicable for SLE15 based CaaSP installations, as those
will no longer use container-feeder, but download the docker images from a
registry instead.

By default this registry will be the official suse registry (registry.suse.com),
but the customer always has the possibility to mirror it, in which case he can
then set this mirror during installation.

This mirror will then be used a fallback, if the official registry can not be
reached.
  • Loading branch information
Florian Bergmann committed Dec 11, 2018
1 parent 598bdde commit 2bc26b6
Show file tree
Hide file tree
Showing 8 changed files with 266 additions and 2 deletions.
44 changes: 44 additions & 0 deletions src/lib/y2caasp/cfa/mirror_conf.rb
Original file line number Diff line number Diff line change
@@ -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
54 changes: 54 additions & 0 deletions src/lib/y2caasp/cfa/mirror_yaml_conf.rb
Original file line number Diff line number Diff line change
@@ -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
11 changes: 9 additions & 2 deletions src/lib/y2caasp/clients/admin_role_dialog.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
91 changes: 91 additions & 0 deletions src/lib/y2caasp/widgets/container_registry_mirror.rb
Original file line number Diff line number Diff line change
@@ -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
_("<h3>The Container Registry Mirror</h3>") +
# TRANSLATORS: a help text for the controller node input field
_("<p>Enter the host, that runs the registry mirror." \
"It must have all container-images required to run CaaS Platform available.</p>")
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
14 changes: 14 additions & 0 deletions src/lib/y2system_role_handlers/dashboard_role_finish.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -36,6 +38,7 @@ class DashboardRoleFinish
def run
run_activation_script
setup_ntp
update_registry_conf
end

protected
Expand Down Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions test/fixtures/mirror-conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"registries": [
{
"Mirrors": [
{
"URL": "https://registry.suse.com/"
}
],
"Prefix": "https://none.none"
}
],
"iptables":false,
"log-level": "warn"
}
10 changes: 10 additions & 0 deletions test/fixtures/registry-conf.yaml
Original file line number Diff line number Diff line change
@@ -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
30 changes: 30 additions & 0 deletions test/lib/y2system_role_handlers/dashboard_role_finish_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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

0 comments on commit 2bc26b6

Please sign in to comment.