Mini Shell
#
# Copyright (C) 2019 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
import os
import shutil
from pyanaconda.core import util
from pyanaconda.modules.common.errors.installation import NetworkInstallationError
from pyanaconda.modules.common.task import Task
from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.modules.network.nm_client import update_connection_values, \
commit_changes_with_autoconnection_blocked, nm_client_in_thread
from pyanaconda.modules.network.ifcfg import find_ifcfg_uuid_of_device
from pyanaconda.modules.network.utils import guard_by_system_configuration
log = get_module_logger(__name__)
import gi
gi.require_version("NM", "1.0")
from gi.repository import NM
class HostnameConfigurationTask(Task):
"""Hostname configuration task."""
HOSTNAME_CONF_FILE_PATH = "/etc/hostname"
def __init__(self, sysroot, hostname, overwrite):
"""Create a new task.
:param sysroot: a path to the root of installed system
:type sysroot: str
:param hostname: static hostname
:type hostname: str
:param overwrite: overwrite config files if they already exist
:type overwrite: bool
"""
super().__init__()
self._sysroot = sysroot
self._hostname = hostname
self._overwrite = overwrite
@property
def name(self):
return "Configure hostname"
def run(self):
_write_config_file(
self._sysroot, self.HOSTNAME_CONF_FILE_PATH,
"{}\n".format(self._hostname),
"Cannot write hostname configuration file",
self._overwrite
)
def _write_config_file(root, path, content, error_msg, overwrite):
"""Write content into config file on the target system.
:param root: path to the root of the target system
:type root: str
:param path: config file path in target system root
:type path: str
:param content: content to be written into config file
:type content: str
:param error_msg: error message in case of failure
:type error_msg: str
:param overwrite: overwrite existing configuration file
:type overwrite: bool
"""
fpath = os.path.normpath(root + path)
if os.path.isfile(fpath) and not overwrite:
return
try:
with open(fpath, "w") as fobj:
fobj.write(content)
except IOError as ioerr:
msg = "{}: {}".format(error_msg, ioerr.strerror)
raise NetworkInstallationError(msg)
class NetworkInstallationTask(Task):
"""Installation task for the network configuration."""
SYSCONF_NETWORK_FILE_PATH = "/etc/sysconfig/network"
ANACONDA_SYSCTL_FILE_PATH = "/etc/sysctl.d/anaconda.conf"
RESOLV_CONF_FILE_PATH = "/etc/resolv.conf"
NETWORK_SCRIPTS_DIR_PATH = "/etc/sysconfig/network-scripts"
PREFIXDEVNAME_DIR_PATH = "/etc/systemd/network"
PREFIXDEVNAME_CONFIG_FILE_PREFIX = "71-net-ifnames-prefix-"
DEVICE_CONFIG_FILE_PREFIXES = ("ifcfg-", "keys-", "route-")
DHCLIENT_FILE_TEMPLATE = "/etc/dhcp/dhclient-{}.conf"
SYSTEMD_NETWORK_CONFIG_DIR = "/etc/systemd/network"
INTERFACE_RENAME_FILE_TEMPLATE = "10-anaconda-ifname-{}.link"
INTERFACE_RENAME_FILE_CONTENT_TEMPLATE = """
# Generated by Anaconda based on ifname= installer boot option.
[Match]
MACAddress={}
[Link]
Name={}
""".strip()
def __init__(self, sysroot, disable_ipv6, overwrite,
network_ifaces, ifname_option_values,
configure_persistent_device_names):
"""Create a new task.
:param sysroot: a path to the root of installed system
:type sysroot: str
:param disable_ipv6: disable ipv6 on target system
:type disable_ipv6: bool
:param overwrite: overwrite config files if they already exist
:type overwrite: bool
:param network_ifaces: list of network interfaces for dhcp configuration
:type network_ifaces: list(str)
:param ifname_option_values: list of ifname boot option values
:type ifname_option_values: list(str)
:param configure_persistent_device_names: configure persistent network device
names on target system
:type configure_persistent_device_names: bool
"""
super().__init__()
self._sysroot = sysroot
self._disable_ipv6 = disable_ipv6
self._overwrite = overwrite
self._network_ifaces = network_ifaces
self._ifname_option_values = ifname_option_values
self._configure_persistent_device_names = configure_persistent_device_names
@property
def name(self):
return "Configure network"
def run(self):
self._write_sysconfig_network(self._sysroot, self._overwrite)
self._write_interface_rename_config(self._sysroot, self._ifname_option_values,
self._overwrite)
if self._disable_ipv6:
self._disable_ipv6_on_system(self._sysroot)
self._copy_device_config_files(self._sysroot)
self._copy_dhclient_config_files(self._sysroot, self._network_ifaces)
self._copy_resolv_conf(self._sysroot, self._overwrite)
if self._configure_persistent_device_names:
self._copy_prefixdevname_files(self._sysroot)
def _write_sysconfig_network(self, root, overwrite):
"""Write empty /etc/sysconfig/network target system configuration file.
:param root: path to the root of the target system
:type root: str
:param overwrite: overwrite existing configuration file
:type overwrite: bool
"""
return _write_config_file(root, self.SYSCONF_NETWORK_FILE_PATH,
"# Created by anaconda\n",
"Cannot write {} configuration file".format(
self.SYSCONF_NETWORK_FILE_PATH),
overwrite)
def _write_interface_rename_config(self, root, ifname_option_values, overwrite):
"""Write systemd configuration .link file for interface renaming.
:param root: path to the root of the target system
:type root: str
:param ifname_option_values: list of ifname boot option values
:type ifname_option_values: list(str)
:param overwrite: overwrite existing configuration file
:type overwrite: bool
"""
if ifname_option_values:
target_system_dir = util.join_paths(root, self.SYSTEMD_NETWORK_CONFIG_DIR)
util.mkdirChain(target_system_dir)
for ifname_value in ifname_option_values:
iface, mac = ifname_value.split(":", 1)
content = self.INTERFACE_RENAME_FILE_CONTENT_TEMPLATE.format(mac, iface)
config_file = self.INTERFACE_RENAME_FILE_TEMPLATE.format(iface)
config_file_path = util.join_paths(self.SYSTEMD_NETWORK_CONFIG_DIR, config_file)
_write_config_file(
root,
config_file_path,
content,
"Cannot write {} configuration file for ifname={} option.".format(
config_file_path, ifname_value),
overwrite
)
def _disable_ipv6_on_system(self, root):
"""Disable ipv6 on target system.
:param root: path to the root of the target system
:type root: str
"""
fpath = os.path.normpath(root + self.ANACONDA_SYSCTL_FILE_PATH)
try:
with open(fpath, "a") as f:
f.write("# Anaconda disabling ipv6 (noipv6 option)\n")
f.write("net.ipv6.conf.all.disable_ipv6=1\n")
f.write("net.ipv6.conf.default.disable_ipv6=1\n")
except IOError as ioerr:
msg = "Cannot disable ipv6 on the system: {}".format(ioerr.strerror)
raise NetworkInstallationError(msg)
def _copy_resolv_conf(self, root, overwrite):
"""Copy resolf.conf file to target system.
:param root: path to the root of the target system
:type root: str
:param overwrite: overwrite existing configuration file
:type overwrite: bool
"""
self._copy_file_to_root(root, self.RESOLV_CONF_FILE_PATH)
def _copy_file_to_root(self, root, config_file, overwrite=False):
"""Copy the file to target system.
:param root: path to the root of the target system
:type root: str
:param config_file: path of the file
:type config_file: str
:param overwrite: overwrite existing configuration file
:type overwrite: bool
"""
if not os.path.isfile(config_file):
return
fpath = os.path.normpath(root + config_file)
if os.path.isfile(fpath) and not overwrite:
return
if not os.path.isdir(os.path.dirname(fpath)):
util.mkdirChain(os.path.dirname(fpath))
shutil.copy(config_file, fpath)
def _copy_device_config_files(self, root):
"""Copy network device config (ifcfg) files to target system.
:param root: path to the root of the target system
:type root: str
"""
config_files = os.listdir(self.NETWORK_SCRIPTS_DIR_PATH)
for config_file in config_files:
if config_file.startswith(self.DEVICE_CONFIG_FILE_PREFIXES):
config_file_path = os.path.join(self.NETWORK_SCRIPTS_DIR_PATH,
config_file)
self._copy_file_to_root(root, config_file_path)
def _copy_dhclient_config_files(self, root, network_ifaces):
"""Copy dhclient configuration files to target system.
:param root: path to the root of the target system
:type root: str
:param network_ifaces: ifaces whose config files should be copied
:type network_ifaces: list(str)
"""
for device_name in network_ifaces:
dhclient_file = self.DHCLIENT_FILE_TEMPLATE.format(device_name)
self._copy_file_to_root(root, dhclient_file)
def _copy_prefixdevname_files(self, root):
"""Copy prefixdevname persistent configuration to target system.
:param root: path to the root of the target system
:type root: str
"""
config_files = os.listdir(self.PREFIXDEVNAME_DIR_PATH)
for config_file in config_files:
if config_file.startswith(self.PREFIXDEVNAME_CONFIG_FILE_PREFIX):
config_file_path = os.path.join(self.PREFIXDEVNAME_DIR_PATH,
config_file)
self._copy_file_to_root(root, config_file_path)
class ConfigureActivationOnBootTask(Task):
"""Task for configuration of automatic activation of devices on boot"""
def __init__(self, onboot_ifaces):
"""Create a new task.
:param onboot_ifaces: interfaces that should be autoactivated on boot
:type onboot_ifaces: list(str)
"""
super().__init__()
self._onboot_ifaces = onboot_ifaces
@property
def name(self):
return "Configure automatic activation on boot."
@guard_by_system_configuration(return_value=None)
def run(self):
with nm_client_in_thread() as nm_client:
return self._run(nm_client)
def _run(self, nm_client):
if not nm_client:
log.debug("%s: No NetworkManager available.", self.name)
return None
for iface in self._onboot_ifaces:
con_uuid = find_ifcfg_uuid_of_device(nm_client, iface)
if con_uuid:
con = nm_client.get_connection_by_uuid(con_uuid)
update_connection_values(
con,
[("connection", NM.SETTING_CONNECTION_AUTOCONNECT, True)]
)
commit_changes_with_autoconnection_blocked(con, nm_client)
else:
log.warning("Configure ONBOOT: can't find ifcfg for %s", iface)