summaryrefslogtreecommitdiff
path: root/cloudinit/distros
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit/distros')
-rwxr-xr-xcloudinit/distros/__init__.py493
-rw-r--r--cloudinit/distros/almalinux.py10
-rw-r--r--cloudinit/distros/alpine.py67
-rw-r--r--cloudinit/distros/amazon.py1
-rw-r--r--cloudinit/distros/arch.py177
-rw-r--r--cloudinit/distros/bsd.py69
-rw-r--r--cloudinit/distros/bsd_utils.py18
-rw-r--r--cloudinit/distros/centos.py1
-rw-r--r--cloudinit/distros/cloudlinux.py10
-rw-r--r--cloudinit/distros/debian.py274
-rw-r--r--cloudinit/distros/dragonflybsd.py12
-rw-r--r--cloudinit/distros/eurolinux.py10
-rw-r--r--cloudinit/distros/fedora.py1
-rw-r--r--cloudinit/distros/freebsd.py105
-rw-r--r--cloudinit/distros/gentoo.py183
-rw-r--r--cloudinit/distros/miraclelinux.py10
-rw-r--r--cloudinit/distros/net_util.py68
-rw-r--r--cloudinit/distros/netbsd.py85
-rw-r--r--cloudinit/distros/networking.py16
-rw-r--r--cloudinit/distros/openEuler.py10
-rw-r--r--cloudinit/distros/openbsd.py20
-rw-r--r--cloudinit/distros/opensuse.py137
-rw-r--r--cloudinit/distros/parsers/__init__.py3
-rw-r--r--cloudinit/distros/parsers/hostname.py24
-rw-r--r--cloudinit/distros/parsers/hosts.py24
-rw-r--r--cloudinit/distros/parsers/networkmanager_conf.py6
-rw-r--r--cloudinit/distros/parsers/resolv_conf.py73
-rw-r--r--cloudinit/distros/parsers/sys_conf.py38
-rw-r--r--cloudinit/distros/photon.py150
-rw-r--r--cloudinit/distros/rhel.py103
-rw-r--r--cloudinit/distros/rhel_util.py4
-rw-r--r--cloudinit/distros/rocky.py10
-rw-r--r--cloudinit/distros/sles.py1
-rw-r--r--cloudinit/distros/tests/__init__.py0
-rw-r--r--cloudinit/distros/tests/test_init.py156
-rw-r--r--cloudinit/distros/tests/test_networking.py223
-rw-r--r--cloudinit/distros/ubuntu.py33
-rwxr-xr-xcloudinit/distros/ug_util.py290
-rw-r--r--cloudinit/distros/virtuozzo.py10
39 files changed, 1520 insertions, 1405 deletions
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index 1e118472..76acd6a3 100755
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -16,48 +16,53 @@ import stat
import string
import urllib.parse
from io import StringIO
+from typing import Any, Mapping, Type
from cloudinit import importer
from cloudinit import log as logging
-from cloudinit import net
-from cloudinit.net import eni
-from cloudinit.net import network_state
-from cloudinit.net import renderers
-from cloudinit import persistence
-from cloudinit import ssh_util
-from cloudinit import type_utils
-from cloudinit import subp
-from cloudinit import util
-
-from cloudinit.features import \
- ALLOW_EC2_MIRRORS_ON_NON_AWS_INSTANCE_TYPES
-
+from cloudinit import net, persistence, ssh_util, subp, type_utils, util
from cloudinit.distros.parsers import hosts
-from .networking import LinuxNetworking
+from cloudinit.features import ALLOW_EC2_MIRRORS_ON_NON_AWS_INSTANCE_TYPES
+from cloudinit.net import activators, eni, network_state, renderers
+from cloudinit.net.network_state import parse_net_config_data
+from .networking import LinuxNetworking, Networking
# Used when a cloud-config module can be run on all cloud-init distibutions.
# The value 'all' is surfaced in module documentation for distro support.
-ALL_DISTROS = 'all'
+ALL_DISTROS = "all"
OSFAMILIES = {
- 'alpine': ['alpine'],
- 'arch': ['arch'],
- 'debian': ['debian', 'ubuntu'],
- 'freebsd': ['freebsd'],
- 'gentoo': ['gentoo'],
- 'redhat': ['amazon', 'centos', 'fedora', 'rhel'],
- 'suse': ['opensuse', 'sles'],
+ "alpine": ["alpine"],
+ "arch": ["arch"],
+ "debian": ["debian", "ubuntu"],
+ "freebsd": ["freebsd"],
+ "gentoo": ["gentoo"],
+ "redhat": [
+ "almalinux",
+ "amazon",
+ "centos",
+ "cloudlinux",
+ "eurolinux",
+ "fedora",
+ "miraclelinux",
+ "openEuler",
+ "photon",
+ "rhel",
+ "rocky",
+ "virtuozzo",
+ ],
+ "suse": ["opensuse", "sles"],
}
LOG = logging.getLogger(__name__)
# This is a best guess regex, based on current EC2 AZs on 2017-12-11.
# It could break when Amazon adds new regions and new AZs.
-_EC2_AZ_RE = re.compile('^[a-z][a-z]-(?:[a-z]+-)+[0-9][a-z]$')
+_EC2_AZ_RE = re.compile("^[a-z][a-z]-(?:[a-z]+-)+[0-9][a-z]$")
# Default NTP Client Configurations
-PREFERRED_NTP_CLIENTS = ['chrony', 'systemd-timesyncd', 'ntp', 'ntpdate']
+PREFERRED_NTP_CLIENTS = ["chrony", "systemd-timesyncd", "ntp", "ntpdate"]
# Letters/Digits/Hyphen characters, for use in domain name validation
LDH_ASCII_CHARS = string.ascii_letters + string.digits + "-"
@@ -70,21 +75,23 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
ci_sudoers_fn = "/etc/sudoers.d/90-cloud-init-users"
hostname_conf_fn = "/etc/hostname"
tz_zone_dir = "/usr/share/zoneinfo"
- init_cmd = ['service'] # systemctl, service etc
- renderer_configs = {}
+ init_cmd = ["service"] # systemctl, service etc
+ renderer_configs: Mapping[str, Mapping[str, Any]] = {}
_preferred_ntp_clients = None
- networking_cls = LinuxNetworking
+ networking_cls: Type[Networking] = LinuxNetworking
# This is used by self.shutdown_command(), and can be overridden in
# subclasses
- shutdown_options_map = {'halt': '-H', 'poweroff': '-P', 'reboot': '-r'}
+ shutdown_options_map = {"halt": "-H", "poweroff": "-P", "reboot": "-r"}
_ci_pkl_version = 1
+ prefer_fqdn = False
+ resolve_conf_fn = "/etc/resolv.conf"
def __init__(self, name, cfg, paths):
self._paths = paths
self._cfg = cfg
self.name = name
- self.networking = self.networking_cls()
+ self.networking: Networking = self.networking_cls()
def _unpickle(self, ci_pkl_version: int) -> None:
"""Perform deserialization fixes for Distro."""
@@ -103,34 +110,38 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
raise NotImplementedError()
def _write_network(self, settings):
- raise RuntimeError(
+ """Deprecated. Remove if/when arch and gentoo support renderers."""
+ raise NotImplementedError(
"Legacy function '_write_network' was called in distro '%s'.\n"
- "_write_network_config needs implementation.\n" % self.name)
-
- def _write_network_config(self, settings):
- raise NotImplementedError()
+ "_write_network_config needs implementation.\n" % self.name
+ )
- def _supported_write_network_config(self, network_config):
+ def _write_network_state(self, network_state):
priority = util.get_cfg_by_path(
- self._cfg, ('network', 'renderers'), None)
+ self._cfg, ("network", "renderers"), None
+ )
name, render_cls = renderers.select(priority=priority)
- LOG.debug("Selected renderer '%s' from priority list: %s",
- name, priority)
+ LOG.debug(
+ "Selected renderer '%s' from priority list: %s", name, priority
+ )
renderer = render_cls(config=self.renderer_configs.get(name))
- renderer.render_network_config(network_config)
- return []
+ renderer.render_network_state(network_state)
def _find_tz_file(self, tz):
tz_file = os.path.join(self.tz_zone_dir, str(tz))
if not os.path.isfile(tz_file):
- raise IOError(("Invalid timezone %s,"
- " no file found at %s") % (tz, tz_file))
+ raise IOError(
+ "Invalid timezone %s, no file found at %s" % (tz, tz_file)
+ )
return tz_file
def get_option(self, opt_name, default=None):
return self._cfg.get(opt_name, default)
+ def set_option(self, opt_name, value=None):
+ self._cfg[opt_name] = value
+
def set_hostname(self, hostname, fqdn=None):
writeable_hostname = self._select_hostname(hostname, fqdn)
self._write_hostname(writeable_hostname, self.hostname_conf_fn)
@@ -141,7 +152,7 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
return uses_systemd()
@abc.abstractmethod
- def package_command(self, cmd, args=None, pkgs=None):
+ def package_command(self, command, args=None, pkgs=None):
raise NotImplementedError()
@abc.abstractmethod
@@ -164,10 +175,12 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
# This resolves the package_mirrors config option
# down to a single dict of {mirror_name: mirror_url}
arch_info = self._get_arch_package_mirror_info(arch)
- return _get_package_mirror_info(data_source=data_source,
- mirror_info=arch_info)
+ return _get_package_mirror_info(
+ data_source=data_source, mirror_info=arch_info
+ )
def apply_network(self, settings, bring_up=True):
+ """Deprecated. Remove if/when arch and gentoo support renderers."""
# this applies network where 'settings' is interfaces(5) style
# it is obsolete compared to apply_network_config
# Write it out
@@ -182,36 +195,62 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
return False
def _apply_network_from_network_config(self, netconfig, bring_up=True):
+ """Deprecated. Remove if/when arch and gentoo support renderers."""
distro = self.__class__
- LOG.warning("apply_network_config is not currently implemented "
- "for distribution '%s'. Attempting to use apply_network",
- distro)
- header = '\n'.join([
- "# Converted from network_config for distro %s" % distro,
- "# Implementation of _write_network_config is needed."
- ])
+ LOG.warning(
+ "apply_network_config is not currently implemented "
+ "for distribution '%s'. Attempting to use apply_network",
+ distro,
+ )
+ header = "\n".join(
+ [
+ "# Converted from network_config for distro %s" % distro,
+ "# Implementation of _write_network_config is needed.",
+ ]
+ )
ns = network_state.parse_net_config_data(netconfig)
contents = eni.network_state_to_eni(
- ns, header=header, render_hwaddress=True)
+ ns, header=header, render_hwaddress=True
+ )
return self.apply_network(contents, bring_up=bring_up)
def generate_fallback_config(self):
return net.generate_fallback_config()
- def apply_network_config(self, netconfig, bring_up=False):
- # apply network config netconfig
+ def apply_network_config(self, netconfig, bring_up=False) -> bool:
+ """Apply the network config.
+
+ If bring_up is True, attempt to bring up the passed in devices. If
+ devices is None, attempt to bring up devices returned by
+ _write_network_config.
+
+ Returns True if any devices failed to come up, otherwise False.
+ """
# This method is preferred to apply_network which only takes
# a much less complete network config format (interfaces(5)).
+ network_state = parse_net_config_data(netconfig)
try:
- dev_names = self._write_network_config(netconfig)
+ self._write_network_state(network_state)
except NotImplementedError:
# backwards compat until all distros have apply_network_config
return self._apply_network_from_network_config(
- netconfig, bring_up=bring_up)
+ netconfig, bring_up=bring_up
+ )
# Now try to bring them up
if bring_up:
- return self._bring_up_interfaces(dev_names)
+ LOG.debug("Bringing up newly configured network interfaces")
+ try:
+ network_activator = activators.select_activator()
+ except activators.NoActivatorException:
+ LOG.warning(
+ "No network activator found, not bringing up "
+ "network interfaces"
+ )
+ return True
+ network_activator.bring_up_all_interfaces(network_state)
+ else:
+ LOG.debug("Not bringing up newly configured network interfaces")
return False
def apply_network_config_names(self, netconfig):
@@ -248,17 +287,28 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
# temporarily (until reboot so it should
# not be depended on). Use the write
# hostname functions for 'permanent' adjustments.
- LOG.debug("Non-persistently setting the system hostname to %s",
- hostname)
+ LOG.debug(
+ "Non-persistently setting the system hostname to %s", hostname
+ )
try:
- subp.subp(['hostname', hostname])
+ subp.subp(["hostname", hostname])
except subp.ProcessExecutionError:
- util.logexc(LOG, "Failed to non-persistently adjust the system "
- "hostname to %s", hostname)
+ util.logexc(
+ LOG,
+ "Failed to non-persistently adjust the system hostname to %s",
+ hostname,
+ )
def _select_hostname(self, hostname, fqdn):
# Prefer the short hostname over the long
# fully qualified domain name
+ if (
+ util.get_cfg_option_bool(
+ self._cfg, "prefer_fqdn_over_hostname", self.prefer_fqdn
+ )
+ and fqdn
+ ):
+ return fqdn
if not hostname:
return fqdn
return hostname
@@ -300,32 +350,39 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
# If the system hostname is different than the previous
# one or the desired one lets update it as well
- if ((not sys_hostname) or (sys_hostname == prev_hostname and
- sys_hostname != hostname)):
+ if (not sys_hostname) or (
+ sys_hostname == prev_hostname and sys_hostname != hostname
+ ):
update_files.append(sys_fn)
# If something else has changed the hostname after we set it
# initially, we should not overwrite those changes (we should
# only be setting the hostname once per instance)
- if (sys_hostname and prev_hostname and
- sys_hostname != prev_hostname):
- LOG.info("%s differs from %s, assuming user maintained hostname.",
- prev_hostname_fn, sys_fn)
+ if sys_hostname and prev_hostname and sys_hostname != prev_hostname:
+ LOG.info(
+ "%s differs from %s, assuming user maintained hostname.",
+ prev_hostname_fn,
+ sys_fn,
+ )
return
# Remove duplicates (incase the previous config filename)
# is the same as the system config filename, don't bother
# doing it twice
update_files = set([f for f in update_files if f])
- LOG.debug("Attempting to update hostname to %s in %s files",
- hostname, len(update_files))
+ LOG.debug(
+ "Attempting to update hostname to %s in %s files",
+ hostname,
+ len(update_files),
+ )
for fn in update_files:
try:
self._write_hostname(hostname, fn)
except IOError:
- util.logexc(LOG, "Failed to write hostname %s to %s", hostname,
- fn)
+ util.logexc(
+ LOG, "Failed to write hostname %s to %s", hostname, fn
+ )
# If the system hostname file name was provided set the
# non-fqdn as the transient hostname.
@@ -333,11 +390,11 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
self._apply_hostname(applying_hostname)
def update_etc_hosts(self, hostname, fqdn):
- header = ''
+ header = ""
if os.path.exists(self.hosts_fn):
eh = hosts.HostsConf(util.load_file(self.hosts_fn))
else:
- eh = hosts.HostsConf('')
+ eh = hosts.HostsConf("")
header = util.make_header(base="added")
local_ip = self._get_localhost_ip()
prev_info = eh.get_entry(local_ip)
@@ -384,20 +441,11 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
return self._preferred_ntp_clients
def _bring_up_interface(self, device_name):
- cmd = ['ifup', device_name]
- LOG.debug("Attempting to run bring up interface %s using command %s",
- device_name, cmd)
- try:
- (_out, err) = subp.subp(cmd)
- if len(err):
- LOG.warning("Running %s resulted in stderr output: %s",
- cmd, err)
- return True
- except subp.ProcessExecutionError:
- util.logexc(LOG, "Running interface command %s failed", cmd)
- return False
+ """Deprecated. Remove if/when arch and gentoo support renderers."""
+ raise NotImplementedError
def _bring_up_interfaces(self, device_names):
+ """Deprecated. Remove if/when arch and gentoo support renderers."""
am_failed = 0
for d in device_names:
if not self._bring_up_interface(d):
@@ -407,7 +455,7 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
return False
def get_default_user(self):
- return self.get_option('default_user')
+ return self.get_option("default_user")
def add_user(self, name, **kwargs):
"""
@@ -423,43 +471,43 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
LOG.info("User %s already exists, skipping.", name)
return
- if 'create_groups' in kwargs:
- create_groups = kwargs.pop('create_groups')
+ if "create_groups" in kwargs:
+ create_groups = kwargs.pop("create_groups")
else:
create_groups = True
- useradd_cmd = ['useradd', name]
- log_useradd_cmd = ['useradd', name]
+ useradd_cmd = ["useradd", name]
+ log_useradd_cmd = ["useradd", name]
if util.system_is_snappy():
- useradd_cmd.append('--extrausers')
- log_useradd_cmd.append('--extrausers')
+ useradd_cmd.append("--extrausers")
+ log_useradd_cmd.append("--extrausers")
# Since we are creating users, we want to carefully validate the
# inputs. If something goes wrong, we can end up with a system
# that nobody can login to.
useradd_opts = {
- "gecos": '--comment',
- "homedir": '--home',
- "primary_group": '--gid',
- "uid": '--uid',
- "groups": '--groups',
- "passwd": '--password',
- "shell": '--shell',
- "expiredate": '--expiredate',
- "inactive": '--inactive',
- "selinux_user": '--selinux-user',
+ "gecos": "--comment",
+ "homedir": "--home",
+ "primary_group": "--gid",
+ "uid": "--uid",
+ "groups": "--groups",
+ "passwd": "--password",
+ "shell": "--shell",
+ "expiredate": "--expiredate",
+ "inactive": "--inactive",
+ "selinux_user": "--selinux-user",
}
useradd_flags = {
- "no_user_group": '--no-user-group',
- "system": '--system',
- "no_log_init": '--no-log-init',
+ "no_user_group": "--no-user-group",
+ "system": "--system",
+ "no_log_init": "--no-log-init",
}
- redact_opts = ['passwd']
+ redact_opts = ["passwd"]
# support kwargs having groups=[list] or groups="g1,g2"
- groups = kwargs.get('groups')
+ groups = kwargs.get("groups")
if groups:
if isinstance(groups, str):
groups = groups.split(",")
@@ -470,9 +518,9 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
# kwargs.items loop below wants a comma delimeted string
# that can go right through to the command.
- kwargs['groups'] = ",".join(groups)
+ kwargs["groups"] = ",".join(groups)
- primary_group = kwargs.get('primary_group')
+ primary_group = kwargs.get("primary_group")
if primary_group:
groups.append(primary_group)
@@ -490,7 +538,7 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
# Redact certain fields from the logs
if key in redact_opts:
- log_useradd_cmd.extend([useradd_opts[key], 'REDACTED'])
+ log_useradd_cmd.extend([useradd_opts[key], "REDACTED"])
else:
log_useradd_cmd.extend([useradd_opts[key], val])
@@ -500,12 +548,12 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
# Don't create the home directory if directed so or if the user is a
# system user
- if kwargs.get('no_create_home') or kwargs.get('system'):
- useradd_cmd.append('-M')
- log_useradd_cmd.append('-M')
+ if kwargs.get("no_create_home") or kwargs.get("system"):
+ useradd_cmd.append("-M")
+ log_useradd_cmd.append("-M")
else:
- useradd_cmd.append('-m')
- log_useradd_cmd.append('-m')
+ useradd_cmd.append("-m")
+ log_useradd_cmd.append("-m")
# Run the command
LOG.debug("Adding user %s", name)
@@ -520,8 +568,8 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
Add a snappy user to the system using snappy tools
"""
- snapuser = kwargs.get('snapuser')
- known = kwargs.get('known', False)
+ snapuser = kwargs.get("snapuser")
+ known = kwargs.get("known", False)
create_user_cmd = ["snap", "create-user", "--sudoer", "--json"]
if known:
create_user_cmd.append("--known")
@@ -530,11 +578,12 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
# Run the command
LOG.debug("Adding snap user %s", name)
try:
- (out, err) = subp.subp(create_user_cmd, logstring=create_user_cmd,
- capture=True)
+ (out, err) = subp.subp(
+ create_user_cmd, logstring=create_user_cmd, capture=True
+ )
LOG.debug("snap create-user returned: %s:%s", out, err)
jobj = util.load_json(out)
- username = jobj.get('username', None)
+ username = jobj.get("username", None)
except Exception as e:
util.logexc(LOG, "Failed to create snap user %s", name)
raise e
@@ -562,60 +611,66 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
"""
# Add a snap user, if requested
- if 'snapuser' in kwargs:
+ if "snapuser" in kwargs:
return self.add_snap_user(name, **kwargs)
# Add the user
self.add_user(name, **kwargs)
# Set password if plain-text password provided and non-empty
- if 'plain_text_passwd' in kwargs and kwargs['plain_text_passwd']:
- self.set_passwd(name, kwargs['plain_text_passwd'])
+ if "plain_text_passwd" in kwargs and kwargs["plain_text_passwd"]:
+ self.set_passwd(name, kwargs["plain_text_passwd"])
# Set password if hashed password is provided and non-empty
- if 'hashed_passwd' in kwargs and kwargs['hashed_passwd']:
- self.set_passwd(name, kwargs['hashed_passwd'], hashed=True)
+ if "hashed_passwd" in kwargs and kwargs["hashed_passwd"]:
+ self.set_passwd(name, kwargs["hashed_passwd"], hashed=True)
# Default locking down the account. 'lock_passwd' defaults to True.
# lock account unless lock_password is False.
- if kwargs.get('lock_passwd', True):
+ if kwargs.get("lock_passwd", True):
self.lock_passwd(name)
# Configure sudo access
- if 'sudo' in kwargs and kwargs['sudo'] is not False:
- self.write_sudo_rules(name, kwargs['sudo'])
+ if "sudo" in kwargs and kwargs["sudo"] is not False:
+ self.write_sudo_rules(name, kwargs["sudo"])
# Import SSH keys
- if 'ssh_authorized_keys' in kwargs:
+ if "ssh_authorized_keys" in kwargs:
# Try to handle this in a smart manner.
- keys = kwargs['ssh_authorized_keys']
+ keys = kwargs["ssh_authorized_keys"]
if isinstance(keys, str):
keys = [keys]
elif isinstance(keys, dict):
keys = list(keys.values())
if keys is not None:
if not isinstance(keys, (tuple, list, set)):
- LOG.warning("Invalid type '%s' detected for"
- " 'ssh_authorized_keys', expected list,"
- " string, dict, or set.", type(keys))
+ LOG.warning(
+ "Invalid type '%s' detected for"
+ " 'ssh_authorized_keys', expected list,"
+ " string, dict, or set.",
+ type(keys),
+ )
keys = []
else:
keys = set(keys) or []
ssh_util.setup_user_keys(set(keys), name)
- if 'ssh_redirect_user' in kwargs:
- cloud_keys = kwargs.get('cloud_public_ssh_keys', [])
+ if "ssh_redirect_user" in kwargs:
+ cloud_keys = kwargs.get("cloud_public_ssh_keys", [])
if not cloud_keys:
LOG.warning(
- 'Unable to disable SSH logins for %s given'
- ' ssh_redirect_user: %s. No cloud public-keys present.',
- name, kwargs['ssh_redirect_user'])
+ "Unable to disable SSH logins for %s given"
+ " ssh_redirect_user: %s. No cloud public-keys present.",
+ name,
+ kwargs["ssh_redirect_user"],
+ )
else:
- redirect_user = kwargs['ssh_redirect_user']
+ redirect_user = kwargs["ssh_redirect_user"]
disable_option = ssh_util.DISABLE_USER_OPTS
- disable_option = disable_option.replace('$USER', redirect_user)
- disable_option = disable_option.replace('$DISABLE_USER', name)
+ disable_option = disable_option.replace("$USER", redirect_user)
+ disable_option = disable_option.replace("$DISABLE_USER", name)
ssh_util.setup_user_keys(
- set(cloud_keys), name, options=disable_option)
+ set(cloud_keys), name, options=disable_option
+ )
return True
def lock_passwd(self, name):
@@ -623,36 +678,36 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
Lock the password of a user, i.e., disable password logins
"""
# passwd must use short '-l' due to SLES11 lacking long form '--lock'
- lock_tools = (['passwd', '-l', name], ['usermod', '--lock', name])
+ lock_tools = (["passwd", "-l", name], ["usermod", "--lock", name])
try:
cmd = next(tool for tool in lock_tools if subp.which(tool[0]))
except StopIteration as e:
- raise RuntimeError((
+ raise RuntimeError(
"Unable to lock user account '%s'. No tools available. "
- " Tried: %s.") % (name, [c[0] for c in lock_tools])
+ " Tried: %s." % (name, [c[0] for c in lock_tools])
) from e
try:
subp.subp(cmd)
except Exception as e:
- util.logexc(LOG, 'Failed to disable password for user %s', name)
+ util.logexc(LOG, "Failed to disable password for user %s", name)
raise e
def expire_passwd(self, user):
try:
- subp.subp(['passwd', '--expire', user])
+ subp.subp(["passwd", "--expire", user])
except Exception as e:
util.logexc(LOG, "Failed to set 'expire' for %s", user)
raise e
def set_passwd(self, user, passwd, hashed=False):
- pass_string = '%s:%s' % (user, passwd)
- cmd = ['chpasswd']
+ pass_string = "%s:%s" % (user, passwd)
+ cmd = ["chpasswd"]
if hashed:
# Need to use the short option name '-e' instead of '--encrypted'
# (which would be more descriptive) since SLES 11 doesn't know
# about long names.
- cmd.append('-e')
+ cmd.append("-e")
try:
subp.subp(cmd, pass_string, logstring="chpasswd for %s" % user)
@@ -662,10 +717,10 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
return True
- def ensure_sudo_dir(self, path, sudo_base='/etc/sudoers'):
+ def ensure_sudo_dir(self, path, sudo_base="/etc/sudoers"):
# Ensure the dir is included and that
# it actually exists as a directory
- sudoers_contents = ''
+ sudoers_contents = ""
base_exists = False
if os.path.exists(sudo_base):
sudoers_contents = util.load_file(sudo_base)
@@ -673,7 +728,7 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
found_include = False
for line in sudoers_contents.splitlines():
line = line.strip()
- include_match = re.search(r"^#includedir\s+(.*)$", line)
+ include_match = re.search(r"^[#|@]includedir\s+(.*)$", line)
if not include_match:
continue
included_dir = include_match.group(1).strip()
@@ -686,15 +741,23 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
if not found_include:
try:
if not base_exists:
- lines = [('# See sudoers(5) for more information'
- ' on "#include" directives:'), '',
- util.make_header(base="added"),
- "#includedir %s" % (path), '']
+ lines = [
+ "# See sudoers(5) for more information"
+ ' on "#include" directives:',
+ "",
+ util.make_header(base="added"),
+ "#includedir %s" % (path),
+ "",
+ ]
sudoers_contents = "\n".join(lines)
util.write_file(sudo_base, sudoers_contents, 0o440)
else:
- lines = ['', util.make_header(base="added"),
- "#includedir %s" % (path), '']
+ lines = [
+ "",
+ util.make_header(base="added"),
+ "#includedir %s" % (path),
+ "",
+ ]
sudoers_contents = "\n".join(lines)
util.append_file(sudo_base, sudoers_contents)
LOG.debug("Added '#includedir %s' to %s", path, sudo_base)
@@ -708,7 +771,7 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
sudo_file = self.ci_sudoers_fn
lines = [
- '',
+ "",
"# User rules for %s" % user,
]
if isinstance(rules, (list, tuple)):
@@ -741,9 +804,9 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
raise e
def create_group(self, name, members=None):
- group_add_cmd = ['groupadd', name]
+ group_add_cmd = ["groupadd", name]
if util.system_is_snappy():
- group_add_cmd.append('--extrausers')
+ group_add_cmd.append("--extrausers")
if not members:
members = []
@@ -761,11 +824,15 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
if len(members) > 0:
for member in members:
if not util.is_user(member):
- LOG.warning("Unable to add group member '%s' to group '%s'"
- "; user does not exist.", member, name)
+ LOG.warning(
+ "Unable to add group member '%s' to group '%s'"
+ "; user does not exist.",
+ member,
+ name,
+ )
continue
- subp.subp(['usermod', '-a', '-G', name, member])
+ subp.subp(["usermod", "-a", "-G", name, member])
LOG.info("Added user '%s' to group '%s'", member, name)
def shutdown_command(self, *, mode, delay, message):
@@ -784,6 +851,51 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
args.append(message)
return args
+ def manage_service(self, action, service):
+ """
+ Perform the requested action on a service. This handles the common
+ 'systemctl' and 'service' cases and may be overridden in subclasses
+ as necessary.
+ May raise ProcessExecutionError
+ """
+ init_cmd = self.init_cmd
+ if self.uses_systemd() or "systemctl" in init_cmd:
+ init_cmd = ["systemctl"]
+ cmds = {
+ "stop": ["stop", service],
+ "start": ["start", service],
+ "enable": ["enable", service],
+ "restart": ["restart", service],
+ "reload": ["reload-or-restart", service],
+ "try-reload": ["reload-or-try-restart", service],
+ }
+ else:
+ cmds = {
+ "stop": [service, "stop"],
+ "start": [service, "start"],
+ "enable": [service, "start"],
+ "restart": [service, "restart"],
+ "reload": [service, "restart"],
+ "try-reload": [service, "restart"],
+ }
+ cmd = list(init_cmd) + list(cmds[action])
+ return subp.subp(cmd, capture=True)
+
+ def set_keymap(self, layout, model, variant, options):
+ if self.uses_systemd():
+ subp.subp(
+ [
+ "localectl",
+ "set-x11-keymap",
+ layout,
+ model,
+ variant,
+ options,
+ ]
+ )
+ else:
+ raise NotImplementedError()
+
def _apply_hostname_transformations_to_url(url: str, transformations: list):
"""
@@ -837,7 +949,7 @@ def _sanitize_mirror_url(url: str):
* Converts it to its IDN form (see below for details)
* Replaces any non-Letters/Digits/Hyphen (LDH) characters in it with
hyphens
- * TODO: Remove any leading/trailing hyphens from each domain name label
+ * Removes any leading/trailing hyphens from each domain name label
Before we replace any invalid domain name characters, we first need to
ensure that any valid non-ASCII characters in the hostname will not be
@@ -871,27 +983,25 @@ def _sanitize_mirror_url(url: str):
# This is an IP address, not a hostname, so no need to apply the
# transformations
lambda hostname: None if net.is_ip_address(hostname) else hostname,
-
# Encode with IDNA to get the correct characters (as `bytes`), then
# decode with ASCII so we return a `str`
- lambda hostname: hostname.encode('idna').decode('ascii'),
-
+ lambda hostname: hostname.encode("idna").decode("ascii"),
# Replace any unacceptable characters with "-"
- lambda hostname: ''.join(
+ lambda hostname: "".join(
c if c in acceptable_chars else "-" for c in hostname
),
-
# Drop leading/trailing hyphens from each part of the hostname
- lambda hostname: '.'.join(
- part.strip('-') for part in hostname.split('.')
+ lambda hostname: ".".join(
+ part.strip("-") for part in hostname.split(".")
),
]
return _apply_hostname_transformations_to_url(url, transformations)
-def _get_package_mirror_info(mirror_info, data_source=None,
- mirror_filter=util.search_for_mirror):
+def _get_package_mirror_info(
+ mirror_info, data_source=None, mirror_filter=util.search_for_mirror
+):
# given a arch specific 'mirror_info' entry (from package_mirrors)
# search through the 'search' entries, and fallback appropriately
# return a dict with only {name: mirror} entries.
@@ -900,7 +1010,7 @@ def _get_package_mirror_info(mirror_info, data_source=None,
subst = {}
if data_source and data_source.availability_zone:
- subst['availability_zone'] = data_source.availability_zone
+ subst["availability_zone"] = data_source.availability_zone
# ec2 availability zones are named cc-direction-[0-9][a-d] (us-east-1b)
# the region is us-east-1. so region = az[0:-1]
@@ -908,18 +1018,18 @@ def _get_package_mirror_info(mirror_info, data_source=None,
ec2_region = data_source.availability_zone[0:-1]
if ALLOW_EC2_MIRRORS_ON_NON_AWS_INSTANCE_TYPES:
- subst['ec2_region'] = "%s" % ec2_region
+ subst["ec2_region"] = "%s" % ec2_region
elif data_source.platform_type == "ec2":
- subst['ec2_region'] = "%s" % ec2_region
+ subst["ec2_region"] = "%s" % ec2_region
if data_source and data_source.region:
- subst['region'] = data_source.region
+ subst["region"] = data_source.region
results = {}
- for (name, mirror) in mirror_info.get('failsafe', {}).items():
+ for (name, mirror) in mirror_info.get("failsafe", {}).items():
results[name] = mirror
- for (name, searchlist) in mirror_info.get('search', {}).items():
+ for (name, searchlist) in mirror_info.get("search", {}).items():
mirrors = []
for tmpl in searchlist:
try:
@@ -953,17 +1063,20 @@ def _get_arch_package_mirror_info(package_mirrors, arch):
def fetch(name):
- locs, looked_locs = importer.find_module(name, ['', __name__], ['Distro'])
+ locs, looked_locs = importer.find_module(name, ["", __name__], ["Distro"])
if not locs:
- raise ImportError("No distribution found for distro %s (searched %s)"
- % (name, looked_locs))
+ raise ImportError(
+ "No distribution found for distro %s (searched %s)"
+ % (name, looked_locs)
+ )
mod = importer.import_module(locs[0])
- cls = getattr(mod, 'Distro')
+ cls = getattr(mod, "Distro")
return cls
-def set_etc_timezone(tz, tz_file=None, tz_conf="/etc/timezone",
- tz_local="/etc/localtime"):
+def set_etc_timezone(
+ tz, tz_file=None, tz_conf="/etc/timezone", tz_local="/etc/localtime"
+):
util.write_file(tz_conf, str(tz).rstrip() + "\n")
# This ensures that the correct tz will be used for the system
if tz_local and tz_file:
@@ -980,7 +1093,7 @@ def set_etc_timezone(tz, tz_file=None, tz_conf="/etc/timezone",
def uses_systemd():
try:
- res = os.lstat('/run/systemd/system')
+ res = os.lstat("/run/systemd/system")
return stat.S_ISDIR(res.st_mode)
except Exception:
return False
diff --git a/cloudinit/distros/almalinux.py b/cloudinit/distros/almalinux.py
new file mode 100644
index 00000000..3dc0a342
--- /dev/null
+++ b/cloudinit/distros/almalinux.py
@@ -0,0 +1,10 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+from cloudinit.distros import rhel
+
+
+class Distro(rhel.Distro):
+ pass
+
+
+# vi: ts=4 expandtab
diff --git a/cloudinit/distros/alpine.py b/cloudinit/distros/alpine.py
index ca5bfe80..3d7d4891 100644
--- a/cloudinit/distros/alpine.py
+++ b/cloudinit/distros/alpine.py
@@ -6,13 +6,8 @@
#
# This file is part of cloud-init. See LICENSE file for license information.
-from cloudinit import distros
-from cloudinit import helpers
-from cloudinit import subp
-from cloudinit import util
-
+from cloudinit import distros, helpers, subp, util
from cloudinit.distros.parsers.hostname import HostnameConf
-
from cloudinit.settings import PER_INSTANCE
NETWORK_FILE_HEADER = """\
@@ -26,12 +21,11 @@ NETWORK_FILE_HEADER = """\
class Distro(distros.Distro):
- init_cmd = ['rc-service'] # init scripts
+ init_cmd = ["rc-service"] # init scripts
locale_conf_fn = "/etc/profile.d/locale.sh"
network_conf_fn = "/etc/network/interfaces"
renderer_configs = {
- "eni": {"eni_path": network_conf_fn,
- "eni_header": NETWORK_FILE_HEADER}
+ "eni": {"eni_path": network_conf_fn, "eni_header": NETWORK_FILE_HEADER}
}
def __init__(self, name, cfg, paths):
@@ -40,13 +34,13 @@ class Distro(distros.Distro):
# calls from repeatly happening (when they
# should only happen say once per instance...)
self._runner = helpers.Runners(paths)
- self.default_locale = 'C.UTF-8'
- self.osfamily = 'alpine'
- cfg['ssh_svcname'] = 'sshd'
+ self.default_locale = "C.UTF-8"
+ self.osfamily = "alpine"
+ cfg["ssh_svcname"] = "sshd"
def get_locale(self):
"""The default locale for Alpine Linux is different than
- cloud-init's DataSource default.
+ cloud-init's DataSource default.
"""
return self.default_locale
@@ -71,33 +65,20 @@ class Distro(distros.Distro):
def install_packages(self, pkglist):
self.update_package_sources()
- self.package_command('add', pkgs=pkglist)
-
- def _write_network_config(self, netconfig):
- return self._supported_write_network_config(netconfig)
-
- def _bring_up_interfaces(self, device_names):
- use_all = False
- for d in device_names:
- if d == 'all':
- use_all = True
- if use_all:
- return distros.Distro._bring_up_interface(self, '-a')
- else:
- return distros.Distro._bring_up_interfaces(self, device_names)
+ self.package_command("add", pkgs=pkglist)
- def _write_hostname(self, your_hostname, out_fn):
+ def _write_hostname(self, hostname, filename):
conf = None
try:
# Try to update the previous one
# so lets see if we can read it first.
- conf = self._read_hostname_conf(out_fn)
+ conf = self._read_hostname_conf(filename)
except IOError:
pass
if not conf:
- conf = HostnameConf('')
- conf.set_hostname(your_hostname)
- util.write_file(out_fn, str(conf), 0o644)
+ conf = HostnameConf("")
+ conf.set_hostname(hostname)
+ util.write_file(filename, str(conf), 0o644)
def _read_system_hostname(self):
sys_hostname = self._read_hostname(self.hostname_conf_fn)
@@ -129,7 +110,7 @@ class Distro(distros.Distro):
if pkgs is None:
pkgs = []
- cmd = ['apk']
+ cmd = ["apk"]
# Redirect output
cmd.append("--quiet")
@@ -141,25 +122,32 @@ class Distro(distros.Distro):
if command:
cmd.append(command)
- pkglist = util.expand_package_list('%s-%s', pkgs)
+ if command == "upgrade":
+ cmd.extend(["--update-cache", "--available"])
+
+ pkglist = util.expand_package_list("%s-%s", pkgs)
cmd.extend(pkglist)
# Allow the output of this to flow outwards (ie not be captured)
subp.subp(cmd, capture=False)
def update_package_sources(self):
- self._runner.run("update-sources", self.package_command,
- ["update"], freq=PER_INSTANCE)
+ self._runner.run(
+ "update-sources",
+ self.package_command,
+ ["update"],
+ freq=PER_INSTANCE,
+ )
@property
def preferred_ntp_clients(self):
"""Allow distro to determine the preferred ntp client list"""
if not self._preferred_ntp_clients:
- self._preferred_ntp_clients = ['chrony', 'ntp']
+ self._preferred_ntp_clients = ["chrony", "ntp"]
return self._preferred_ntp_clients
- def shutdown_command(self, mode='poweroff', delay='now', message=None):
+ def shutdown_command(self, mode="poweroff", delay="now", message=None):
# called from cc_power_state_change.load_power_state
# Alpine has halt/poweroff/reboot, with the following specifics:
# - we use them rather than the generic "shutdown"
@@ -173,7 +161,7 @@ class Distro(distros.Distro):
# halt/poweroff/reboot commands take seconds rather than minutes.
if delay == "now":
# Alpine's commands do not understand "now".
- command += ['0']
+ command += ["0"]
else:
try:
command.append(str(int(delay) * 60))
@@ -185,4 +173,5 @@ class Distro(distros.Distro):
return command
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/amazon.py b/cloudinit/distros/amazon.py
index 5fcec952..a3573547 100644
--- a/cloudinit/distros/amazon.py
+++ b/cloudinit/distros/amazon.py
@@ -14,7 +14,6 @@ from cloudinit.distros import rhel
class Distro(rhel.Distro):
-
def update_package_sources(self):
return None
diff --git a/cloudinit/distros/arch.py b/cloudinit/distros/arch.py
index 967be168..0bdfef83 100644
--- a/cloudinit/distros/arch.py
+++ b/cloudinit/distros/arch.py
@@ -4,33 +4,29 @@
#
# This file is part of cloud-init. See LICENSE file for license information.
-from cloudinit import distros
-from cloudinit import helpers
-from cloudinit import log as logging
-from cloudinit import util
-from cloudinit import subp
+import os
+from cloudinit import distros, helpers
+from cloudinit import log as logging
+from cloudinit import subp, util
from cloudinit.distros import net_util
from cloudinit.distros.parsers.hostname import HostnameConf
-
from cloudinit.net.renderers import RendererNotFoundError
-
from cloudinit.settings import PER_INSTANCE
-import os
-
LOG = logging.getLogger(__name__)
class Distro(distros.Distro):
- locale_conf_fn = "/etc/locale.gen"
+ locale_gen_fn = "/etc/locale.gen"
network_conf_dir = "/etc/netctl"
- resolve_conf_fn = "/etc/resolv.conf"
- init_cmd = ['systemctl'] # init scripts
+ init_cmd = ["systemctl"] # init scripts
renderer_configs = {
- "netplan": {"netplan_path": "/etc/netplan/50-cloud-init.yaml",
- "netplan_header": "# generated by cloud-init\n",
- "postcmds": True}
+ "netplan": {
+ "netplan_path": "/etc/netplan/50-cloud-init.yaml",
+ "netplan_header": "# generated by cloud-init\n",
+ "postcmds": True,
+ }
}
def __init__(self, name, cfg, paths):
@@ -39,83 +35,94 @@ class Distro(distros.Distro):
# calls from repeatly happening (when they
# should only happen say once per instance...)
self._runner = helpers.Runners(paths)
- self.osfamily = 'arch'
- cfg['ssh_svcname'] = 'sshd'
+ self.osfamily = "arch"
+ cfg["ssh_svcname"] = "sshd"
def apply_locale(self, locale, out_fn=None):
- if not out_fn:
- out_fn = self.locale_conf_fn
- subp.subp(['locale-gen', '-G', locale], capture=False)
- # "" provides trailing newline during join
+ if out_fn is not None and out_fn != "/etc/locale.conf":
+ LOG.warning(
+ "Invalid locale_configfile %s, only supported "
+ "value is /etc/locale.conf",
+ out_fn,
+ )
lines = [
util.make_header(),
- 'LANG="%s"' % (locale),
+ # Hard-coding the charset isn't ideal, but there is no other way.
+ "%s UTF-8" % (locale),
"",
]
- util.write_file(out_fn, "\n".join(lines))
+ util.write_file(self.locale_gen_fn, "\n".join(lines))
+ subp.subp(["locale-gen"], capture=False)
+ # In the future systemd can handle locale-gen stuff:
+ # https://github.com/systemd/systemd/pull/9864
+ subp.subp(["localectl", "set-locale", locale], capture=False)
def install_packages(self, pkglist):
self.update_package_sources()
- self.package_command('', pkgs=pkglist)
+ self.package_command("", pkgs=pkglist)
- def _write_network_config(self, netconfig):
+ def _write_network_state(self, network_state):
try:
- return self._supported_write_network_config(netconfig)
+ super()._write_network_state(network_state)
except RendererNotFoundError as e:
# Fall back to old _write_network
raise NotImplementedError from e
def _write_network(self, settings):
entries = net_util.translate_network(settings)
- LOG.debug("Translated ubuntu style network settings %s into %s",
- settings, entries)
+ LOG.debug(
+ "Translated ubuntu style network settings %s into %s",
+ settings,
+ entries,
+ )
return _render_network(
- entries, resolv_conf=self.resolve_conf_fn,
+ entries,
+ resolv_conf=self.resolve_conf_fn,
conf_dir=self.network_conf_dir,
- enable_func=self._enable_interface)
+ enable_func=self._enable_interface,
+ )
def _enable_interface(self, device_name):
- cmd = ['netctl', 'reenable', device_name]
+ cmd = ["netctl", "reenable", device_name]
try:
(_out, err) = subp.subp(cmd)
if len(err):
- LOG.warning("Running %s resulted in stderr output: %s",
- cmd, err)
+ LOG.warning(
+ "Running %s resulted in stderr output: %s", cmd, err
+ )
except subp.ProcessExecutionError:
util.logexc(LOG, "Running interface command %s failed", cmd)
def _bring_up_interface(self, device_name):
- cmd = ['netctl', 'restart', device_name]
- LOG.debug("Attempting to run bring up interface %s using command %s",
- device_name, cmd)
+ cmd = ["netctl", "restart", device_name]
+ LOG.debug(
+ "Attempting to run bring up interface %s using command %s",
+ device_name,
+ cmd,
+ )
try:
(_out, err) = subp.subp(cmd)
if len(err):
- LOG.warning("Running %s resulted in stderr output: %s",
- cmd, err)
+ LOG.warning(
+ "Running %s resulted in stderr output: %s", cmd, err
+ )
return True
except subp.ProcessExecutionError:
util.logexc(LOG, "Running interface command %s failed", cmd)
return False
- def _bring_up_interfaces(self, device_names):
- for d in device_names:
- if not self._bring_up_interface(d):
- return False
- return True
-
- def _write_hostname(self, your_hostname, out_fn):
+ def _write_hostname(self, hostname, filename):
conf = None
try:
# Try to update the previous one
# so lets see if we can read it first.
- conf = self._read_hostname_conf(out_fn)
+ conf = self._read_hostname_conf(filename)
except IOError:
pass
if not conf:
- conf = HostnameConf('')
- conf.set_hostname(your_hostname)
- util.write_file(out_fn, str(conf), omode="w", mode=0o644)
+ conf = HostnameConf("")
+ conf.set_hostname(hostname)
+ util.write_file(filename, str(conf), omode="w", mode=0o644)
def _read_system_hostname(self):
sys_hostname = self._read_hostname(self.hostname_conf_fn)
@@ -137,6 +144,21 @@ class Distro(distros.Distro):
return default
return hostname
+ # hostname (inetutils) isn't installed per default on arch, so we use
+ # hostnamectl which is installed per default (systemd).
+ def _apply_hostname(self, hostname):
+ LOG.debug(
+ "Non-persistently setting the system hostname to %s", hostname
+ )
+ try:
+ subp.subp(["hostnamectl", "--transient", "set-hostname", hostname])
+ except subp.ProcessExecutionError:
+ util.logexc(
+ LOG,
+ "Failed to non-persistently adjust the system hostname to %s",
+ hostname,
+ )
+
def set_timezone(self, tz):
distros.set_etc_timezone(tz=tz, tz_file=self._find_tz_file(tz))
@@ -144,7 +166,7 @@ class Distro(distros.Distro):
if pkgs is None:
pkgs = []
- cmd = ['pacman', "-Sy", "--quiet", "--noconfirm"]
+ cmd = ["pacman", "-Sy", "--quiet", "--noconfirm"]
# Redirect output
if args and isinstance(args, str):
@@ -152,22 +174,30 @@ class Distro(distros.Distro):
elif args and isinstance(args, list):
cmd.extend(args)
+ if command == "upgrade":
+ command = "-u"
if command:
cmd.append(command)
- pkglist = util.expand_package_list('%s-%s', pkgs)
+ pkglist = util.expand_package_list("%s-%s", pkgs)
cmd.extend(pkglist)
# Allow the output of this to flow outwards (ie not be captured)
subp.subp(cmd, capture=False)
def update_package_sources(self):
- self._runner.run("update-sources", self.package_command,
- ["-y"], freq=PER_INSTANCE)
-
-
-def _render_network(entries, target="/", conf_dir="etc/netctl",
- resolv_conf="etc/resolv.conf", enable_func=None):
+ self._runner.run(
+ "update-sources", self.package_command, ["-y"], freq=PER_INSTANCE
+ )
+
+
+def _render_network(
+ entries,
+ target="/",
+ conf_dir="etc/netctl",
+ resolv_conf="etc/resolv.conf",
+ enable_func=None,
+):
"""Render the translate_network format into netctl files in target.
Paths will be rendered under target.
"""
@@ -178,29 +208,27 @@ def _render_network(entries, target="/", conf_dir="etc/netctl",
conf_dir = subp.target_path(target, conf_dir)
for (dev, info) in entries.items():
- if dev == 'lo':
+ if dev == "lo":
# no configuration should be rendered for 'lo'
continue
devs.append(dev)
net_fn = os.path.join(conf_dir, dev)
net_cfg = {
- 'Connection': 'ethernet',
- 'Interface': dev,
- 'IP': info.get('bootproto'),
- 'Address': "%s/%s" % (info.get('address'),
- info.get('netmask')),
- 'Gateway': info.get('gateway'),
- 'DNS': info.get('dns-nameservers', []),
+ "Connection": "ethernet",
+ "Interface": dev,
+ "IP": info.get("bootproto"),
+ "Address": "%s/%s" % (info.get("address"), info.get("netmask")),
+ "Gateway": info.get("gateway"),
+ "DNS": info.get("dns-nameservers", []),
}
util.write_file(net_fn, convert_netctl(net_cfg))
- if enable_func and info.get('auto'):
+ if enable_func and info.get("auto"):
enable_func(dev)
- if 'dns-nameservers' in info:
- nameservers.extend(info['dns-nameservers'])
+ if "dns-nameservers" in info:
+ nameservers.extend(info["dns-nameservers"])
if nameservers:
- util.write_file(resolv_conf,
- convert_resolv_conf(nameservers))
+ util.write_file(resolv_conf, convert_resolv_conf(nameservers))
return devs
@@ -217,17 +245,18 @@ def convert_netctl(settings):
if val is None:
val = ""
elif isinstance(val, (tuple, list)):
- val = "(" + ' '.join("'%s'" % v for v in val) + ")"
+ val = "(" + " ".join("'%s'" % v for v in val) + ")"
result.append("%s=%s\n" % (key, val))
- return ''.join(result)
+ return "".join(result)
def convert_resolv_conf(settings):
"""Returns a settings string formatted for resolv.conf."""
- result = ''
+ result = ""
if isinstance(settings, list):
for ns in settings:
- result = result + 'nameserver %s\n' % ns
+ result = result + "nameserver %s\n" % ns
return result
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/bsd.py b/cloudinit/distros/bsd.py
index f717a667..1b4498b3 100644
--- a/cloudinit/distros/bsd.py
+++ b/cloudinit/distros/bsd.py
@@ -1,12 +1,10 @@
import platform
-from cloudinit import distros
-from cloudinit.distros import bsd_utils
-from cloudinit import helpers
+from cloudinit import distros, helpers
from cloudinit import log as logging
-from cloudinit import net
-from cloudinit import subp
-from cloudinit import util
+from cloudinit import net, subp, util
+from cloudinit.distros import bsd_utils
+
from .networking import BSDNetworking
LOG = logging.getLogger(__name__)
@@ -14,12 +12,12 @@ LOG = logging.getLogger(__name__)
class BSD(distros.Distro):
networking_cls = BSDNetworking
- hostname_conf_fn = '/etc/rc.conf'
+ hostname_conf_fn = "/etc/rc.conf"
rc_conf_fn = "/etc/rc.conf"
# This differs from the parent Distro class, which has -P for
# poweroff.
- shutdown_options_map = {'halt': '-H', 'poweroff': '-p', 'reboot': '-r'}
+ shutdown_options_map = {"halt": "-H", "poweroff": "-p", "reboot": "-r"}
# Set in BSD distro subclasses
group_add_cmd_prefix = []
@@ -35,7 +33,7 @@ class BSD(distros.Distro):
# calls from repeatly happening (when they
# should only happen say once per instance...)
self._runner = helpers.Runners(paths)
- cfg['ssh_svcname'] = 'sshd'
+ cfg["ssh_svcname"] = "sshd"
self.osfamily = platform.system().lower()
def _read_system_hostname(self):
@@ -43,13 +41,13 @@ class BSD(distros.Distro):
return (self.hostname_conf_fn, sys_hostname)
def _read_hostname(self, filename, default=None):
- return bsd_utils.get_rc_config_value('hostname')
+ return bsd_utils.get_rc_config_value("hostname")
def _get_add_member_to_group_cmd(self, member_name, group_name):
- raise NotImplementedError('Return list cmd to add member to group')
+ raise NotImplementedError("Return list cmd to add member to group")
def _write_hostname(self, hostname, filename):
- bsd_utils.set_rc_config_value('hostname', hostname, fn='/etc/rc.conf')
+ bsd_utils.set_rc_config_value("hostname", hostname, fn="/etc/rc.conf")
def create_group(self, name, members=None):
if util.is_group(name):
@@ -66,45 +64,55 @@ class BSD(distros.Distro):
members = []
for member in members:
if not util.is_user(member):
- LOG.warning("Unable to add group member '%s' to group '%s'"
- "; user does not exist.", member, name)
+ LOG.warning(
+ "Unable to add group member '%s' to group '%s'"
+ "; user does not exist.",
+ member,
+ name,
+ )
continue
try:
subp.subp(self._get_add_member_to_group_cmd(member, name))
LOG.info("Added user '%s' to group '%s'", member, name)
except Exception:
- util.logexc(LOG, "Failed to add user '%s' to group '%s'",
- member, name)
+ util.logexc(
+ LOG, "Failed to add user '%s' to group '%s'", member, name
+ )
def generate_fallback_config(self):
- nconf = {'config': [], 'version': 1}
+ nconf = {"config": [], "version": 1}
for mac, name in net.get_interfaces_by_mac().items():
- nconf['config'].append(
- {'type': 'physical', 'name': name,
- 'mac_address': mac, 'subnets': [{'type': 'dhcp'}]})
+ nconf["config"].append(
+ {
+ "type": "physical",
+ "name": name,
+ "mac_address": mac,
+ "subnets": [{"type": "dhcp"}],
+ }
+ )
return nconf
def install_packages(self, pkglist):
self.update_package_sources()
- self.package_command('install', pkgs=pkglist)
+ self.package_command("install", pkgs=pkglist)
def _get_pkg_cmd_environ(self):
"""Return environment vars used in *BSD package_command operations"""
- raise NotImplementedError('BSD subclasses return a dict of env vars')
+ raise NotImplementedError("BSD subclasses return a dict of env vars")
def package_command(self, command, args=None, pkgs=None):
if pkgs is None:
pkgs = []
- if command == 'install':
+ if command == "install":
cmd = self.pkg_cmd_install_prefix
- elif command == 'remove':
+ elif command == "remove":
cmd = self.pkg_cmd_remove_prefix
- elif command == 'update':
+ elif command == "update":
if not self.pkg_cmd_update_prefix:
return
cmd = self.pkg_cmd_update_prefix
- elif command == 'upgrade':
+ elif command == "upgrade":
if not self.pkg_cmd_upgrade_prefix:
return
cmd = self.pkg_cmd_upgrade_prefix
@@ -114,20 +122,17 @@ class BSD(distros.Distro):
elif args and isinstance(args, list):
cmd.extend(args)
- pkglist = util.expand_package_list('%s-%s', pkgs)
+ pkglist = util.expand_package_list("%s-%s", pkgs)
cmd.extend(pkglist)
# Allow the output of this to flow outwards (ie not be captured)
subp.subp(cmd, env=self._get_pkg_cmd_environ(), capture=False)
- def _write_network_config(self, netconfig):
- return self._supported_write_network_config(netconfig)
-
def set_timezone(self, tz):
distros.set_etc_timezone(tz=tz, tz_file=self._find_tz_file(tz))
def apply_locale(self, locale, out_fn=None):
- LOG.debug('Cannot set the locale.')
+ LOG.debug("Cannot set the locale.")
def apply_network_config_names(self, netconfig):
- LOG.debug('Cannot rename network interface.')
+ LOG.debug("Cannot rename network interface.")
diff --git a/cloudinit/distros/bsd_utils.py b/cloudinit/distros/bsd_utils.py
index 079d0d53..00cd0662 100644
--- a/cloudinit/distros/bsd_utils.py
+++ b/cloudinit/distros/bsd_utils.py
@@ -18,31 +18,31 @@ def _unquote(value):
return value
-def get_rc_config_value(key, fn='/etc/rc.conf'):
- key_prefix = '{}='.format(key)
+def get_rc_config_value(key, fn="/etc/rc.conf"):
+ key_prefix = "{}=".format(key)
for line in util.load_file(fn).splitlines():
if line.startswith(key_prefix):
- value = line.replace(key_prefix, '')
+ value = line.replace(key_prefix, "")
return _unquote(value)
-def set_rc_config_value(key, value, fn='/etc/rc.conf'):
+def set_rc_config_value(key, value, fn="/etc/rc.conf"):
lines = []
done = False
value = shlex.quote(value)
original_content = util.load_file(fn)
for line in original_content.splitlines():
- if '=' in line:
- k, v = line.split('=', 1)
+ if "=" in line:
+ k, v = line.split("=", 1)
if k == key:
v = value
done = True
- lines.append('='.join([k, v]))
+ lines.append("=".join([k, v]))
else:
lines.append(line)
if not done:
- lines.append('='.join([key, value]))
- new_content = '\n'.join(lines) + '\n'
+ lines.append("=".join([key, value]))
+ new_content = "\n".join(lines) + "\n"
if new_content != original_content:
util.write_file(fn, new_content)
diff --git a/cloudinit/distros/centos.py b/cloudinit/distros/centos.py
index edb3165d..3dc0a342 100644
--- a/cloudinit/distros/centos.py
+++ b/cloudinit/distros/centos.py
@@ -6,4 +6,5 @@ from cloudinit.distros import rhel
class Distro(rhel.Distro):
pass
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/cloudlinux.py b/cloudinit/distros/cloudlinux.py
new file mode 100644
index 00000000..3dc0a342
--- /dev/null
+++ b/cloudinit/distros/cloudlinux.py
@@ -0,0 +1,10 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+from cloudinit.distros import rhel
+
+
+class Distro(rhel.Distro):
+ pass
+
+
+# vi: ts=4 expandtab
diff --git a/cloudinit/distros/debian.py b/cloudinit/distros/debian.py
index 844aaf21..6dc1ad40 100644
--- a/cloudinit/distros/debian.py
+++ b/cloudinit/distros/debian.py
@@ -7,27 +7,29 @@
# Author: Joshua Harlow <harlowja@yahoo-inc.com>
#
# This file is part of cloud-init. See LICENSE file for license information.
-
+import fcntl
import os
+import time
-from cloudinit import distros
-from cloudinit import helpers
+from cloudinit import distros, helpers
from cloudinit import log as logging
-from cloudinit import subp
-from cloudinit import util
-
+from cloudinit import subp, util
from cloudinit.distros.parsers.hostname import HostnameConf
-
from cloudinit.settings import PER_INSTANCE
LOG = logging.getLogger(__name__)
-APT_GET_COMMAND = ('apt-get', '--option=Dpkg::Options::=--force-confold',
- '--option=Dpkg::options::=--force-unsafe-io',
- '--assume-yes', '--quiet')
+APT_LOCK_WAIT_TIMEOUT = 30
+APT_GET_COMMAND = (
+ "apt-get",
+ "--option=Dpkg::Options::=--force-confold",
+ "--option=Dpkg::options::=--force-unsafe-io",
+ "--assume-yes",
+ "--quiet",
+)
APT_GET_WRAPPER = {
- 'command': 'eatmydata',
- 'enabled': 'auto',
+ "command": "eatmydata",
+ "enabled": "auto",
}
NETWORK_FILE_HEADER = """\
@@ -41,19 +43,36 @@ NETWORK_FILE_HEADER = """\
NETWORK_CONF_FN = "/etc/network/interfaces.d/50-cloud-init"
LOCALE_CONF_FN = "/etc/default/locale"
+# The frontend lock needs to be acquired first followed by the order that
+# apt uses. /var/lib/apt/lists is locked independently of that install chain,
+# and only locked during update, so you can acquire it either order.
+# Also update does not acquire the dpkg frontend lock.
+# More context:
+# https://github.com/canonical/cloud-init/pull/1034#issuecomment-986971376
+APT_LOCK_FILES = [
+ "/var/lib/dpkg/lock-frontend",
+ "/var/lib/dpkg/lock",
+ "/var/cache/apt/archives/lock",
+ "/var/lib/apt/lists/lock",
+]
+
class Distro(distros.Distro):
hostname_conf_fn = "/etc/hostname"
network_conf_fn = {
"eni": "/etc/network/interfaces.d/50-cloud-init",
- "netplan": "/etc/netplan/50-cloud-init.yaml"
+ "netplan": "/etc/netplan/50-cloud-init.yaml",
}
renderer_configs = {
- "eni": {"eni_path": network_conf_fn["eni"],
- "eni_header": NETWORK_FILE_HEADER},
- "netplan": {"netplan_path": network_conf_fn["netplan"],
- "netplan_header": NETWORK_FILE_HEADER,
- "postcmds": True}
+ "eni": {
+ "eni_path": network_conf_fn["eni"],
+ "eni_header": NETWORK_FILE_HEADER,
+ },
+ "netplan": {
+ "netplan_path": network_conf_fn["netplan"],
+ "netplan_header": NETWORK_FILE_HEADER,
+ "postcmds": True,
+ },
}
def __init__(self, name, cfg, paths):
@@ -62,8 +81,8 @@ class Distro(distros.Distro):
# calls from repeatly happening (when they
# should only happen say once per instance...)
self._runner = helpers.Runners(paths)
- self.osfamily = 'debian'
- self.default_locale = 'en_US.UTF-8'
+ self.osfamily = "debian"
+ self.default_locale = "en_US.UTF-8"
self.system_locale = None
def get_locale(self):
@@ -74,25 +93,29 @@ class Distro(distros.Distro):
self.system_locale = read_system_locale()
# Return system_locale setting if valid, else use default locale
- return (self.system_locale if self.system_locale else
- self.default_locale)
+ return (
+ self.system_locale if self.system_locale else self.default_locale
+ )
- def apply_locale(self, locale, out_fn=None, keyname='LANG'):
+ def apply_locale(self, locale, out_fn=None, keyname="LANG"):
"""Apply specified locale to system, regenerate if specified locale
- differs from system default."""
+ differs from system default."""
if not out_fn:
out_fn = LOCALE_CONF_FN
if not locale:
- raise ValueError('Failed to provide locale value.')
+ raise ValueError("Failed to provide locale value.")
# Only call locale regeneration if needed
# Update system locale config with specified locale if needed
distro_locale = self.get_locale()
conf_fn_exists = os.path.exists(out_fn)
sys_locale_unset = False if self.system_locale else True
- need_regen = (locale.lower() != distro_locale.lower() or
- not conf_fn_exists or sys_locale_unset)
+ need_regen = (
+ locale.lower() != distro_locale.lower()
+ or not conf_fn_exists
+ or sys_locale_unset
+ )
need_conf = not conf_fn_exists or need_regen or sys_locale_unset
if need_regen:
@@ -100,7 +123,10 @@ class Distro(distros.Distro):
else:
LOG.debug(
"System has '%s=%s' requested '%s', skipping regeneration.",
- keyname, self.system_locale, locale)
+ keyname,
+ self.system_locale,
+ locale,
+ )
if need_conf:
update_locale_conf(locale, out_fn, keyname=keyname)
@@ -109,34 +135,24 @@ class Distro(distros.Distro):
def install_packages(self, pkglist):
self.update_package_sources()
- self.package_command('install', pkgs=pkglist)
+ self.package_command("install", pkgs=pkglist)
- def _write_network_config(self, netconfig):
+ def _write_network_state(self, network_state):
_maybe_remove_legacy_eth0()
- return self._supported_write_network_config(netconfig)
-
- def _bring_up_interfaces(self, device_names):
- use_all = False
- for d in device_names:
- if d == 'all':
- use_all = True
- if use_all:
- return distros.Distro._bring_up_interface(self, '--all')
- else:
- return distros.Distro._bring_up_interfaces(self, device_names)
+ return super()._write_network_state(network_state)
- def _write_hostname(self, your_hostname, out_fn):
+ def _write_hostname(self, hostname, filename):
conf = None
try:
# Try to update the previous one
# so lets see if we can read it first.
- conf = self._read_hostname_conf(out_fn)
+ conf = self._read_hostname_conf(filename)
except IOError:
pass
if not conf:
- conf = HostnameConf('')
- conf.set_hostname(your_hostname)
- util.write_file(out_fn, str(conf), 0o644)
+ conf = HostnameConf("")
+ conf.set_hostname(hostname)
+ util.write_file(filename, str(conf), 0o644)
def _read_system_hostname(self):
sys_hostname = self._read_hostname(self.hostname_conf_fn)
@@ -165,18 +181,90 @@ class Distro(distros.Distro):
def set_timezone(self, tz):
distros.set_etc_timezone(tz=tz, tz_file=self._find_tz_file(tz))
+ def _apt_lock_available(self, lock_files=None):
+ """Determines if another process holds any apt locks.
+
+ If all locks are clear, return True else False.
+ """
+ if lock_files is None:
+ lock_files = APT_LOCK_FILES
+ for lock in lock_files:
+ if not os.path.exists(lock):
+ # Only wait for lock files that already exist
+ continue
+ with open(lock, "w") as handle:
+ try:
+ fcntl.lockf(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
+ except OSError:
+ return False
+ return True
+
+ def _wait_for_apt_command(
+ self, short_cmd, subp_kwargs, timeout=APT_LOCK_WAIT_TIMEOUT
+ ):
+ """Wait for apt install to complete.
+
+ short_cmd: Name of command like "upgrade" or "install"
+ subp_kwargs: kwargs to pass to subp
+ """
+ start_time = time.time()
+ LOG.debug("Waiting for apt lock")
+ while time.time() - start_time < timeout:
+ if not self._apt_lock_available():
+ time.sleep(1)
+ continue
+ LOG.debug("apt lock available")
+ try:
+ # Allow the output of this to flow outwards (not be captured)
+ log_msg = "apt-%s [%s]" % (
+ short_cmd,
+ " ".join(subp_kwargs["args"]),
+ )
+ return util.log_time(
+ logfunc=LOG.debug,
+ msg=log_msg,
+ func=subp.subp,
+ kwargs=subp_kwargs,
+ )
+ except subp.ProcessExecutionError:
+ # Even though we have already waited for the apt lock to be
+ # available, it is possible that the lock was acquired by
+ # another process since the check. Since apt doesn't provide
+ # a meaningful error code to check and checking the error
+ # text is fragile and subject to internationalization, we
+ # can instead check the apt lock again. If the apt lock is
+ # still available, given the length of an average apt
+ # transaction, it is extremely unlikely that another process
+ # raced us when we tried to acquire it, so raise the apt
+ # error received. If the lock is unavailable, just keep waiting
+ if self._apt_lock_available():
+ raise
+ LOG.debug("Another process holds apt lock. Waiting...")
+ time.sleep(1)
+ raise TimeoutError("Could not get apt lock")
+
def package_command(self, command, args=None, pkgs=None):
+ """Run the given package command.
+
+ On Debian, this will run apt-get (unless APT_GET_COMMAND is set).
+
+ command: The command to run, like "upgrade" or "install"
+ args: Arguments passed to apt itself in addition to
+ any specified in APT_GET_COMMAND
+ pkgs: Apt packages that the command will apply to
+ """
if pkgs is None:
pkgs = []
e = os.environ.copy()
- # See: http://manpages.ubuntu.com/manpages/xenial/man7/debconf.7.html
- e['DEBIAN_FRONTEND'] = 'noninteractive'
+ # See: http://manpages.ubuntu.com/manpages/bionic/man7/debconf.7.html
+ e["DEBIAN_FRONTEND"] = "noninteractive"
wcfg = self.get_option("apt_get_wrapper", APT_GET_WRAPPER)
cmd = _get_wrapper_prefix(
- wcfg.get('command', APT_GET_WRAPPER['command']),
- wcfg.get('enabled', APT_GET_WRAPPER['enabled']))
+ wcfg.get("command", APT_GET_WRAPPER["command"]),
+ wcfg.get("enabled", APT_GET_WRAPPER["enabled"]),
+ )
cmd.extend(list(self.get_option("apt_get_command", APT_GET_COMMAND)))
@@ -187,35 +275,46 @@ class Distro(distros.Distro):
subcmd = command
if command == "upgrade":
- subcmd = self.get_option("apt_get_upgrade_subcommand",
- "dist-upgrade")
+ subcmd = self.get_option(
+ "apt_get_upgrade_subcommand", "dist-upgrade"
+ )
cmd.append(subcmd)
- pkglist = util.expand_package_list('%s=%s', pkgs)
+ pkglist = util.expand_package_list("%s=%s", pkgs)
cmd.extend(pkglist)
- # Allow the output of this to flow outwards (ie not be captured)
- util.log_time(logfunc=LOG.debug,
- msg="apt-%s [%s]" % (command, ' '.join(cmd)),
- func=subp.subp,
- args=(cmd,), kwargs={'env': e, 'capture': False})
+ self._wait_for_apt_command(
+ short_cmd=command,
+ subp_kwargs={"args": cmd, "env": e, "capture": False},
+ )
def update_package_sources(self):
- self._runner.run("update-sources", self.package_command,
- ["update"], freq=PER_INSTANCE)
+ self._runner.run(
+ "update-sources",
+ self.package_command,
+ ["update"],
+ freq=PER_INSTANCE,
+ )
def get_primary_arch(self):
return util.get_dpkg_architecture()
+ def set_keymap(self, layout, model, variant, options):
+ # Let localectl take care of updating /etc/default/keyboard
+ distros.Distro.set_keymap(self, layout, model, variant, options)
+ # Workaround for localectl not applying new settings instantly
+ # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=926037
+ self.manage_service("restart", "console-setup")
+
def _get_wrapper_prefix(cmd, mode):
if isinstance(cmd, str):
cmd = [str(cmd)]
- if (util.is_true(mode) or
- (str(mode).lower() == "auto" and cmd[0] and
- subp.which(cmd[0]))):
+ if util.is_true(mode) or (
+ str(mode).lower() == "auto" and cmd[0] and subp.which(cmd[0])
+ ):
return cmd
else:
return []
@@ -223,13 +322,13 @@ def _get_wrapper_prefix(cmd, mode):
def _maybe_remove_legacy_eth0(path="/etc/network/interfaces.d/eth0.cfg"):
"""Ubuntu cloud images previously included a 'eth0.cfg' that had
- hard coded content. That file would interfere with the rendered
- configuration if it was present.
+ hard coded content. That file would interfere with the rendered
+ configuration if it was present.
- if the file does not exist do nothing.
- If the file exists:
- - with known content, remove it and warn
- - with unknown content, leave it and warn
+ if the file does not exist do nothing.
+ If the file exists:
+ - with known content, remove it and warn
+ - with unknown content, leave it and warn
"""
if not os.path.exists(path):
@@ -239,24 +338,25 @@ def _maybe_remove_legacy_eth0(path="/etc/network/interfaces.d/eth0.cfg"):
try:
contents = util.load_file(path)
known_contents = ["auto eth0", "iface eth0 inet dhcp"]
- lines = [f.strip() for f in contents.splitlines()
- if not f.startswith("#")]
+ lines = [
+ f.strip() for f in contents.splitlines() if not f.startswith("#")
+ ]
if lines == known_contents:
util.del_file(path)
msg = "removed %s with known contents" % path
else:
- msg = (bmsg + " '%s' exists with user configured content." % path)
+ msg = bmsg + " '%s' exists with user configured content." % path
except Exception:
msg = bmsg + " %s exists, but could not be read." % path
LOG.warning(msg)
-def read_system_locale(sys_path=LOCALE_CONF_FN, keyname='LANG'):
+def read_system_locale(sys_path=LOCALE_CONF_FN, keyname="LANG"):
"""Read system default locale setting, if present"""
sys_val = ""
if not sys_path:
- raise ValueError('Invalid path: %s' % sys_path)
+ raise ValueError("Invalid path: %s" % sys_path)
if os.path.exists(sys_path):
locale_content = util.load_file(sys_path)
@@ -266,16 +366,22 @@ def read_system_locale(sys_path=LOCALE_CONF_FN, keyname='LANG'):
return sys_val
-def update_locale_conf(locale, sys_path, keyname='LANG'):
+def update_locale_conf(locale, sys_path, keyname="LANG"):
"""Update system locale config"""
- LOG.debug('Updating %s with locale setting %s=%s',
- sys_path, keyname, locale)
+ LOG.debug(
+ "Updating %s with locale setting %s=%s", sys_path, keyname, locale
+ )
subp.subp(
- ['update-locale', '--locale-file=' + sys_path,
- '%s=%s' % (keyname, locale)], capture=False)
+ [
+ "update-locale",
+ "--locale-file=" + sys_path,
+ "%s=%s" % (keyname, locale),
+ ],
+ capture=False,
+ )
-def regenerate_locale(locale, sys_path, keyname='LANG'):
+def regenerate_locale(locale, sys_path, keyname="LANG"):
"""
Run locale-gen for the provided locale and set the default
system variable `keyname` appropriately in the provided `sys_path`.
@@ -286,13 +392,13 @@ def regenerate_locale(locale, sys_path, keyname='LANG'):
# C
# C.UTF-8
# POSIX
- if locale.lower() in ['c', 'c.utf-8', 'posix']:
- LOG.debug('%s=%s does not require rengeneration', keyname, locale)
+ if locale.lower() in ["c", "c.utf-8", "posix"]:
+ LOG.debug("%s=%s does not require rengeneration", keyname, locale)
return
# finally, trigger regeneration
- LOG.debug('Generating locales for %s', locale)
- subp.subp(['locale-gen', locale], capture=False)
+ LOG.debug("Generating locales for %s", locale)
+ subp.subp(["locale-gen", locale], capture=False)
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/dragonflybsd.py b/cloudinit/distros/dragonflybsd.py
new file mode 100644
index 00000000..0d02bee0
--- /dev/null
+++ b/cloudinit/distros/dragonflybsd.py
@@ -0,0 +1,12 @@
+# Copyright (C) 2020-2021 Gonéri Le Bouder
+#
+# This file is part of cloud-init. See LICENSE file for license information.
+
+import cloudinit.distros.freebsd
+
+
+class Distro(cloudinit.distros.freebsd.Distro):
+ home_dir = "/home"
+
+
+# vi: ts=4 expandtab
diff --git a/cloudinit/distros/eurolinux.py b/cloudinit/distros/eurolinux.py
new file mode 100644
index 00000000..3dc0a342
--- /dev/null
+++ b/cloudinit/distros/eurolinux.py
@@ -0,0 +1,10 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+from cloudinit.distros import rhel
+
+
+class Distro(rhel.Distro):
+ pass
+
+
+# vi: ts=4 expandtab
diff --git a/cloudinit/distros/fedora.py b/cloudinit/distros/fedora.py
index 0fe1fbca..39203225 100644
--- a/cloudinit/distros/fedora.py
+++ b/cloudinit/distros/fedora.py
@@ -14,4 +14,5 @@ from cloudinit.distros import rhel
class Distro(rhel.Distro):
pass
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/freebsd.py b/cloudinit/distros/freebsd.py
index dde34d41..513abdc2 100644
--- a/cloudinit/distros/freebsd.py
+++ b/cloudinit/distros/freebsd.py
@@ -10,53 +10,54 @@ from io import StringIO
import cloudinit.distros.bsd
from cloudinit import log as logging
-from cloudinit import subp
-from cloudinit import util
+from cloudinit import subp, util
from cloudinit.settings import PER_INSTANCE
LOG = logging.getLogger(__name__)
class Distro(cloudinit.distros.bsd.BSD):
- usr_lib_exec = '/usr/local/lib'
- login_conf_fn = '/etc/login.conf'
- login_conf_fn_bak = '/etc/login.conf.orig'
- ci_sudoers_fn = '/usr/local/etc/sudoers.d/90-cloud-init-users'
- group_add_cmd_prefix = ['pw', 'group', 'add']
+ """
+ Distro subclass for FreeBSD.
+
+ (N.B. DragonFlyBSD inherits from this class.)
+ """
+
+ usr_lib_exec = "/usr/local/lib"
+ login_conf_fn = "/etc/login.conf"
+ login_conf_fn_bak = "/etc/login.conf.orig"
+ ci_sudoers_fn = "/usr/local/etc/sudoers.d/90-cloud-init-users"
+ group_add_cmd_prefix = ["pw", "group", "add"]
pkg_cmd_install_prefix = ["pkg", "install"]
pkg_cmd_remove_prefix = ["pkg", "remove"]
pkg_cmd_update_prefix = ["pkg", "update"]
pkg_cmd_upgrade_prefix = ["pkg", "upgrade"]
-
- def _select_hostname(self, hostname, fqdn):
- # Should be FQDN if available. See rc.conf(5) in FreeBSD
- if fqdn:
- return fqdn
- return hostname
+ prefer_fqdn = True # See rc.conf(5) in FreeBSD
+ home_dir = "/usr/home"
def _get_add_member_to_group_cmd(self, member_name, group_name):
- return ['pw', 'usermod', '-n', member_name, '-G', group_name]
+ return ["pw", "usermod", "-n", member_name, "-G", group_name]
def add_user(self, name, **kwargs):
if util.is_user(name):
LOG.info("User %s already exists, skipping.", name)
return False
- pw_useradd_cmd = ['pw', 'useradd', '-n', name]
- log_pw_useradd_cmd = ['pw', 'useradd', '-n', name]
+ pw_useradd_cmd = ["pw", "useradd", "-n", name]
+ log_pw_useradd_cmd = ["pw", "useradd", "-n", name]
pw_useradd_opts = {
- "homedir": '-d',
- "gecos": '-c',
- "primary_group": '-g',
- "groups": '-G',
- "shell": '-s',
- "inactive": '-E',
+ "homedir": "-d",
+ "gecos": "-c",
+ "primary_group": "-g",
+ "groups": "-G",
+ "shell": "-s",
+ "inactive": "-E",
}
pw_useradd_flags = {
- "no_user_group": '--no-user-group',
- "system": '--system',
- "no_log_init": '--no-log-init',
+ "no_user_group": "--no-user-group",
+ "system": "--system",
+ "no_log_init": "--no-log-init",
}
for key, val in kwargs.items():
@@ -67,14 +68,19 @@ class Distro(cloudinit.distros.bsd.BSD):
pw_useradd_cmd.append(pw_useradd_flags[key])
log_pw_useradd_cmd.append(pw_useradd_flags[key])
- if 'no_create_home' in kwargs or 'system' in kwargs:
- pw_useradd_cmd.append('-d/nonexistent')
- log_pw_useradd_cmd.append('-d/nonexistent')
+ if "no_create_home" in kwargs or "system" in kwargs:
+ pw_useradd_cmd.append("-d/nonexistent")
+ log_pw_useradd_cmd.append("-d/nonexistent")
else:
- pw_useradd_cmd.append('-d/usr/home/%s' % name)
- pw_useradd_cmd.append('-m')
- log_pw_useradd_cmd.append('-d/usr/home/%s' % name)
- log_pw_useradd_cmd.append('-m')
+ pw_useradd_cmd.append(
+ "-d{home_dir}/{name}".format(home_dir=self.home_dir, name=name)
+ )
+ pw_useradd_cmd.append("-m")
+ log_pw_useradd_cmd.append(
+ "-d{home_dir}/{name}".format(home_dir=self.home_dir, name=name)
+ )
+
+ log_pw_useradd_cmd.append("-m")
# Run the command
LOG.info("Adding user %s", name)
@@ -85,13 +91,13 @@ class Distro(cloudinit.distros.bsd.BSD):
raise
# Set the password if it is provided
# For security consideration, only hashed passwd is assumed
- passwd_val = kwargs.get('passwd', None)
+ passwd_val = kwargs.get("passwd", None)
if passwd_val is not None:
self.set_passwd(name, passwd_val, hashed=True)
def expire_passwd(self, user):
try:
- subp.subp(['pw', 'usermod', user, '-p', '01-Jan-1970'])
+ subp.subp(["pw", "usermod", user, "-p", "01-Jan-1970"])
except Exception:
util.logexc(LOG, "Failed to set pw expiration for %s", user)
raise
@@ -103,15 +109,18 @@ class Distro(cloudinit.distros.bsd.BSD):
hash_opt = "-h"
try:
- subp.subp(['pw', 'usermod', user, hash_opt, '0'],
- data=passwd, logstring="chpasswd for %s" % user)
+ subp.subp(
+ ["pw", "usermod", user, hash_opt, "0"],
+ data=passwd,
+ logstring="chpasswd for %s" % user,
+ )
except Exception:
util.logexc(LOG, "Failed to set password for %s", user)
raise
def lock_passwd(self, name):
try:
- subp.subp(['pw', 'usermod', name, '-h', '-'])
+ subp.subp(["pw", "usermod", name, "-h", "-"])
except Exception:
util.logexc(LOG, "Failed to lock user %s", name)
raise
@@ -120,8 +129,9 @@ class Distro(cloudinit.distros.bsd.BSD):
# Adjust the locales value to the new value
newconf = StringIO()
for line in util.load_file(self.login_conf_fn).splitlines():
- newconf.write(re.sub(r'^default:',
- r'default:lang=%s:' % locale, line))
+ newconf.write(
+ re.sub(r"^default:", r"default:lang=%s:" % locale, line)
+ )
newconf.write("\n")
# Make a backup of login.conf.
@@ -132,15 +142,16 @@ class Distro(cloudinit.distros.bsd.BSD):
try:
LOG.debug("Running cap_mkdb for %s", locale)
- subp.subp(['cap_mkdb', self.login_conf_fn])
+ subp.subp(["cap_mkdb", self.login_conf_fn])
except subp.ProcessExecutionError:
# cap_mkdb failed, so restore the backup.
util.logexc(LOG, "Failed to apply locale %s", locale)
try:
util.copy(self.login_conf_fn_bak, self.login_conf_fn)
except IOError:
- util.logexc(LOG, "Failed to restore %s backup",
- self.login_conf_fn)
+ util.logexc(
+ LOG, "Failed to restore %s backup", self.login_conf_fn
+ )
def apply_network_config_names(self, netconfig):
# This is handled by the freebsd network renderer. It writes in
@@ -152,12 +163,16 @@ class Distro(cloudinit.distros.bsd.BSD):
def _get_pkg_cmd_environ(self):
"""Return environment vars used in *BSD package_command operations"""
e = os.environ.copy()
- e['ASSUME_ALWAYS_YES'] = 'YES'
+ e["ASSUME_ALWAYS_YES"] = "YES"
return e
def update_package_sources(self):
self._runner.run(
- "update-sources", self.package_command,
- ["update"], freq=PER_INSTANCE)
+ "update-sources",
+ self.package_command,
+ ["update"],
+ freq=PER_INSTANCE,
+ )
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/gentoo.py b/cloudinit/distros/gentoo.py
index e9b82602..4eb76da8 100644
--- a/cloudinit/distros/gentoo.py
+++ b/cloudinit/distros/gentoo.py
@@ -6,26 +6,27 @@
#
# This file is part of cloud-init. See LICENSE file for license information.
-from cloudinit import distros
-from cloudinit import helpers
+from cloudinit import distros, helpers
from cloudinit import log as logging
-from cloudinit import subp
-from cloudinit import util
-
+from cloudinit import subp, util
from cloudinit.distros import net_util
from cloudinit.distros.parsers.hostname import HostnameConf
-
from cloudinit.settings import PER_INSTANCE
LOG = logging.getLogger(__name__)
class Distro(distros.Distro):
- locale_conf_fn = '/etc/locale.gen'
- network_conf_fn = '/etc/conf.d/net'
- resolve_conf_fn = '/etc/resolv.conf'
- hostname_conf_fn = '/etc/conf.d/hostname'
- init_cmd = ['rc-service'] # init scripts
+ locale_conf_fn = "/etc/env.d/02locale"
+ locale_gen_fn = "/etc/locale.gen"
+ network_conf_fn = "/etc/conf.d/net"
+ hostname_conf_fn = "/etc/conf.d/hostname"
+ init_cmd = ["rc-service"] # init scripts
+ default_locale = "en_US.UTF-8"
+
+ # C.UTF8 makes sense to generate, but is not selected
+ # Add /etc/locale.gen entries to this list to support more locales
+ locales = ["C.UTF8 UTF-8", "en_US.UTF-8 UTF-8"]
def __init__(self, name, cfg, paths):
distros.Distro.__init__(self, name, cfg, paths)
@@ -33,97 +34,121 @@ class Distro(distros.Distro):
# calls from repeatly happening (when they
# should only happen say once per instance...)
self._runner = helpers.Runners(paths)
- self.osfamily = 'gentoo'
+ self.osfamily = "gentoo"
# Fix sshd restarts
- cfg['ssh_svcname'] = '/etc/init.d/sshd'
-
- def apply_locale(self, locale, out_fn=None):
- if not out_fn:
- out_fn = self.locale_conf_fn
- subp.subp(['locale-gen', '-G', locale], capture=False)
- # "" provides trailing newline during join
- lines = [
- util.make_header(),
- 'LANG="%s"' % locale,
- "",
- ]
- util.write_file(out_fn, "\n".join(lines))
+ cfg["ssh_svcname"] = "/etc/init.d/sshd"
+ if distros.uses_systemd():
+ LOG.error("Cloud-init does not support systemd with gentoo")
+
+ def apply_locale(self, _, out_fn=None):
+ """rc-only - not compatible with systemd
+
+ Locales need to be added to /etc/locale.gen and generated prior
+ to selection. Default to en_US.UTF-8 for simplicity.
+ """
+ util.write_file(self.locale_gen_fn, "\n".join(self.locales), mode=644)
+
+ # generate locales
+ subp.subp(["locale-gen"], capture=False)
+
+ # select locale
+ subp.subp(
+ ["eselect", "locale", "set", self.default_locale], capture=False
+ )
def install_packages(self, pkglist):
self.update_package_sources()
- self.package_command('', pkgs=pkglist)
+ self.package_command("", pkgs=pkglist)
def _write_network(self, settings):
entries = net_util.translate_network(settings)
- LOG.debug("Translated ubuntu style network settings %s into %s",
- settings, entries)
+ LOG.debug(
+ "Translated ubuntu style network settings %s into %s",
+ settings,
+ entries,
+ )
dev_names = entries.keys()
nameservers = []
for (dev, info) in entries.items():
- if 'dns-nameservers' in info:
- nameservers.extend(info['dns-nameservers'])
- if dev == 'lo':
+ if "dns-nameservers" in info:
+ nameservers.extend(info["dns-nameservers"])
+ if dev == "lo":
continue
- net_fn = self.network_conf_fn + '.' + dev
- dns_nameservers = info.get('dns-nameservers')
+ net_fn = self.network_conf_fn + "." + dev
+ dns_nameservers = info.get("dns-nameservers")
if isinstance(dns_nameservers, (list, tuple)):
- dns_nameservers = str(tuple(dns_nameservers)).replace(',', '')
+ dns_nameservers = str(tuple(dns_nameservers)).replace(",", "")
# eth0, {'auto': True, 'ipv6': {}, 'bootproto': 'dhcp'}
# lo, {'dns-nameservers': ['10.0.1.3'], 'ipv6': {}, 'auto': True}
- results = ''
- if info.get('bootproto') == 'dhcp':
+ results = ""
+ if info.get("bootproto") == "dhcp":
results += 'config_{name}="dhcp"'.format(name=dev)
else:
results += (
'config_{name}="{ip_address} netmask {netmask}"\n'
'mac_{name}="{hwaddr}"\n'
- ).format(name=dev, ip_address=info.get('address'),
- netmask=info.get('netmask'),
- hwaddr=info.get('hwaddress'))
- results += 'routes_{name}="default via {gateway}"\n'.format(
+ ).format(
name=dev,
- gateway=info.get('gateway')
+ ip_address=info.get("address"),
+ netmask=info.get("netmask"),
+ hwaddr=info.get("hwaddress"),
)
- if info.get('dns-nameservers'):
+ results += 'routes_{name}="default via {gateway}"\n'.format(
+ name=dev, gateway=info.get("gateway")
+ )
+ if info.get("dns-nameservers"):
results += 'dns_servers_{name}="{dnsservers}"\n'.format(
- name=dev,
- dnsservers=dns_nameservers)
+ name=dev, dnsservers=dns_nameservers
+ )
util.write_file(net_fn, results)
self._create_network_symlink(dev)
- if info.get('auto'):
- cmd = ['rc-update', 'add', 'net.{name}'.format(name=dev),
- 'default']
+ if info.get("auto"):
+ cmd = [
+ "rc-update",
+ "add",
+ "net.{name}".format(name=dev),
+ "default",
+ ]
try:
(_out, err) = subp.subp(cmd)
if len(err):
- LOG.warning("Running %s resulted in stderr output: %s",
- cmd, err)
+ LOG.warning(
+ "Running %s resulted in stderr output: %s",
+ cmd,
+ err,
+ )
except subp.ProcessExecutionError:
- util.logexc(LOG, "Running interface command %s failed",
- cmd)
+ util.logexc(
+ LOG, "Running interface command %s failed", cmd
+ )
if nameservers:
- util.write_file(self.resolve_conf_fn,
- convert_resolv_conf(nameservers))
+ util.write_file(
+ self.resolve_conf_fn, convert_resolv_conf(nameservers)
+ )
return dev_names
@staticmethod
def _create_network_symlink(interface_name):
- file_path = '/etc/init.d/net.{name}'.format(name=interface_name)
+ file_path = "/etc/init.d/net.{name}".format(name=interface_name)
if not util.is_link(file_path):
- util.sym_link('/etc/init.d/net.lo', file_path)
+ util.sym_link("/etc/init.d/net.lo", file_path)
def _bring_up_interface(self, device_name):
- cmd = ['/etc/init.d/net.%s' % device_name, 'restart']
- LOG.debug("Attempting to run bring up interface %s using command %s",
- device_name, cmd)
+ cmd = ["/etc/init.d/net.%s" % device_name, "restart"]
+ LOG.debug(
+ "Attempting to run bring up interface %s using command %s",
+ device_name,
+ cmd,
+ )
try:
(_out, err) = subp.subp(cmd)
if len(err):
- LOG.warning("Running %s resulted in stderr output: %s",
- cmd, err)
+ LOG.warning(
+ "Running %s resulted in stderr output: %s", cmd, err
+ )
return True
except subp.ProcessExecutionError:
util.logexc(LOG, "Running interface command %s failed", cmd)
@@ -132,40 +157,41 @@ class Distro(distros.Distro):
def _bring_up_interfaces(self, device_names):
use_all = False
for d in device_names:
- if d == 'all':
+ if d == "all":
use_all = True
if use_all:
# Grab device names from init scripts
- cmd = ['ls', '/etc/init.d/net.*']
+ cmd = ["ls", "/etc/init.d/net.*"]
try:
(_out, err) = subp.subp(cmd)
if len(err):
- LOG.warning("Running %s resulted in stderr output: %s",
- cmd, err)
+ LOG.warning(
+ "Running %s resulted in stderr output: %s", cmd, err
+ )
except subp.ProcessExecutionError:
util.logexc(LOG, "Running interface command %s failed", cmd)
return False
- devices = [x.split('.')[2] for x in _out.split(' ')]
+ devices = [x.split(".")[2] for x in _out.split(" ")]
return distros.Distro._bring_up_interfaces(self, devices)
else:
return distros.Distro._bring_up_interfaces(self, device_names)
- def _write_hostname(self, your_hostname, out_fn):
+ def _write_hostname(self, hostname, filename):
conf = None
try:
# Try to update the previous one
# so lets see if we can read it first.
- conf = self._read_hostname_conf(out_fn)
+ conf = self._read_hostname_conf(filename)
except IOError:
pass
if not conf:
- conf = HostnameConf('')
+ conf = HostnameConf("")
# Many distro's format is the hostname by itself, and that is the
# way HostnameConf works but gentoo expects it to be in
# hostname="the-actual-hostname"
- conf.set_hostname('hostname="%s"' % your_hostname)
- util.write_file(out_fn, str(conf), 0o644)
+ conf.set_hostname('hostname="%s"' % hostname)
+ util.write_file(filename, str(conf), 0o644)
def _read_system_hostname(self):
sys_hostname = self._read_hostname(self.hostname_conf_fn)
@@ -195,7 +221,7 @@ class Distro(distros.Distro):
if pkgs is None:
pkgs = []
- cmd = list('emerge')
+ cmd = list("emerge")
# Redirect output
cmd.append("--quiet")
@@ -207,23 +233,28 @@ class Distro(distros.Distro):
if command:
cmd.append(command)
- pkglist = util.expand_package_list('%s-%s', pkgs)
+ pkglist = util.expand_package_list("%s-%s", pkgs)
cmd.extend(pkglist)
# Allow the output of this to flow outwards (ie not be captured)
subp.subp(cmd, capture=False)
def update_package_sources(self):
- self._runner.run("update-sources", self.package_command,
- ["-u", "world"], freq=PER_INSTANCE)
+ self._runner.run(
+ "update-sources",
+ self.package_command,
+ ["-u", "world"],
+ freq=PER_INSTANCE,
+ )
def convert_resolv_conf(settings):
"""Returns a settings string formatted for resolv.conf."""
- result = ''
+ result = ""
if isinstance(settings, list):
for ns in settings:
- result += 'nameserver %s\n' % ns
+ result += "nameserver %s\n" % ns
return result
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/miraclelinux.py b/cloudinit/distros/miraclelinux.py
new file mode 100644
index 00000000..3dc0a342
--- /dev/null
+++ b/cloudinit/distros/miraclelinux.py
@@ -0,0 +1,10 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+from cloudinit.distros import rhel
+
+
+class Distro(rhel.Distro):
+ pass
+
+
+# vi: ts=4 expandtab
diff --git a/cloudinit/distros/net_util.py b/cloudinit/distros/net_util.py
index edfcd99d..e37fb19b 100644
--- a/cloudinit/distros/net_util.py
+++ b/cloudinit/distros/net_util.py
@@ -68,7 +68,9 @@
# }
from cloudinit.net.network_state import (
- net_prefix_to_ipv4_mask, mask_and_ipv4_to_bcast_addr)
+ mask_and_ipv4_to_bcast_addr,
+ net_prefix_to_ipv4_mask,
+)
def translate_network(settings):
@@ -86,7 +88,7 @@ def translate_network(settings):
ifaces = []
consume = {}
for (cmd, args) in entries:
- if cmd == 'iface':
+ if cmd == "iface":
if consume:
ifaces.append(consume)
consume = {}
@@ -96,19 +98,19 @@ def translate_network(settings):
# Check if anything left over to consume
absorb = False
for (cmd, args) in consume.items():
- if cmd == 'iface':
+ if cmd == "iface":
absorb = True
if absorb:
ifaces.append(consume)
# Now translate
real_ifaces = {}
for info in ifaces:
- if 'iface' not in info:
+ if "iface" not in info:
continue
- iface_details = info['iface'].split(None)
+ iface_details = info["iface"].split(None)
# Check if current device *may* have an ipv6 IP
use_ipv6 = False
- if 'inet6' in iface_details:
+ if "inet6" in iface_details:
use_ipv6 = True
dev_name = None
if len(iface_details) >= 1:
@@ -118,55 +120,54 @@ def translate_network(settings):
if not dev_name:
continue
iface_info = {}
- iface_info['ipv6'] = {}
+ iface_info["ipv6"] = {}
if len(iface_details) >= 3:
proto_type = iface_details[2].strip().lower()
# Seems like this can be 'loopback' which we don't
# really care about
- if proto_type in ['dhcp', 'static']:
- iface_info['bootproto'] = proto_type
+ if proto_type in ["dhcp", "static"]:
+ iface_info["bootproto"] = proto_type
# These can just be copied over
if use_ipv6:
- for k in ['address', 'gateway']:
+ for k in ["address", "gateway"]:
if k in info:
val = info[k].strip().lower()
if val:
- iface_info['ipv6'][k] = val
+ iface_info["ipv6"][k] = val
else:
- for k in ['netmask', 'address', 'gateway', 'broadcast']:
+ for k in ["netmask", "address", "gateway", "broadcast"]:
if k in info:
val = info[k].strip().lower()
if val:
iface_info[k] = val
# handle static ip configurations using
# ipaddress/prefix-length format
- if 'address' in iface_info:
- if 'netmask' not in iface_info:
+ if "address" in iface_info:
+ if "netmask" not in iface_info:
# check if the address has a network prefix
- addr, _, prefix = iface_info['address'].partition('/')
+ addr, _, prefix = iface_info["address"].partition("/")
if prefix:
- iface_info['netmask'] = (
- net_prefix_to_ipv4_mask(prefix))
- iface_info['address'] = addr
+ iface_info["netmask"] = net_prefix_to_ipv4_mask(prefix)
+ iface_info["address"] = addr
# if we set the netmask, we also can set the broadcast
- iface_info['broadcast'] = (
- mask_and_ipv4_to_bcast_addr(
- iface_info['netmask'], addr))
+ iface_info["broadcast"] = mask_and_ipv4_to_bcast_addr(
+ iface_info["netmask"], addr
+ )
# Name server info provided??
- if 'dns-nameservers' in info:
- iface_info['dns-nameservers'] = info['dns-nameservers'].split()
+ if "dns-nameservers" in info:
+ iface_info["dns-nameservers"] = info["dns-nameservers"].split()
# Name server search info provided??
- if 'dns-search' in info:
- iface_info['dns-search'] = info['dns-search'].split()
+ if "dns-search" in info:
+ iface_info["dns-search"] = info["dns-search"].split()
# Is any mac address spoofing going on??
- if 'hwaddress' in info:
- hw_info = info['hwaddress'].lower().strip()
+ if "hwaddress" in info:
+ hw_info = info["hwaddress"].lower().strip()
hw_split = hw_info.split(None, 1)
- if len(hw_split) == 2 and hw_split[0].startswith('ether'):
+ if len(hw_split) == 2 and hw_split[0].startswith("ether"):
hw_addr = hw_split[1]
if hw_addr:
- iface_info['hwaddress'] = hw_addr
+ iface_info["hwaddress"] = hw_addr
# If ipv6 is enabled, device will have multiple IPs, so we need to
# update the dictionary instead of overwriting it...
if dev_name in real_ifaces:
@@ -179,13 +180,14 @@ def translate_network(settings):
if not args:
continue
dev_name = args[0].strip().lower()
- if cmd == 'auto':
+ if cmd == "auto":
# Seems like auto can be like 'auto eth0 eth0:1' so just get the
# first part out as the device name
if dev_name in real_ifaces:
- real_ifaces[dev_name]['auto'] = True
- if cmd == 'iface' and 'inet6' in args:
- real_ifaces[dev_name]['inet6'] = True
+ real_ifaces[dev_name]["auto"] = True
+ if cmd == "iface" and "inet6" in args:
+ real_ifaces[dev_name]["inet6"] = True
return real_ifaces
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/netbsd.py b/cloudinit/distros/netbsd.py
index f1a9b182..9c38ae51 100644
--- a/cloudinit/distros/netbsd.py
+++ b/cloudinit/distros/netbsd.py
@@ -8,8 +8,7 @@ import platform
import cloudinit.distros.bsd
from cloudinit import log as logging
-from cloudinit import subp
-from cloudinit import util
+from cloudinit import subp, util
LOG = logging.getLogger(__name__)
@@ -21,42 +20,42 @@ class NetBSD(cloudinit.distros.bsd.BSD):
(N.B. OpenBSD inherits from this class.)
"""
- ci_sudoers_fn = '/usr/pkg/etc/sudoers.d/90-cloud-init-users'
+ ci_sudoers_fn = "/usr/pkg/etc/sudoers.d/90-cloud-init-users"
group_add_cmd_prefix = ["groupadd"]
def __init__(self, name, cfg, paths):
super().__init__(name, cfg, paths)
if os.path.exists("/usr/pkg/bin/pkgin"):
- self.pkg_cmd_install_prefix = ['pkgin', '-y', 'install']
- self.pkg_cmd_remove_prefix = ['pkgin', '-y', 'remove']
- self.pkg_cmd_update_prefix = ['pkgin', '-y', 'update']
- self.pkg_cmd_upgrade_prefix = ['pkgin', '-y', 'full-upgrade']
+ self.pkg_cmd_install_prefix = ["pkgin", "-y", "install"]
+ self.pkg_cmd_remove_prefix = ["pkgin", "-y", "remove"]
+ self.pkg_cmd_update_prefix = ["pkgin", "-y", "update"]
+ self.pkg_cmd_upgrade_prefix = ["pkgin", "-y", "full-upgrade"]
else:
- self.pkg_cmd_install_prefix = ['pkg_add', '-U']
- self.pkg_cmd_remove_prefix = ['pkg_delete']
+ self.pkg_cmd_install_prefix = ["pkg_add", "-U"]
+ self.pkg_cmd_remove_prefix = ["pkg_delete"]
def _get_add_member_to_group_cmd(self, member_name, group_name):
- return ['usermod', '-G', group_name, member_name]
+ return ["usermod", "-G", group_name, member_name]
def add_user(self, name, **kwargs):
if util.is_user(name):
LOG.info("User %s already exists, skipping.", name)
return False
- adduser_cmd = ['useradd']
- log_adduser_cmd = ['useradd']
+ adduser_cmd = ["useradd"]
+ log_adduser_cmd = ["useradd"]
adduser_opts = {
- "homedir": '-d',
- "gecos": '-c',
- "primary_group": '-g',
- "groups": '-G',
- "shell": '-s',
+ "homedir": "-d",
+ "gecos": "-c",
+ "primary_group": "-g",
+ "groups": "-G",
+ "shell": "-s",
}
adduser_flags = {
- "no_user_group": '--no-user-group',
- "system": '--system',
- "no_log_init": '--no-log-init',
+ "no_user_group": "--no-user-group",
+ "system": "--system",
+ "no_log_init": "--no-log-init",
}
for key, val in kwargs.items():
@@ -67,9 +66,9 @@ class NetBSD(cloudinit.distros.bsd.BSD):
adduser_cmd.append(adduser_flags[key])
log_adduser_cmd.append(adduser_flags[key])
- if 'no_create_home' not in kwargs or 'system' not in kwargs:
- adduser_cmd += ['-m']
- log_adduser_cmd += ['-m']
+ if "no_create_home" not in kwargs or "system" not in kwargs:
+ adduser_cmd += ["-m"]
+ log_adduser_cmd += ["-m"]
adduser_cmd += [name]
log_adduser_cmd += [name]
@@ -83,29 +82,28 @@ class NetBSD(cloudinit.distros.bsd.BSD):
raise
# Set the password if it is provided
# For security consideration, only hashed passwd is assumed
- passwd_val = kwargs.get('passwd', None)
+ passwd_val = kwargs.get("passwd", None)
if passwd_val is not None:
self.set_passwd(name, passwd_val, hashed=True)
def set_passwd(self, user, passwd, hashed=False):
if hashed:
hashed_pw = passwd
- elif not hasattr(crypt, 'METHOD_BLOWFISH'):
+ elif not hasattr(crypt, "METHOD_BLOWFISH"):
# crypt.METHOD_BLOWFISH comes with Python 3.7 which is available
# on NetBSD 7 and 8.
- LOG.error((
- 'Cannot set non-encrypted password for user %s. '
- 'Python >= 3.7 is required.'), user)
+ LOG.error(
+ "Cannot set non-encrypted password for user %s. "
+ "Python >= 3.7 is required.",
+ user,
+ )
return
else:
method = crypt.METHOD_BLOWFISH # pylint: disable=E1101
- hashed_pw = crypt.crypt(
- passwd,
- crypt.mksalt(method)
- )
+ hashed_pw = crypt.crypt(passwd, crypt.mksalt(method))
try:
- subp.subp(['usermod', '-p', hashed_pw, user])
+ subp.subp(["usermod", "-p", hashed_pw, user])
except Exception:
util.logexc(LOG, "Failed to set password for %s", user)
raise
@@ -113,40 +111,42 @@ class NetBSD(cloudinit.distros.bsd.BSD):
def force_passwd_change(self, user):
try:
- subp.subp(['usermod', '-F', user])
+ subp.subp(["usermod", "-F", user])
except Exception:
util.logexc(LOG, "Failed to set pw expiration for %s", user)
raise
def lock_passwd(self, name):
try:
- subp.subp(['usermod', '-C', 'yes', name])
+ subp.subp(["usermod", "-C", "yes", name])
except Exception:
util.logexc(LOG, "Failed to lock user %s", name)
raise
def unlock_passwd(self, name):
try:
- subp.subp(['usermod', '-C', 'no', name])
+ subp.subp(["usermod", "-C", "no", name])
except Exception:
util.logexc(LOG, "Failed to unlock user %s", name)
raise
def apply_locale(self, locale, out_fn=None):
- LOG.debug('Cannot set the locale.')
+ LOG.debug("Cannot set the locale.")
def apply_network_config_names(self, netconfig):
- LOG.debug('NetBSD cannot rename network interface.')
+ LOG.debug("NetBSD cannot rename network interface.")
def _get_pkg_cmd_environ(self):
"""Return env vars used in NetBSD package_command operations"""
os_release = platform.release()
os_arch = platform.machine()
e = os.environ.copy()
- e['PKG_PATH'] = (
- 'http://cdn.netbsd.org/pub/pkgsrc/'
- 'packages/NetBSD/%s/%s/All'
- ) % (os_arch, os_release)
+ e[
+ "PKG_PATH"
+ ] = "http://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/%s/%s/All" % (
+ os_arch,
+ os_release,
+ )
return e
def update_package_sources(self):
@@ -156,4 +156,5 @@ class NetBSD(cloudinit.distros.bsd.BSD):
class Distro(NetBSD):
pass
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/networking.py b/cloudinit/distros/networking.py
index c291196a..b24b6233 100644
--- a/cloudinit/distros/networking.py
+++ b/cloudinit/distros/networking.py
@@ -1,10 +1,9 @@
import abc
import logging
import os
+from typing import List, Optional
-from cloudinit import subp
-from cloudinit import net, util
-
+from cloudinit import net, subp, util
LOG = logging.getLogger(__name__)
@@ -24,7 +23,7 @@ class Networking(metaclass=abc.ABCMeta):
"""
def __init__(self):
- self.blacklist_drivers = None
+ self.blacklist_drivers: Optional[List[str]] = None
def _get_current_rename_info(self) -> dict:
return net._get_current_rename_info()
@@ -73,7 +72,8 @@ class Networking(metaclass=abc.ABCMeta):
def get_interfaces_by_mac(self) -> dict:
return net.get_interfaces_by_mac(
- blacklist_drivers=self.blacklist_drivers)
+ blacklist_drivers=self.blacklist_drivers
+ )
def get_master(self, devname: DeviceName):
return net.get_master(devname)
@@ -225,7 +225,7 @@ class LinuxNetworking(Networking):
def try_set_link_up(self, devname: DeviceName) -> bool:
"""Try setting the link to up explicitly and return if it is up.
- Not guaranteed to bring the interface up. The caller is expected to
- add wait times before retrying."""
- subp.subp(['ip', 'link', 'set', devname, 'up'])
+ Not guaranteed to bring the interface up. The caller is expected to
+ add wait times before retrying."""
+ subp.subp(["ip", "link", "set", devname, "up"])
return self.is_up(devname)
diff --git a/cloudinit/distros/openEuler.py b/cloudinit/distros/openEuler.py
new file mode 100644
index 00000000..3dc0a342
--- /dev/null
+++ b/cloudinit/distros/openEuler.py
@@ -0,0 +1,10 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+from cloudinit.distros import rhel
+
+
+class Distro(rhel.Distro):
+ pass
+
+
+# vi: ts=4 expandtab
diff --git a/cloudinit/distros/openbsd.py b/cloudinit/distros/openbsd.py
index 720c9cf3..ccdb8799 100644
--- a/cloudinit/distros/openbsd.py
+++ b/cloudinit/distros/openbsd.py
@@ -7,28 +7,27 @@ import platform
import cloudinit.distros.netbsd
from cloudinit import log as logging
-from cloudinit import subp
-from cloudinit import util
+from cloudinit import subp, util
LOG = logging.getLogger(__name__)
class Distro(cloudinit.distros.netbsd.NetBSD):
- hostname_conf_fn = '/etc/myname'
+ hostname_conf_fn = "/etc/myname"
def _read_hostname(self, filename, default=None):
return util.load_file(self.hostname_conf_fn)
def _write_hostname(self, hostname, filename):
- content = hostname + '\n'
+ content = hostname + "\n"
util.write_file(self.hostname_conf_fn, content)
def _get_add_member_to_group_cmd(self, member_name, group_name):
- return ['usermod', '-G', group_name, member_name]
+ return ["usermod", "-G", group_name, member_name]
def lock_passwd(self, name):
try:
- subp.subp(['usermod', '-p', '*', name])
+ subp.subp(["usermod", "-p", "*", name])
except Exception:
util.logexc(LOG, "Failed to lock user %s", name)
raise
@@ -41,11 +40,10 @@ class Distro(cloudinit.distros.netbsd.NetBSD):
os_release = platform.release()
os_arch = platform.machine()
e = os.environ.copy()
- e['PKG_PATH'] = (
- 'ftp://ftp.openbsd.org/pub/OpenBSD/{os_release}/'
- 'packages/{os_arch}/').format(
- os_arch=os_arch, os_release=os_release
- )
+ e["PKG_PATH"] = (
+ "ftp://ftp.openbsd.org/pub/OpenBSD/{os_release}/"
+ "packages/{os_arch}/"
+ ).format(os_arch=os_arch, os_release=os_release)
return e
diff --git a/cloudinit/distros/opensuse.py b/cloudinit/distros/opensuse.py
index 7ca0ef99..00ed1514 100644
--- a/cloudinit/distros/opensuse.py
+++ b/cloudinit/distros/opensuse.py
@@ -8,69 +8,61 @@
#
# This file is part of cloud-init. See LICENSE file for license information.
-from cloudinit import distros
-
-from cloudinit.distros.parsers.hostname import HostnameConf
-
-from cloudinit import helpers
-from cloudinit import subp
-from cloudinit import util
-
+from cloudinit import distros, helpers, subp, util
from cloudinit.distros import rhel_util as rhutil
+from cloudinit.distros.parsers.hostname import HostnameConf
from cloudinit.settings import PER_INSTANCE
class Distro(distros.Distro):
- clock_conf_fn = '/etc/sysconfig/clock'
- hostname_conf_fn = '/etc/HOSTNAME'
- init_cmd = ['service']
- locale_conf_fn = '/etc/sysconfig/language'
- network_conf_fn = '/etc/sysconfig/network/config'
- network_script_tpl = '/etc/sysconfig/network/ifcfg-%s'
- resolve_conf_fn = '/etc/resolv.conf'
- route_conf_tpl = '/etc/sysconfig/network/ifroute-%s'
- systemd_hostname_conf_fn = '/etc/hostname'
- systemd_locale_conf_fn = '/etc/locale.conf'
- tz_local_fn = '/etc/localtime'
+ clock_conf_fn = "/etc/sysconfig/clock"
+ hostname_conf_fn = "/etc/HOSTNAME"
+ init_cmd = ["service"]
+ locale_conf_fn = "/etc/sysconfig/language"
+ network_conf_fn = "/etc/sysconfig/network/config"
+ network_script_tpl = "/etc/sysconfig/network/ifcfg-%s"
+ route_conf_tpl = "/etc/sysconfig/network/ifroute-%s"
+ systemd_hostname_conf_fn = "/etc/hostname"
+ systemd_locale_conf_fn = "/etc/locale.conf"
+ tz_local_fn = "/etc/localtime"
renderer_configs = {
- 'sysconfig': {
- 'control': 'etc/sysconfig/network/config',
- 'flavor': 'suse',
- 'iface_templates': '%(base)s/network/ifcfg-%(name)s',
- 'netrules_path': (
- 'etc/udev/rules.d/85-persistent-net-cloud-init.rules'),
- 'route_templates': {
- 'ipv4': '%(base)s/network/ifroute-%(name)s',
- 'ipv6': '%(base)s/network/ifroute-%(name)s',
- }
+ "sysconfig": {
+ "control": "etc/sysconfig/network/config",
+ "flavor": "suse",
+ "iface_templates": "%(base)s/network/ifcfg-%(name)s",
+ "netrules_path": (
+ "etc/udev/rules.d/85-persistent-net-cloud-init.rules"
+ ),
+ "route_templates": {
+ "ipv4": "%(base)s/network/ifroute-%(name)s",
+ "ipv6": "%(base)s/network/ifroute-%(name)s",
+ },
}
}
def __init__(self, name, cfg, paths):
distros.Distro.__init__(self, name, cfg, paths)
self._runner = helpers.Runners(paths)
- self.osfamily = 'suse'
- cfg['ssh_svcname'] = 'sshd'
+ self.osfamily = "suse"
+ cfg["ssh_svcname"] = "sshd"
if self.uses_systemd():
- self.init_cmd = ['systemctl']
- cfg['ssh_svcname'] = 'sshd.service'
+ self.init_cmd = ["systemctl"]
+ cfg["ssh_svcname"] = "sshd.service"
def apply_locale(self, locale, out_fn=None):
if self.uses_systemd():
if not out_fn:
out_fn = self.systemd_locale_conf_fn
- locale_cfg = {'LANG': locale}
+ locale_cfg = {"LANG": locale}
else:
if not out_fn:
out_fn = self.locale_conf_fn
- locale_cfg = {'RC_LANG': locale}
+ locale_cfg = {"RC_LANG": locale}
rhutil.update_sysconfig_file(out_fn, locale_cfg)
def install_packages(self, pkglist):
self.package_command(
- 'install',
- args='--auto-agree-with-licenses',
- pkgs=pkglist
+ "install", args="--auto-agree-with-licenses", pkgs=pkglist
)
def package_command(self, command, args=None, pkgs=None):
@@ -78,11 +70,11 @@ class Distro(distros.Distro):
pkgs = []
# No user interaction possible, enable non-interactive mode
- cmd = ['zypper', '--non-interactive']
+ cmd = ["zypper", "--non-interactive"]
# Command is the operation, such as install
- if command == 'upgrade':
- command = 'update'
+ if command == "upgrade":
+ command = "update"
cmd.append(command)
# args are the arguments to the command, not global options
@@ -91,7 +83,7 @@ class Distro(distros.Distro):
elif args and isinstance(args, list):
cmd.extend(args)
- pkglist = util.expand_package_list('%s-%s', pkgs)
+ pkglist = util.expand_package_list("%s-%s", pkgs)
cmd.extend(pkglist)
# Allow the output of this to flow outwards (ie not be captured)
@@ -107,27 +99,25 @@ class Distro(distros.Distro):
else:
# Adjust the sysconfig clock zone setting
clock_cfg = {
- 'TIMEZONE': str(tz),
+ "TIMEZONE": str(tz),
}
rhutil.update_sysconfig_file(self.clock_conf_fn, clock_cfg)
# This ensures that the correct tz will be used for the system
util.copy(tz_file, self.tz_local_fn)
def update_package_sources(self):
- self._runner.run("update-sources", self.package_command,
- ['refresh'], freq=PER_INSTANCE)
-
- def _bring_up_interfaces(self, device_names):
- if device_names and 'all' in device_names:
- raise RuntimeError(('Distro %s can not translate '
- 'the device name "all"') % (self.name))
- return distros.Distro._bring_up_interfaces(self, device_names)
+ self._runner.run(
+ "update-sources",
+ self.package_command,
+ ["refresh"],
+ freq=PER_INSTANCE,
+ )
def _read_hostname(self, filename, default=None):
- if self.uses_systemd() and filename.endswith('/previous-hostname'):
+ if self.uses_systemd() and filename.endswith("/previous-hostname"):
return util.load_file(filename).strip()
elif self.uses_systemd():
- (out, _err) = subp.subp(['hostname'])
+ (out, _err) = subp.subp(["hostname"])
if len(out):
return out
else:
@@ -157,26 +147,23 @@ class Distro(distros.Distro):
host_fn = self.hostname_conf_fn
return (host_fn, self._read_hostname(host_fn))
- def _write_hostname(self, hostname, out_fn):
- if self.uses_systemd() and out_fn.endswith('/previous-hostname'):
- util.write_file(out_fn, hostname)
+ def _write_hostname(self, hostname, filename):
+ if self.uses_systemd() and filename.endswith("/previous-hostname"):
+ util.write_file(filename, hostname)
elif self.uses_systemd():
- subp.subp(['hostnamectl', 'set-hostname', str(hostname)])
+ subp.subp(["hostnamectl", "set-hostname", str(hostname)])
else:
conf = None
try:
# Try to update the previous one
# so lets see if we can read it first.
- conf = self._read_hostname_conf(out_fn)
+ conf = self._read_hostname_conf(filename)
except IOError:
pass
if not conf:
- conf = HostnameConf('')
+ conf = HostnameConf("")
conf.set_hostname(hostname)
- util.write_file(out_fn, str(conf), 0o644)
-
- def _write_network_config(self, netconfig):
- return self._supported_write_network_config(netconfig)
+ util.write_file(filename, str(conf), 0o644)
@property
def preferred_ntp_clients(self):
@@ -184,22 +171,28 @@ class Distro(distros.Distro):
# Allow distro to determine the preferred ntp client list
if not self._preferred_ntp_clients:
- distro_info = util.system_info()['dist']
+ distro_info = util.system_info()["dist"]
name = distro_info[0]
- major_ver = int(distro_info[1].split('.')[0])
+ major_ver = int(distro_info[1].split(".")[0])
# This is horribly complicated because of a case of
# "we do not care if versions should be increasing syndrome"
- if (
- (major_ver >= 15 and 'openSUSE' not in name) or
- (major_ver >= 15 and 'openSUSE' in name and major_ver != 42)
+ if (major_ver >= 15 and "openSUSE" not in name) or (
+ major_ver >= 15 and "openSUSE" in name and major_ver != 42
):
- self._preferred_ntp_clients = ['chrony',
- 'systemd-timesyncd', 'ntp']
+ self._preferred_ntp_clients = [
+ "chrony",
+ "systemd-timesyncd",
+ "ntp",
+ ]
else:
- self._preferred_ntp_clients = ['ntp',
- 'systemd-timesyncd', 'chrony']
+ self._preferred_ntp_clients = [
+ "ntp",
+ "systemd-timesyncd",
+ "chrony",
+ ]
return self._preferred_ntp_clients
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/parsers/__init__.py b/cloudinit/distros/parsers/__init__.py
index 6b5b6dde..5bea2ae1 100644
--- a/cloudinit/distros/parsers/__init__.py
+++ b/cloudinit/distros/parsers/__init__.py
@@ -9,10 +9,11 @@ def chop_comment(text, comment_chars):
comment_locations = [text.find(c) for c in comment_chars]
comment_locations = [c for c in comment_locations if c != -1]
if not comment_locations:
- return (text, '')
+ return (text, "")
min_comment = min(comment_locations)
before_comment = text[0:min_comment]
comment = text[min_comment:]
return (before_comment, comment)
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/parsers/hostname.py b/cloudinit/distros/parsers/hostname.py
index e74c083c..61674082 100644
--- a/cloudinit/distros/parsers/hostname.py
+++ b/cloudinit/distros/parsers/hostname.py
@@ -23,11 +23,11 @@ class HostnameConf(object):
self.parse()
contents = StringIO()
for (line_type, components) in self._contents:
- if line_type == 'blank':
+ if line_type == "blank":
contents.write("%s\n" % (components[0]))
- elif line_type == 'all_comment':
+ elif line_type == "all_comment":
contents.write("%s\n" % (components[0]))
- elif line_type == 'hostname':
+ elif line_type == "hostname":
(hostname, tail) = components
contents.write("%s%s\n" % (hostname, tail))
# Ensure trailing newline
@@ -40,7 +40,7 @@ class HostnameConf(object):
def hostname(self):
self.parse()
for (line_type, components) in self._contents:
- if line_type == 'hostname':
+ if line_type == "hostname":
return components[0]
return None
@@ -51,28 +51,28 @@ class HostnameConf(object):
self.parse()
replaced = False
for (line_type, components) in self._contents:
- if line_type == 'hostname':
+ if line_type == "hostname":
components[0] = str(your_hostname)
replaced = True
if not replaced:
- self._contents.append(('hostname', [str(your_hostname), '']))
+ self._contents.append(("hostname", [str(your_hostname), ""]))
def _parse(self, contents):
entries = []
hostnames_found = set()
for line in contents.splitlines():
if not len(line.strip()):
- entries.append(('blank', [line]))
+ entries.append(("blank", [line]))
continue
- (head, tail) = chop_comment(line.strip(), '#')
+ (head, tail) = chop_comment(line.strip(), "#")
if not len(head):
- entries.append(('all_comment', [line]))
+ entries.append(("all_comment", [line]))
continue
- entries.append(('hostname', [head, tail]))
+ entries.append(("hostname", [head, tail]))
hostnames_found.add(head)
if len(hostnames_found) > 1:
- raise IOError("Multiple hostnames (%s) found!"
- % (hostnames_found))
+ raise IOError("Multiple hostnames (%s) found!" % (hostnames_found))
return entries
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/parsers/hosts.py b/cloudinit/distros/parsers/hosts.py
index 54e4e934..e43880af 100644
--- a/cloudinit/distros/parsers/hosts.py
+++ b/cloudinit/distros/parsers/hosts.py
@@ -25,7 +25,7 @@ class HostsConf(object):
self.parse()
options = []
for (line_type, components) in self._contents:
- if line_type == 'option':
+ if line_type == "option":
(pieces, _tail) = components
if len(pieces) and pieces[0] == ip:
options.append(pieces[1:])
@@ -35,7 +35,7 @@ class HostsConf(object):
self.parse()
n_entries = []
for (line_type, components) in self._contents:
- if line_type != 'option':
+ if line_type != "option":
n_entries.append((line_type, components))
continue
else:
@@ -48,35 +48,37 @@ class HostsConf(object):
def add_entry(self, ip, canonical_hostname, *aliases):
self.parse()
- self._contents.append(('option',
- ([ip, canonical_hostname] + list(aliases), '')))
+ self._contents.append(
+ ("option", ([ip, canonical_hostname] + list(aliases), ""))
+ )
def _parse(self, contents):
entries = []
for line in contents.splitlines():
if not len(line.strip()):
- entries.append(('blank', [line]))
+ entries.append(("blank", [line]))
continue
- (head, tail) = chop_comment(line.strip(), '#')
+ (head, tail) = chop_comment(line.strip(), "#")
if not len(head):
- entries.append(('all_comment', [line]))
+ entries.append(("all_comment", [line]))
continue
- entries.append(('option', [head.split(None), tail]))
+ entries.append(("option", [head.split(None), tail]))
return entries
def __str__(self):
self.parse()
contents = StringIO()
for (line_type, components) in self._contents:
- if line_type == 'blank':
+ if line_type == "blank":
contents.write("%s\n" % (components[0]))
- elif line_type == 'all_comment':
+ elif line_type == "all_comment":
contents.write("%s\n" % (components[0]))
- elif line_type == 'option':
+ elif line_type == "option":
(pieces, tail) = components
pieces = [str(p) for p in pieces]
pieces = "\t".join(pieces)
contents.write("%s%s\n" % (pieces, tail))
return contents.getvalue()
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/parsers/networkmanager_conf.py b/cloudinit/distros/parsers/networkmanager_conf.py
index ac51f122..4b669b0f 100644
--- a/cloudinit/distros/parsers/networkmanager_conf.py
+++ b/cloudinit/distros/parsers/networkmanager_conf.py
@@ -13,9 +13,9 @@ import configobj
class NetworkManagerConf(configobj.ConfigObj):
def __init__(self, contents):
- configobj.ConfigObj.__init__(self, contents,
- interpolation=False,
- write_empty_values=False)
+ configobj.ConfigObj.__init__(
+ self, contents, interpolation=False, write_empty_values=False
+ )
def set_section_keypair(self, section_name, key, value):
if section_name not in self.sections:
diff --git a/cloudinit/distros/parsers/resolv_conf.py b/cloudinit/distros/parsers/resolv_conf.py
index 62929d03..0ef4e147 100644
--- a/cloudinit/distros/parsers/resolv_conf.py
+++ b/cloudinit/distros/parsers/resolv_conf.py
@@ -6,9 +6,9 @@
from io import StringIO
-from cloudinit.distros.parsers import chop_comment
from cloudinit import log as logging
from cloudinit import util
+from cloudinit.distros.parsers import chop_comment
LOG = logging.getLogger(__name__)
@@ -26,12 +26,12 @@ class ResolvConf(object):
@property
def nameservers(self):
self.parse()
- return self._retr_option('nameserver')
+ return self._retr_option("nameserver")
@property
def local_domain(self):
self.parse()
- dm = self._retr_option('domain')
+ dm = self._retr_option("domain")
if dm:
return dm[0]
return None
@@ -39,7 +39,7 @@ class ResolvConf(object):
@property
def search_domains(self):
self.parse()
- current_sds = self._retr_option('search')
+ current_sds = self._retr_option("search")
flat_sds = []
for sdlist in current_sds:
for sd in sdlist.split(None):
@@ -51,11 +51,11 @@ class ResolvConf(object):
self.parse()
contents = StringIO()
for (line_type, components) in self._contents:
- if line_type == 'blank':
+ if line_type == "blank":
contents.write("\n")
- elif line_type == 'all_comment':
+ elif line_type == "all_comment":
contents.write("%s\n" % (components[0]))
- elif line_type == 'option':
+ elif line_type == "option":
(cfg_opt, cfg_value, comment_tail) = components
line = "%s %s" % (cfg_opt, cfg_value)
if len(comment_tail):
@@ -66,7 +66,7 @@ class ResolvConf(object):
def _retr_option(self, opt_name):
found = []
for (line_type, components) in self._contents:
- if line_type == 'option':
+ if line_type == "option":
(cfg_opt, cfg_value, _comment_tail) = components
if cfg_opt == opt_name:
found.append(cfg_value)
@@ -74,27 +74,29 @@ class ResolvConf(object):
def add_nameserver(self, ns):
self.parse()
- current_ns = self._retr_option('nameserver')
+ current_ns = self._retr_option("nameserver")
new_ns = list(current_ns)
new_ns.append(str(ns))
new_ns = util.uniq_list(new_ns)
if len(new_ns) == len(current_ns):
return current_ns
if len(current_ns) >= 3:
- LOG.warning("ignoring nameserver %r: adding would "
- "exceed the maximum of "
- "'3' name servers (see resolv.conf(5))", ns)
+ LOG.warning(
+ "ignoring nameserver %r: adding would "
+ "exceed the maximum of "
+ "'3' name servers (see resolv.conf(5))",
+ ns,
+ )
return current_ns[:3]
- self._remove_option('nameserver')
+ self._remove_option("nameserver")
for n in new_ns:
- self._contents.append(('option', ['nameserver', n, '']))
+ self._contents.append(("option", ["nameserver", n, ""]))
return new_ns
def _remove_option(self, opt_name):
-
def remove_opt(item):
line_type, components = item
- if line_type != 'option':
+ if line_type != "option":
return False
(cfg_opt, _cfg_value, _comment_tail) = components
if cfg_opt != opt_name:
@@ -116,23 +118,26 @@ class ResolvConf(object):
return new_sds
if len(flat_sds) >= 6:
# Hard restriction on only 6 search domains
- raise ValueError(("Adding %r would go beyond the "
- "'6' maximum search domains") % (search_domain))
+ raise ValueError(
+ "Adding %r would go beyond the '6' maximum search domains"
+ % (search_domain)
+ )
s_list = " ".join(new_sds)
if len(s_list) > 256:
# Some hard limit on 256 chars total
- raise ValueError(("Adding %r would go beyond the "
- "256 maximum search list character limit")
- % (search_domain))
- self._remove_option('search')
- self._contents.append(('option', ['search', s_list, '']))
+ raise ValueError(
+ "Adding %r would go beyond the "
+ "256 maximum search list character limit" % (search_domain)
+ )
+ self._remove_option("search")
+ self._contents.append(("option", ["search", s_list, ""]))
return flat_sds
@local_domain.setter
def local_domain(self, domain):
self.parse()
- self._remove_option('domain')
- self._contents.append(('option', ['domain', str(domain), '']))
+ self._remove_option("domain")
+ self._contents.append(("option", ["domain", str(domain), ""]))
return domain
def _parse(self, contents):
@@ -140,24 +145,30 @@ class ResolvConf(object):
for (i, line) in enumerate(contents.splitlines()):
sline = line.strip()
if not sline:
- entries.append(('blank', [line]))
+ entries.append(("blank", [line]))
continue
- (head, tail) = chop_comment(line, ';#')
+ (head, tail) = chop_comment(line, ";#")
if not len(head.strip()):
- entries.append(('all_comment', [line]))
+ entries.append(("all_comment", [line]))
continue
if not tail:
- tail = ''
+ tail = ""
try:
(cfg_opt, cfg_values) = head.split(None, 1)
except (IndexError, ValueError) as e:
raise IOError(
"Incorrectly formatted resolv.conf line %s" % (i + 1)
) from e
- if cfg_opt not in ['nameserver', 'domain',
- 'search', 'sortlist', 'options']:
+ if cfg_opt not in [
+ "nameserver",
+ "domain",
+ "search",
+ "sortlist",
+ "options",
+ ]:
raise IOError("Unexpected resolv.conf option %s" % (cfg_opt))
entries.append(("option", [cfg_opt, cfg_values, tail]))
return entries
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/parsers/sys_conf.py b/cloudinit/distros/parsers/sys_conf.py
index dee4c551..4132734c 100644
--- a/cloudinit/distros/parsers/sys_conf.py
+++ b/cloudinit/distros/parsers/sys_conf.py
@@ -20,7 +20,7 @@ import configobj
# See: http://pubs.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap08.html
# or look at the 'param_expand()' function in the subst.c file in the bash
# source tarball...
-SHELL_VAR_RULE = r'[a-zA-Z_]+[a-zA-Z0-9_]*'
+SHELL_VAR_RULE = r"[a-zA-Z_]+[a-zA-Z0-9_]*"
SHELL_VAR_REGEXES = [
# Basic variables
re.compile(r"\$" + SHELL_VAR_RULE),
@@ -48,10 +48,11 @@ class SysConf(configobj.ConfigObj):
``configobj.ConfigObj.__init__`` (i.e. "a filename, file like object,
or list of lines").
"""
+
def __init__(self, contents):
- configobj.ConfigObj.__init__(self, contents,
- interpolation=False,
- write_empty_values=True)
+ configobj.ConfigObj.__init__(
+ self, contents, interpolation=False, write_empty_values=True
+ )
def __str__(self):
contents = self.write()
@@ -66,11 +67,13 @@ class SysConf(configobj.ConfigObj):
if not isinstance(value, str):
raise ValueError('Value "%s" is not a string' % (value))
if len(value) == 0:
- return ''
+ return ""
quot_func = None
if value[0] in ['"', "'"] and value[-1] in ['"', "'"]:
if len(value) == 1:
- quot_func = (lambda x: self._get_single_quote(x) % x)
+ quot_func = (
+ lambda x: self._get_single_quote(x) % x
+ ) # noqa: E731
else:
# Quote whitespace if it isn't the start + end of a shell command
if value.strip().startswith("$(") and value.strip().endswith(")"):
@@ -82,11 +85,13 @@ class SysConf(configobj.ConfigObj):
# leave it alone since the pipes.quote function likes
# to use single quotes which won't get expanded...
if re.search(r"[\n\"']", value):
- quot_func = (lambda x:
- self._get_triple_quote(x) % x)
+ quot_func = (
+ lambda x: self._get_triple_quote(x) % x
+ ) # noqa: E731
else:
- quot_func = (lambda x:
- self._get_single_quote(x) % x)
+ quot_func = (
+ lambda x: self._get_single_quote(x) % x
+ ) # noqa: E731
else:
quot_func = pipes.quote
if not quot_func:
@@ -99,10 +104,13 @@ class SysConf(configobj.ConfigObj):
val = self._decode_element(self._quote(this_entry))
key = self._decode_element(self._quote(entry))
cmnt = self._decode_element(comment)
- return '%s%s%s%s%s' % (indent_string,
- key,
- self._a_to_u('='),
- val,
- cmnt)
+ return "%s%s%s%s%s" % (
+ indent_string,
+ key,
+ self._a_to_u("="),
+ val,
+ cmnt,
+ )
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/photon.py b/cloudinit/distros/photon.py
new file mode 100644
index 00000000..14cefe90
--- /dev/null
+++ b/cloudinit/distros/photon.py
@@ -0,0 +1,150 @@
+#!/usr/bin/env python3
+# vi: ts=4 expandtab
+#
+# Copyright (C) 2021 VMware Inc.
+#
+# This file is part of cloud-init. See LICENSE file for license information.
+
+from cloudinit import distros, helpers
+from cloudinit import log as logging
+from cloudinit import net, subp, util
+from cloudinit.distros import rhel_util as rhutil
+from cloudinit.settings import PER_INSTANCE
+
+LOG = logging.getLogger(__name__)
+
+
+class Distro(distros.Distro):
+ systemd_hostname_conf_fn = "/etc/hostname"
+ network_conf_dir = "/etc/systemd/network/"
+ systemd_locale_conf_fn = "/etc/locale.conf"
+ resolve_conf_fn = "/etc/systemd/resolved.conf"
+
+ renderer_configs = {
+ "networkd": {
+ "resolv_conf_fn": resolve_conf_fn,
+ "network_conf_dir": network_conf_dir,
+ }
+ }
+
+ # Should be fqdn if we can use it
+ prefer_fqdn = True
+
+ def __init__(self, name, cfg, paths):
+ distros.Distro.__init__(self, name, cfg, paths)
+ # This will be used to restrict certain
+ # calls from repeatly happening (when they
+ # should only happen say once per instance...)
+ self._runner = helpers.Runners(paths)
+ self.osfamily = "photon"
+ self.init_cmd = ["systemctl"]
+
+ def exec_cmd(self, cmd, capture=True):
+ LOG.debug("Attempting to run: %s", cmd)
+ try:
+ (out, err) = subp.subp(cmd, capture=capture)
+ if err:
+ LOG.warning(
+ "Running %s resulted in stderr output: %s", cmd, err
+ )
+ return True, out, err
+ return False, out, err
+ except subp.ProcessExecutionError:
+ util.logexc(LOG, "Command %s failed", cmd)
+ return True, None, None
+
+ def generate_fallback_config(self):
+ key = "disable_fallback_netcfg"
+ disable_fallback_netcfg = self._cfg.get(key, True)
+ LOG.debug("%s value is: %s", key, disable_fallback_netcfg)
+
+ if not disable_fallback_netcfg:
+ return net.generate_fallback_config()
+
+ LOG.info(
+ "Skipping generate_fallback_config. Rely on PhotonOS default "
+ "network config"
+ )
+ return None
+
+ def apply_locale(self, locale, out_fn=None):
+ # This has a dependancy on glibc-i18n, user need to manually install it
+ # and enable the option in cloud.cfg
+ if not out_fn:
+ out_fn = self.systemd_locale_conf_fn
+
+ locale_cfg = {
+ "LANG": locale,
+ }
+
+ rhutil.update_sysconfig_file(out_fn, locale_cfg)
+
+ # rhutil will modify /etc/locale.conf
+ # For locale change to take effect, reboot is needed or we can restart
+ # systemd-localed. This is equivalent of localectl
+ cmd = ["systemctl", "restart", "systemd-localed"]
+ self.exec_cmd(cmd)
+
+ def install_packages(self, pkglist):
+ # self.update_package_sources()
+ self.package_command("install", pkgs=pkglist)
+
+ def _write_hostname(self, hostname, filename):
+ if filename and filename.endswith("/previous-hostname"):
+ util.write_file(filename, hostname)
+ else:
+ ret, _out, err = self.exec_cmd(
+ ["hostnamectl", "set-hostname", str(hostname)]
+ )
+ if ret:
+ LOG.warning(
+ (
+ "Error while setting hostname: %s\nGiven hostname: %s",
+ err,
+ hostname,
+ )
+ )
+
+ def _read_system_hostname(self):
+ sys_hostname = self._read_hostname(self.systemd_hostname_conf_fn)
+ return (self.systemd_hostname_conf_fn, sys_hostname)
+
+ def _read_hostname(self, filename, default=None):
+ if filename and filename.endswith("/previous-hostname"):
+ return util.load_file(filename).strip()
+
+ _ret, out, _err = self.exec_cmd(["hostname", "-f"])
+ return out.strip() if out else default
+
+ def _get_localhost_ip(self):
+ return "127.0.1.1"
+
+ def set_timezone(self, tz):
+ distros.set_etc_timezone(tz=tz, tz_file=self._find_tz_file(tz))
+
+ def package_command(self, command, args=None, pkgs=None):
+ if not pkgs:
+ pkgs = []
+
+ cmd = ["tdnf", "-y"]
+ if args and isinstance(args, str):
+ cmd.append(args)
+ elif args and isinstance(args, list):
+ cmd.extend(args)
+
+ cmd.append(command)
+
+ pkglist = util.expand_package_list("%s-%s", pkgs)
+ cmd.extend(pkglist)
+
+ ret, _out, err = self.exec_cmd(cmd)
+ if ret:
+ LOG.error("Error while installing packages: %s", err)
+
+ def update_package_sources(self):
+ self._runner.run(
+ "update-sources",
+ self.package_command,
+ ["makecache"],
+ freq=PER_INSTANCE,
+ )
diff --git a/cloudinit/distros/rhel.py b/cloudinit/distros/rhel.py
index c72f7c17..84744ece 100644
--- a/cloudinit/distros/rhel.py
+++ b/cloudinit/distros/rhel.py
@@ -8,12 +8,9 @@
#
# This file is part of cloud-init. See LICENSE file for license information.
-from cloudinit import distros
-from cloudinit import helpers
+from cloudinit import distros, helpers
from cloudinit import log as logging
-from cloudinit import subp
-from cloudinit import util
-
+from cloudinit import subp, util
from cloudinit.distros import rhel_util
from cloudinit.settings import PER_INSTANCE
@@ -22,48 +19,48 @@ LOG = logging.getLogger(__name__)
def _make_sysconfig_bool(val):
if val:
- return 'yes'
+ return "yes"
else:
- return 'no'
+ return "no"
class Distro(distros.Distro):
# See: https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Networking_Guide/sec-Network_Configuration_Using_sysconfig_Files.html # noqa
clock_conf_fn = "/etc/sysconfig/clock"
- locale_conf_fn = '/etc/sysconfig/i18n'
- systemd_locale_conf_fn = '/etc/locale.conf'
+ locale_conf_fn = "/etc/sysconfig/i18n"
+ systemd_locale_conf_fn = "/etc/locale.conf"
network_conf_fn = "/etc/sysconfig/network"
hostname_conf_fn = "/etc/sysconfig/network"
systemd_hostname_conf_fn = "/etc/hostname"
- network_script_tpl = '/etc/sysconfig/network-scripts/ifcfg-%s'
- resolve_conf_fn = "/etc/resolv.conf"
+ network_script_tpl = "/etc/sysconfig/network-scripts/ifcfg-%s"
tz_local_fn = "/etc/localtime"
usr_lib_exec = "/usr/libexec"
renderer_configs = {
- 'sysconfig': {
- 'control': 'etc/sysconfig/network',
- 'iface_templates': '%(base)s/network-scripts/ifcfg-%(name)s',
- 'route_templates': {
- 'ipv4': '%(base)s/network-scripts/route-%(name)s',
- 'ipv6': '%(base)s/network-scripts/route6-%(name)s'
- }
+ "sysconfig": {
+ "control": "etc/sysconfig/network",
+ "iface_templates": "%(base)s/network-scripts/ifcfg-%(name)s",
+ "route_templates": {
+ "ipv4": "%(base)s/network-scripts/route-%(name)s",
+ "ipv6": "%(base)s/network-scripts/route6-%(name)s",
+ },
}
}
+ # Should be fqdn if we can use it
+ # See: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/5/html/deployment_guide/ch-sysconfig # noqa: E501
+ prefer_fqdn = True
+
def __init__(self, name, cfg, paths):
distros.Distro.__init__(self, name, cfg, paths)
# This will be used to restrict certain
# calls from repeatly happening (when they
# should only happen say once per instance...)
self._runner = helpers.Runners(paths)
- self.osfamily = 'redhat'
- cfg['ssh_svcname'] = 'sshd'
+ self.osfamily = "redhat"
+ cfg["ssh_svcname"] = "sshd"
def install_packages(self, pkglist):
- self.package_command('install', pkgs=pkglist)
-
- def _write_network_config(self, netconfig):
- return self._supported_write_network_config(netconfig)
+ self.package_command("install", pkgs=pkglist)
def apply_locale(self, locale, out_fn=None):
if self.uses_systemd():
@@ -74,29 +71,22 @@ class Distro(distros.Distro):
if not out_fn:
out_fn = self.locale_conf_fn
locale_cfg = {
- 'LANG': locale,
+ "LANG": locale,
}
rhel_util.update_sysconfig_file(out_fn, locale_cfg)
- def _write_hostname(self, hostname, out_fn):
+ def _write_hostname(self, hostname, filename):
# systemd will never update previous-hostname for us, so
# we need to do it ourselves
- if self.uses_systemd() and out_fn.endswith('/previous-hostname'):
- util.write_file(out_fn, hostname)
+ if self.uses_systemd() and filename.endswith("/previous-hostname"):
+ util.write_file(filename, hostname)
elif self.uses_systemd():
- subp.subp(['hostnamectl', 'set-hostname', str(hostname)])
+ subp.subp(["hostnamectl", "set-hostname", str(hostname)])
else:
host_cfg = {
- 'HOSTNAME': hostname,
+ "HOSTNAME": hostname,
}
- rhel_util.update_sysconfig_file(out_fn, host_cfg)
-
- def _select_hostname(self, hostname, fqdn):
- # Should be fqdn if we can use it
- # See: https://www.centos.org/docs/5/html/Deployment_Guide-en-US/ch-sysconfig.html#s2-sysconfig-network # noqa
- if fqdn:
- return fqdn
- return hostname
+ rhel_util.update_sysconfig_file(filename, host_cfg)
def _read_system_hostname(self):
if self.uses_systemd():
@@ -106,27 +96,21 @@ class Distro(distros.Distro):
return (host_fn, self._read_hostname(host_fn))
def _read_hostname(self, filename, default=None):
- if self.uses_systemd() and filename.endswith('/previous-hostname'):
+ if self.uses_systemd() and filename.endswith("/previous-hostname"):
return util.load_file(filename).strip()
elif self.uses_systemd():
- (out, _err) = subp.subp(['hostname'])
+ (out, _err) = subp.subp(["hostname"])
if len(out):
return out
else:
return default
else:
(_exists, contents) = rhel_util.read_sysconfig_file(filename)
- if 'HOSTNAME' in contents:
- return contents['HOSTNAME']
+ if "HOSTNAME" in contents:
+ return contents["HOSTNAME"]
else:
return default
- def _bring_up_interfaces(self, device_names):
- if device_names and 'all' in device_names:
- raise RuntimeError(('Distro %s can not translate '
- 'the device name "all"') % (self.name))
- return distros.Distro._bring_up_interfaces(self, device_names)
-
def set_timezone(self, tz):
tz_file = self._find_tz_file(tz)
if self.uses_systemd():
@@ -137,7 +121,7 @@ class Distro(distros.Distro):
else:
# Adjust the sysconfig clock zone setting
clock_cfg = {
- 'ZONE': str(tz),
+ "ZONE": str(tz),
}
rhel_util.update_sysconfig_file(self.clock_conf_fn, clock_cfg)
# This ensures that the correct tz will be used for the system
@@ -147,18 +131,18 @@ class Distro(distros.Distro):
if pkgs is None:
pkgs = []
- if subp.which('dnf'):
- LOG.debug('Using DNF for package management')
- cmd = ['dnf']
+ if subp.which("dnf"):
+ LOG.debug("Using DNF for package management")
+ cmd = ["dnf"]
else:
- LOG.debug('Using YUM for package management')
+ LOG.debug("Using YUM for package management")
# the '-t' argument makes yum tolerant of errors on the command
# line with regard to packages.
#
# For example: if you request to install foo, bar and baz and baz
# is installed; yum won't error out complaining that baz is already
# installed.
- cmd = ['yum', '-t']
+ cmd = ["yum", "-t"]
# Determines whether or not yum prompts for confirmation
# of critical actions. We don't want to prompt...
cmd.append("-y")
@@ -170,14 +154,19 @@ class Distro(distros.Distro):
cmd.append(command)
- pkglist = util.expand_package_list('%s-%s', pkgs)
+ pkglist = util.expand_package_list("%s-%s", pkgs)
cmd.extend(pkglist)
# Allow the output of this to flow outwards (ie not be captured)
subp.subp(cmd, capture=False)
def update_package_sources(self):
- self._runner.run("update-sources", self.package_command,
- ["makecache"], freq=PER_INSTANCE)
+ self._runner.run(
+ "update-sources",
+ self.package_command,
+ ["makecache"],
+ freq=PER_INSTANCE,
+ )
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/rhel_util.py b/cloudinit/distros/rhel_util.py
index d71394b4..c96f93b5 100644
--- a/cloudinit/distros/rhel_util.py
+++ b/cloudinit/distros/rhel_util.py
@@ -8,10 +8,9 @@
#
# This file is part of cloud-init. See LICENSE file for license information.
-from cloudinit.distros.parsers.sys_conf import SysConf
-
from cloudinit import log as logging
from cloudinit import util
+from cloudinit.distros.parsers.sys_conf import SysConf
LOG = logging.getLogger(__name__)
@@ -49,4 +48,5 @@ def read_sysconfig_file(fn):
contents = []
return (exists, SysConf(contents))
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/rocky.py b/cloudinit/distros/rocky.py
new file mode 100644
index 00000000..3dc0a342
--- /dev/null
+++ b/cloudinit/distros/rocky.py
@@ -0,0 +1,10 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+from cloudinit.distros import rhel
+
+
+class Distro(rhel.Distro):
+ pass
+
+
+# vi: ts=4 expandtab
diff --git a/cloudinit/distros/sles.py b/cloudinit/distros/sles.py
index f3bfb9c2..484214e7 100644
--- a/cloudinit/distros/sles.py
+++ b/cloudinit/distros/sles.py
@@ -10,4 +10,5 @@ from cloudinit.distros import opensuse
class Distro(opensuse.Distro):
pass
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/tests/__init__.py b/cloudinit/distros/tests/__init__.py
deleted file mode 100644
index e69de29b..00000000
--- a/cloudinit/distros/tests/__init__.py
+++ /dev/null
diff --git a/cloudinit/distros/tests/test_init.py b/cloudinit/distros/tests/test_init.py
deleted file mode 100644
index db534654..00000000
--- a/cloudinit/distros/tests/test_init.py
+++ /dev/null
@@ -1,156 +0,0 @@
-# Copyright (C) 2020 Canonical Ltd.
-#
-# Author: Daniel Watkins <oddbloke@ubuntu.com>
-#
-# This file is part of cloud-init. See LICENSE file for license information.
-"""Tests for cloudinit/distros/__init__.py"""
-
-from unittest import mock
-
-import pytest
-
-from cloudinit.distros import _get_package_mirror_info, LDH_ASCII_CHARS
-
-
-# Define a set of characters we would expect to be replaced
-INVALID_URL_CHARS = [
- chr(x) for x in range(127) if chr(x) not in LDH_ASCII_CHARS
-]
-for separator in [":", ".", "/", "#", "?", "@", "[", "]"]:
- # Remove from the set characters that either separate hostname parts (":",
- # "."), terminate hostnames ("/", "#", "?", "@"), or cause Python to be
- # unable to parse URLs ("[", "]").
- INVALID_URL_CHARS.remove(separator)
-
-
-class TestGetPackageMirrorInfo:
- """
- Tests for cloudinit.distros._get_package_mirror_info.
-
- These supplement the tests in tests/unittests/test_distros/test_generic.py
- which are more focused on testing a single production-like configuration.
- These tests are more focused on specific aspects of the unit under test.
- """
-
- @pytest.mark.parametrize('mirror_info,expected', [
- # Empty info gives empty return
- ({}, {}),
- # failsafe values used if present
- ({'failsafe': {'primary': 'http://value', 'security': 'http://other'}},
- {'primary': 'http://value', 'security': 'http://other'}),
- # search values used if present
- ({'search': {'primary': ['http://value'],
- 'security': ['http://other']}},
- {'primary': ['http://value'], 'security': ['http://other']}),
- # failsafe values used if search value not present
- ({'search': {'primary': ['http://value']},
- 'failsafe': {'security': 'http://other'}},
- {'primary': ['http://value'], 'security': 'http://other'})
- ])
- def test_get_package_mirror_info_failsafe(self, mirror_info, expected):
- """
- Test the interaction between search and failsafe inputs
-
- (This doesn't test the case where the mirror_filter removes all search
- options; test_failsafe_used_if_all_search_results_filtered_out covers
- that.)
- """
- assert expected == _get_package_mirror_info(mirror_info,
- mirror_filter=lambda x: x)
-
- def test_failsafe_used_if_all_search_results_filtered_out(self):
- """Test the failsafe option used if all search options eliminated."""
- mirror_info = {
- 'search': {'primary': ['http://value']},
- 'failsafe': {'primary': 'http://other'}
- }
- assert {'primary': 'http://other'} == _get_package_mirror_info(
- mirror_info, mirror_filter=lambda x: False)
-
- @pytest.mark.parametrize('allow_ec2_mirror, platform_type', [
- (True, 'ec2')
- ])
- @pytest.mark.parametrize('availability_zone,region,patterns,expected', (
- # Test ec2_region alone
- ('fk-fake-1f', None, ['http://EC2-%(ec2_region)s/ubuntu'],
- ['http://ec2-fk-fake-1/ubuntu']),
- # Test availability_zone alone
- ('fk-fake-1f', None, ['http://AZ-%(availability_zone)s/ubuntu'],
- ['http://az-fk-fake-1f/ubuntu']),
- # Test region alone
- (None, 'fk-fake-1', ['http://RG-%(region)s/ubuntu'],
- ['http://rg-fk-fake-1/ubuntu']),
- # Test that ec2_region is not available for non-matching AZs
- ('fake-fake-1f', None,
- ['http://EC2-%(ec2_region)s/ubuntu',
- 'http://AZ-%(availability_zone)s/ubuntu'],
- ['http://az-fake-fake-1f/ubuntu']),
- # Test that template order maintained
- (None, 'fake-region',
- ['http://RG-%(region)s-2/ubuntu', 'http://RG-%(region)s-1/ubuntu'],
- ['http://rg-fake-region-2/ubuntu', 'http://rg-fake-region-1/ubuntu']),
- # Test that non-ASCII hostnames are IDNA encoded;
- # "IDNA-ТεЅТ̣".encode('idna') == b"xn--idna--4kd53hh6aba3q"
- (None, 'ТεЅТ̣', ['http://www.IDNA-%(region)s.com/ubuntu'],
- ['http://www.xn--idna--4kd53hh6aba3q.com/ubuntu']),
- # Test that non-ASCII hostnames with a port are IDNA encoded;
- # "IDNA-ТεЅТ̣".encode('idna') == b"xn--idna--4kd53hh6aba3q"
- (None, 'ТεЅТ̣', ['http://www.IDNA-%(region)s.com:8080/ubuntu'],
- ['http://www.xn--idna--4kd53hh6aba3q.com:8080/ubuntu']),
- # Test that non-ASCII non-hostname parts of URLs are unchanged
- (None, 'ТεЅТ̣', ['http://www.example.com/%(region)s/ubuntu'],
- ['http://www.example.com/ТεЅТ̣/ubuntu']),
- # Test that IPv4 addresses are unchanged
- (None, 'fk-fake-1', ['http://192.168.1.1:8080/%(region)s/ubuntu'],
- ['http://192.168.1.1:8080/fk-fake-1/ubuntu']),
- # Test that IPv6 addresses are unchanged
- (None, 'fk-fake-1',
- ['http://[2001:67c:1360:8001::23]/%(region)s/ubuntu'],
- ['http://[2001:67c:1360:8001::23]/fk-fake-1/ubuntu']),
- # Test that unparseable URLs are filtered out of the mirror list
- (None, 'inv[lid',
- ['http://%(region)s.in.hostname/should/be/filtered',
- 'http://but.not.in.the.path/%(region)s'],
- ['http://but.not.in.the.path/inv[lid']),
- (None, '-some-region-',
- ['http://-lead-ing.%(region)s.trail-ing-.example.com/ubuntu'],
- ['http://lead-ing.some-region.trail-ing.example.com/ubuntu']),
- ) + tuple(
- # Dynamically generate a test case for each non-LDH
- # (Letters/Digits/Hyphen) ASCII character, testing that it is
- # substituted with a hyphen
- (None, 'fk{0}fake{0}1'.format(invalid_char),
- ['http://%(region)s/ubuntu'], ['http://fk-fake-1/ubuntu'])
- for invalid_char in INVALID_URL_CHARS
- ))
- def test_valid_substitution(self,
- allow_ec2_mirror,
- platform_type,
- availability_zone,
- region,
- patterns,
- expected):
- """Test substitution works as expected."""
- flag_path = "cloudinit.distros." \
- "ALLOW_EC2_MIRRORS_ON_NON_AWS_INSTANCE_TYPES"
-
- m_data_source = mock.Mock(
- availability_zone=availability_zone,
- region=region,
- platform_type=platform_type
- )
- mirror_info = {'search': {'primary': patterns}}
-
- with mock.patch(flag_path, allow_ec2_mirror):
- ret = _get_package_mirror_info(
- mirror_info,
- data_source=m_data_source,
- mirror_filter=lambda x: x
- )
- print(allow_ec2_mirror)
- print(platform_type)
- print(availability_zone)
- print(region)
- print(patterns)
- print(expected)
- assert {'primary': expected} == ret
diff --git a/cloudinit/distros/tests/test_networking.py b/cloudinit/distros/tests/test_networking.py
deleted file mode 100644
index ec508f4d..00000000
--- a/cloudinit/distros/tests/test_networking.py
+++ /dev/null
@@ -1,223 +0,0 @@
-from unittest import mock
-
-import pytest
-
-from cloudinit import net
-from cloudinit.distros.networking import (
- BSDNetworking,
- LinuxNetworking,
- Networking,
-)
-
-# See https://docs.pytest.org/en/stable/example
-# /parametrize.html#parametrizing-conditional-raising
-from contextlib import ExitStack as does_not_raise
-
-
-@pytest.yield_fixture
-def generic_networking_cls():
- """Returns a direct Networking subclass which errors on /sys usage.
-
- This enables the direct testing of functionality only present on the
- ``Networking`` super-class, and provides a check on accidentally using /sys
- in that context.
- """
-
- class TestNetworking(Networking):
- def is_physical(self, *args, **kwargs):
- raise NotImplementedError
-
- def settle(self, *args, **kwargs):
- raise NotImplementedError
-
- def try_set_link_up(self, *args, **kwargs):
- raise NotImplementedError
-
- error = AssertionError("Unexpectedly used /sys in generic networking code")
- with mock.patch(
- "cloudinit.net.get_sys_class_path", side_effect=error,
- ):
- yield TestNetworking
-
-
-@pytest.yield_fixture
-def sys_class_net(tmpdir):
- sys_class_net_path = tmpdir.join("sys/class/net")
- sys_class_net_path.ensure_dir()
- with mock.patch(
- "cloudinit.net.get_sys_class_path",
- return_value=sys_class_net_path.strpath + "/",
- ):
- yield sys_class_net_path
-
-
-class TestBSDNetworkingIsPhysical:
- def test_raises_notimplementederror(self):
- with pytest.raises(NotImplementedError):
- BSDNetworking().is_physical("eth0")
-
-
-class TestLinuxNetworkingIsPhysical:
- def test_returns_false_by_default(self, sys_class_net):
- assert not LinuxNetworking().is_physical("eth0")
-
- def test_returns_false_if_devname_exists_but_not_physical(
- self, sys_class_net
- ):
- devname = "eth0"
- sys_class_net.join(devname).mkdir()
- assert not LinuxNetworking().is_physical(devname)
-
- def test_returns_true_if_device_is_physical(self, sys_class_net):
- devname = "eth0"
- device_dir = sys_class_net.join(devname)
- device_dir.mkdir()
- device_dir.join("device").write("")
-
- assert LinuxNetworking().is_physical(devname)
-
-
-class TestBSDNetworkingTrySetLinkUp:
- def test_raises_notimplementederror(self):
- with pytest.raises(NotImplementedError):
- BSDNetworking().try_set_link_up("eth0")
-
-
-@mock.patch("cloudinit.net.is_up")
-@mock.patch("cloudinit.distros.networking.subp.subp")
-class TestLinuxNetworkingTrySetLinkUp:
- def test_calls_subp_return_true(self, m_subp, m_is_up):
- devname = "eth0"
- m_is_up.return_value = True
- is_success = LinuxNetworking().try_set_link_up(devname)
-
- assert (mock.call(['ip', 'link', 'set', devname, 'up']) ==
- m_subp.call_args_list[-1])
- assert is_success
-
- def test_calls_subp_return_false(self, m_subp, m_is_up):
- devname = "eth0"
- m_is_up.return_value = False
- is_success = LinuxNetworking().try_set_link_up(devname)
-
- assert (mock.call(['ip', 'link', 'set', devname, 'up']) ==
- m_subp.call_args_list[-1])
- assert not is_success
-
-
-class TestBSDNetworkingSettle:
- def test_settle_doesnt_error(self):
- # This also implicitly tests that it doesn't use subp.subp
- BSDNetworking().settle()
-
-
-@pytest.mark.usefixtures("sys_class_net")
-@mock.patch("cloudinit.distros.networking.util.udevadm_settle", autospec=True)
-class TestLinuxNetworkingSettle:
- def test_no_arguments(self, m_udevadm_settle):
- LinuxNetworking().settle()
-
- assert [mock.call(exists=None)] == m_udevadm_settle.call_args_list
-
- def test_exists_argument(self, m_udevadm_settle):
- LinuxNetworking().settle(exists="ens3")
-
- expected_path = net.sys_dev_path("ens3")
- assert [
- mock.call(exists=expected_path)
- ] == m_udevadm_settle.call_args_list
-
-
-class TestNetworkingWaitForPhysDevs:
- @pytest.fixture
- def wait_for_physdevs_netcfg(self):
- """This config is shared across all the tests in this class."""
-
- def ethernet(mac, name, driver=None, device_id=None):
- v2_cfg = {"set-name": name, "match": {"macaddress": mac}}
- if driver:
- v2_cfg["match"].update({"driver": driver})
- if device_id:
- v2_cfg["match"].update({"device_id": device_id})
-
- return v2_cfg
-
- physdevs = [
- ["aa:bb:cc:dd:ee:ff", "eth0", "virtio", "0x1000"],
- ["00:11:22:33:44:55", "ens3", "e1000", "0x1643"],
- ]
- netcfg = {
- "version": 2,
- "ethernets": {args[1]: ethernet(*args) for args in physdevs},
- }
- return netcfg
-
- def test_skips_settle_if_all_present(
- self, generic_networking_cls, wait_for_physdevs_netcfg,
- ):
- networking = generic_networking_cls()
- with mock.patch.object(
- networking, "get_interfaces_by_mac"
- ) as m_get_interfaces_by_mac:
- m_get_interfaces_by_mac.side_effect = iter(
- [{"aa:bb:cc:dd:ee:ff": "eth0", "00:11:22:33:44:55": "ens3"}]
- )
- with mock.patch.object(
- networking, "settle", autospec=True
- ) as m_settle:
- networking.wait_for_physdevs(wait_for_physdevs_netcfg)
- assert 0 == m_settle.call_count
-
- def test_calls_udev_settle_on_missing(
- self, generic_networking_cls, wait_for_physdevs_netcfg,
- ):
- networking = generic_networking_cls()
- with mock.patch.object(
- networking, "get_interfaces_by_mac"
- ) as m_get_interfaces_by_mac:
- m_get_interfaces_by_mac.side_effect = iter(
- [
- {
- "aa:bb:cc:dd:ee:ff": "eth0"
- }, # first call ens3 is missing
- {
- "aa:bb:cc:dd:ee:ff": "eth0",
- "00:11:22:33:44:55": "ens3",
- }, # second call has both
- ]
- )
- with mock.patch.object(
- networking, "settle", autospec=True
- ) as m_settle:
- networking.wait_for_physdevs(wait_for_physdevs_netcfg)
- m_settle.assert_called_with(exists="ens3")
-
- @pytest.mark.parametrize(
- "strict,expectation",
- [(True, pytest.raises(RuntimeError)), (False, does_not_raise())],
- )
- def test_retrying_and_strict_behaviour(
- self,
- strict,
- expectation,
- generic_networking_cls,
- wait_for_physdevs_netcfg,
- ):
- networking = generic_networking_cls()
- with mock.patch.object(
- networking, "get_interfaces_by_mac"
- ) as m_get_interfaces_by_mac:
- m_get_interfaces_by_mac.return_value = {}
-
- with mock.patch.object(
- networking, "settle", autospec=True
- ) as m_settle:
- with expectation:
- networking.wait_for_physdevs(
- wait_for_physdevs_netcfg, strict=strict
- )
-
- assert (
- 5 * len(wait_for_physdevs_netcfg["ethernets"])
- == m_settle.call_count
- )
diff --git a/cloudinit/distros/ubuntu.py b/cloudinit/distros/ubuntu.py
index 2a1f93d9..ec6470a9 100644
--- a/cloudinit/distros/ubuntu.py
+++ b/cloudinit/distros/ubuntu.py
@@ -9,41 +9,44 @@
#
# This file is part of cloud-init. See LICENSE file for license information.
-from cloudinit.distros import debian
-from cloudinit.distros import PREFERRED_NTP_CLIENTS
-from cloudinit import util
-
import copy
+from cloudinit import util
+from cloudinit.distros import PREFERRED_NTP_CLIENTS, debian
-class Distro(debian.Distro):
+class Distro(debian.Distro):
def __init__(self, name, cfg, paths):
super(Distro, self).__init__(name, cfg, paths)
# Ubuntu specific network cfg locations
self.network_conf_fn = {
"eni": "/etc/network/interfaces.d/50-cloud-init.cfg",
- "netplan": "/etc/netplan/50-cloud-init.yaml"
+ "netplan": "/etc/netplan/50-cloud-init.yaml",
}
self.renderer_configs = {
- "eni": {"eni_path": self.network_conf_fn["eni"],
- "eni_header": debian.NETWORK_FILE_HEADER},
- "netplan": {"netplan_path": self.network_conf_fn["netplan"],
- "netplan_header": debian.NETWORK_FILE_HEADER,
- "postcmds": True}
+ "eni": {
+ "eni_path": self.network_conf_fn["eni"],
+ "eni_header": debian.NETWORK_FILE_HEADER,
+ },
+ "netplan": {
+ "netplan_path": self.network_conf_fn["netplan"],
+ "netplan_header": debian.NETWORK_FILE_HEADER,
+ "postcmds": True,
+ },
}
@property
def preferred_ntp_clients(self):
"""The preferred ntp client is dependent on the version."""
if not self._preferred_ntp_clients:
- (_name, _version, codename) = util.system_info()['dist']
+ (_name, _version, codename) = util.system_info()["dist"]
# Xenial cloud-init only installed ntp, UbuntuCore has timesyncd.
if codename == "xenial" and not util.system_is_snappy():
- self._preferred_ntp_clients = ['ntp']
+ self._preferred_ntp_clients = ["ntp"]
else:
- self._preferred_ntp_clients = (
- copy.deepcopy(PREFERRED_NTP_CLIENTS))
+ self._preferred_ntp_clients = copy.deepcopy(
+ PREFERRED_NTP_CLIENTS
+ )
return self._preferred_ntp_clients
diff --git a/cloudinit/distros/ug_util.py b/cloudinit/distros/ug_util.py
index 08446a95..72766392 100755
--- a/cloudinit/distros/ug_util.py
+++ b/cloudinit/distros/ug_util.py
@@ -10,92 +10,80 @@
# This file is part of cloud-init. See LICENSE file for license information.
from cloudinit import log as logging
-from cloudinit import type_utils
-from cloudinit import util
+from cloudinit import type_utils, util
LOG = logging.getLogger(__name__)
-# Normalizes a input group configuration
-# which can be a comma seperated list of
-# group names, or a list of group names
-# or a python dictionary of group names
-# to a list of members of that group.
+# Normalizes an input group configuration which can be:
+# Comma seperated string or a list or a dictionary
#
-# The output is a dictionary of group
-# names => members of that group which
-# is the standard form used in the rest
-# of cloud-init
+# Returns dictionary of group names => members of that group which is the
+# standard form used in the rest of cloud-init
def _normalize_groups(grp_cfg):
if isinstance(grp_cfg, str):
grp_cfg = grp_cfg.strip().split(",")
+
if isinstance(grp_cfg, list):
c_grp_cfg = {}
for i in grp_cfg:
if isinstance(i, dict):
for k, v in i.items():
- if k not in c_grp_cfg:
- if isinstance(v, list):
- c_grp_cfg[k] = list(v)
- elif isinstance(v, str):
- c_grp_cfg[k] = [v]
- else:
- raise TypeError("Bad group member type %s" %
- type_utils.obj_name(v))
+ if not isinstance(v, (list, str)):
+ raise TypeError(
+ "Bad group member type %s"
+ % (type_utils.obj_name(v))
+ )
+
+ if isinstance(v, list):
+ c_grp_cfg.setdefault(k, []).extend(v)
else:
- if isinstance(v, list):
- c_grp_cfg[k].extend(v)
- elif isinstance(v, str):
- c_grp_cfg[k].append(v)
- else:
- raise TypeError("Bad group member type %s" %
- type_utils.obj_name(v))
+ c_grp_cfg.setdefault(k, []).append(v)
elif isinstance(i, str):
if i not in c_grp_cfg:
c_grp_cfg[i] = []
else:
- raise TypeError("Unknown group name type %s" %
- type_utils.obj_name(i))
+ raise TypeError(
+ "Unknown group name type %s" % (type_utils.obj_name(i))
+ )
grp_cfg = c_grp_cfg
+
groups = {}
if isinstance(grp_cfg, dict):
- for (grp_name, grp_members) in grp_cfg.items():
+ for grp_name, grp_members in grp_cfg.items():
groups[grp_name] = util.uniq_merge_sorted(grp_members)
else:
- raise TypeError(("Group config must be list, dict "
- " or string types only and not %s") %
- type_utils.obj_name(grp_cfg))
+ raise TypeError(
+ "Group config must be list, dict or string type only but found %s"
+ % (type_utils.obj_name(grp_cfg))
+ )
return groups
-# Normalizes a input group configuration
-# which can be a comma seperated list of
-# user names, or a list of string user names
-# or a list of dictionaries with components
-# that define the user config + 'name' (if
-# a 'name' field does not exist then the
-# default user is assumed to 'own' that
-# configuration.
+# Normalizes an input group configuration which can be: a list or a dictionary
+#
+# components that define the user config + 'name' (if a 'name' field does not
+# exist then the default user is assumed to 'own' that configuration.)
#
-# The output is a dictionary of user
-# names => user config which is the standard
-# form used in the rest of cloud-init. Note
-# the default user will have a special config
-# entry 'default' which will be marked as true
-# all other users will be marked as false.
+# Returns a dictionary of user names => user config which is the standard form
+# used in the rest of cloud-init. Note the default user will have a special
+# config entry 'default' which will be marked true and all other users will be
+# marked false.
def _normalize_users(u_cfg, def_user_cfg=None):
if isinstance(u_cfg, dict):
ad_ucfg = []
- for (k, v) in u_cfg.items():
+ for k, v in u_cfg.items():
if isinstance(v, (bool, int, float, str)):
if util.is_true(v):
ad_ucfg.append(str(k))
elif isinstance(v, dict):
- v['name'] = k
+ v["name"] = k
ad_ucfg.append(v)
else:
- raise TypeError(("Unmappable user value type %s"
- " for key %s") % (type_utils.obj_name(v), k))
+ raise TypeError(
+ "Unmappable user value type %s for key %s"
+ % (type_utils.obj_name(v), k)
+ )
u_cfg = ad_ucfg
elif isinstance(u_cfg, str):
u_cfg = util.uniq_merge_sorted(u_cfg)
@@ -107,181 +95,157 @@ def _normalize_users(u_cfg, def_user_cfg=None):
if u and u not in users:
users[u] = {}
elif isinstance(user_config, dict):
- if 'name' in user_config:
- n = user_config.pop('name')
- prev_config = users.get(n) or {}
- users[n] = util.mergemanydict([prev_config,
- user_config])
- else:
- # Assume the default user then
- prev_config = users.get('default') or {}
- users['default'] = util.mergemanydict([prev_config,
- user_config])
+ n = user_config.pop("name", "default")
+ prev_config = users.get(n) or {}
+ users[n] = util.mergemanydict([prev_config, user_config])
else:
- raise TypeError(("User config must be dictionary/list "
- " or string types only and not %s") %
- type_utils.obj_name(user_config))
+ raise TypeError(
+ "User config must be dictionary/list or string "
+ " types only and not %s" % (type_utils.obj_name(user_config))
+ )
# Ensure user options are in the right python friendly format
if users:
c_users = {}
- for (uname, uconfig) in users.items():
+ for uname, uconfig in users.items():
c_uconfig = {}
- for (k, v) in uconfig.items():
- k = k.replace('-', '_').strip()
+ for k, v in uconfig.items():
+ k = k.replace("-", "_").strip()
if k:
c_uconfig[k] = v
c_users[uname] = c_uconfig
users = c_users
- # Fixup the default user into the real
- # default user name and replace it...
+ # Fix the default user into the actual default user name and replace it.
def_user = None
- if users and 'default' in users:
- def_config = users.pop('default')
+ if users and "default" in users:
+ def_config = users.pop("default")
if def_user_cfg:
- # Pickup what the default 'real name' is
- # and any groups that are provided by the
- # default config
+ # Pickup what the default 'real name' is and any groups that are
+ # provided by the default config
def_user_cfg = def_user_cfg.copy()
- def_user = def_user_cfg.pop('name')
- def_groups = def_user_cfg.pop('groups', [])
- # Pickup any config + groups for that user name
- # that we may have previously extracted
+ def_user = def_user_cfg.pop("name")
+ def_groups = def_user_cfg.pop("groups", [])
+ # Pick any config + groups for the user name that we may have
+ # extracted previously
parsed_config = users.pop(def_user, {})
- parsed_groups = parsed_config.get('groups', [])
- # Now merge our extracted groups with
- # anything the default config provided
+ parsed_groups = parsed_config.get("groups", [])
+ # Now merge the extracted groups with the default config provided
users_groups = util.uniq_merge_sorted(parsed_groups, def_groups)
- parsed_config['groups'] = ",".join(users_groups)
- # The real config for the default user is the
- # combination of the default user config provided
- # by the distro, the default user config provided
- # by the above merging for the user 'default' and
- # then the parsed config from the user's 'real name'
- # which does not have to be 'default' (but could be)
- users[def_user] = util.mergemanydict([def_user_cfg,
- def_config,
- parsed_config])
-
- # Ensure that only the default user that we
- # found (if any) is actually marked as being
- # the default user
- if users:
- for (uname, uconfig) in users.items():
- if def_user and uname == def_user:
- uconfig['default'] = True
- else:
- uconfig['default'] = False
+ parsed_config["groups"] = ",".join(users_groups)
+ # The real config for the default user is the combination of the
+ # default user config provided by the distro, the default user
+ # config provided by the above merging for the user 'default' and
+ # then the parsed config from the user's 'real name' which does not
+ # have to be 'default' (but could be)
+ users[def_user] = util.mergemanydict(
+ [def_user_cfg, def_config, parsed_config]
+ )
+
+ # Ensure that only the default user that we found (if any) is actually
+ # marked as the default user
+ for uname, uconfig in users.items():
+ uconfig["default"] = uname == def_user if def_user else False
return users
-# Normalizes a set of user/users and group
-# dictionary configuration into a useable
-# format that the rest of cloud-init can
-# understand using the default user
-# provided by the input distrobution (if any)
-# to allow for mapping of the 'default' user.
+# Normalizes a set of user/users and group dictionary configuration into an
+# usable format so that the rest of cloud-init can understand using the default
+# user provided by the input distribution (if any) to allow mapping of the
+# 'default' user.
#
# Output is a dictionary of group names -> [member] (list)
# and a dictionary of user names -> user configuration (dict)
#
-# If 'user' exists it will override
-# the 'users'[0] entry (if a list) otherwise it will
-# just become an entry in the returned dictionary (no override)
+# If 'user' exists, it will override
+# The 'users'[0] entry (if a list) otherwise it will just become an entry in
+# the returned dictionary (no override)
def normalize_users_groups(cfg, distro):
if not cfg:
cfg = {}
- users = {}
- groups = {}
- if 'groups' in cfg:
- groups = _normalize_groups(cfg['groups'])
-
# Handle the previous style of doing this where the first user
# overrides the concept of the default user if provided in the user: XYZ
# format.
old_user = {}
- if 'user' in cfg and cfg['user']:
- old_user = cfg['user']
- # Translate it into the format that is more useful
- # going forward
+ if "user" in cfg and cfg["user"]:
+ old_user = cfg["user"]
+ # Translate it into a format that will be more useful going forward
if isinstance(old_user, str):
- old_user = {
- 'name': old_user,
- }
- if not isinstance(old_user, dict):
- LOG.warning(("Format for 'user' key must be a string or dictionary"
- " and not %s"), type_utils.obj_name(old_user))
+ old_user = {"name": old_user}
+ elif not isinstance(old_user, dict):
+ LOG.warning(
+ "Format for 'user' key must be a string or dictionary"
+ " and not %s",
+ type_utils.obj_name(old_user),
+ )
old_user = {}
- # If no old user format, then assume the distro
- # provides what the 'default' user maps to, but notice
- # that if this is provided, we won't automatically inject
- # a 'default' user into the users list, while if a old user
- # format is provided we will.
+ # If no old user format, then assume the distro provides what the 'default'
+ # user maps to, but notice that if this is provided, we won't automatically
+ # inject a 'default' user into the users list, while if an old user format
+ # is provided we will.
distro_user_config = {}
try:
distro_user_config = distro.get_default_user()
except NotImplementedError:
- LOG.warning(("Distro has not implemented default user "
- "access. No distribution provided default user"
- " will be normalized."))
-
- # Merge the old user (which may just be an empty dict when not
- # present with the distro provided default user configuration so
- # that the old user style picks up all the distribution specific
- # attributes (if any)
+ LOG.warning(
+ "Distro has not implemented default user access. No "
+ "distribution provided default user will be normalized."
+ )
+
+ # Merge the old user (which may just be an empty dict when not present)
+ # with the distro provided default user configuration so that the old user
+ # style picks up all the distribution specific attributes (if any)
default_user_config = util.mergemanydict([old_user, distro_user_config])
- base_users = cfg.get('users', [])
+ base_users = cfg.get("users", [])
if not isinstance(base_users, (list, dict, str)):
- LOG.warning(("Format for 'users' key must be a comma separated string"
- " or a dictionary or a list and not %s"),
- type_utils.obj_name(base_users))
+ LOG.warning(
+ "Format for 'users' key must be a comma separated string"
+ " or a dictionary or a list but found %s",
+ type_utils.obj_name(base_users),
+ )
base_users = []
if old_user:
- # Ensure that when user: is provided that this user
- # always gets added (as the default user)
+ # When 'user:' is provided, it should be made as the default user
if isinstance(base_users, list):
- # Just add it on at the end...
- base_users.append({'name': 'default'})
+ base_users.append({"name": "default"})
elif isinstance(base_users, dict):
- base_users['default'] = dict(base_users).get('default', True)
+ base_users["default"] = dict(base_users).get("default", True)
elif isinstance(base_users, str):
- # Just append it on to be re-parsed later
base_users += ",default"
+ groups = {}
+ if "groups" in cfg:
+ groups = _normalize_groups(cfg["groups"])
+
users = _normalize_users(base_users, default_user_config)
return (users, groups)
-# Given a user dictionary config it will
-# extract the default user name and user config
-# from that list and return that tuple or
-# return (None, None) if no default user is
-# found in the given input
+# Given a user dictionary config, extract the default user name and user config
+# and return them or return (None, None) if no default user is found
def extract_default(users, default_name=None, default_config=None):
if not users:
- users = {}
+ return (default_name, default_config)
def safe_find(entry):
config = entry[1]
- if not config or 'default' not in config:
+ if not config or "default" not in config:
return False
- else:
- return config['default']
+ return config["default"]
- tmp_users = users.items()
- tmp_users = dict(filter(safe_find, tmp_users))
+ tmp_users = dict(filter(safe_find, users.items()))
if not tmp_users:
return (default_name, default_config)
- else:
- name = list(tmp_users)[0]
- config = tmp_users[name]
- config.pop('default', None)
- return (name, config)
+
+ name = list(tmp_users)[0]
+ config = tmp_users[name]
+ config.pop("default", None)
+ return (name, config)
+
# vi: ts=4 expandtab
diff --git a/cloudinit/distros/virtuozzo.py b/cloudinit/distros/virtuozzo.py
new file mode 100644
index 00000000..3dc0a342
--- /dev/null
+++ b/cloudinit/distros/virtuozzo.py
@@ -0,0 +1,10 @@
+# This file is part of cloud-init. See LICENSE file for license information.
+
+from cloudinit.distros import rhel
+
+
+class Distro(rhel.Distro):
+ pass
+
+
+# vi: ts=4 expandtab