File: bsd.py

package info (click to toggle)
cloud-init 25.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 12,412 kB
  • sloc: python: 135,894; sh: 3,883; makefile: 141; javascript: 30; xml: 22
file content (183 lines) | stat: -rw-r--r-- 6,490 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
import logging
import platform
import re
from typing import List, Optional

import cloudinit.net.netops.bsd_netops as bsd_netops
from cloudinit import distros, helpers, net, subp, util
from cloudinit.distros import PackageList, bsd_utils
from cloudinit.distros.networking import BSDNetworking

LOG = logging.getLogger(__name__)


class BSD(distros.Distro):
    networking_cls = BSDNetworking
    hostname_conf_fn = "/etc/rc.conf"
    rc_conf_fn = "/etc/rc.conf"
    shadow_fn = "/etc/master.passwd"
    default_owner = "root:wheel"

    # This differs from the parent Distro class, which has -P for
    # poweroff.
    shutdown_options_map = {"halt": "-H", "poweroff": "-p", "reboot": "-r"}

    # Set in BSD distro subclasses
    group_add_cmd_prefix: List[str] = []
    pkg_cmd_install_prefix: List[str] = []
    pkg_cmd_remove_prefix: List[str] = []
    # There is no update/upgrade on OpenBSD
    pkg_cmd_update_prefix: Optional[List[str]] = None
    pkg_cmd_upgrade_prefix: Optional[List[str]] = None
    net_ops = bsd_netops.BsdNetOps

    def __init__(self, name, cfg, paths):
        super().__init__(name, cfg, paths)
        # This will be used to restrict certain
        # calls from repeatedly happening (when they
        # should only happen say once per instance...)
        self._runner = helpers.Runners(paths)
        cfg["ssh_svcname"] = "sshd"
        cfg["rsyslog_svcname"] = "rsyslogd"
        self.osfamily = platform.system().lower()
        self.net_ops = bsd_netops.BsdNetOps
        self.is_linux = False

    def _unpickle(self, ci_pkl_version: int) -> None:
        super()._unpickle(ci_pkl_version)

        # this needs to be after the super class _unpickle to override it
        self.is_linux = False

    def _read_system_hostname(self):
        sys_hostname = self._read_hostname(self.hostname_conf_fn)
        return (self.hostname_conf_fn, sys_hostname)

    def _read_hostname(self, filename, default=None):
        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")

    def _write_hostname(self, hostname, filename):
        bsd_utils.set_rc_config_value("hostname", hostname, fn="/etc/rc.conf")

    def create_group(self, name, members=None):
        if util.is_group(name):
            LOG.warning("Skipping creation of existing group '%s'", name)
        else:
            group_add_cmd = self.group_add_cmd_prefix + [name]
            try:
                subp.subp(group_add_cmd)
                LOG.info("Created new group %s", name)
            except Exception:
                util.logexc(LOG, "Failed to create group %s", name)

        if not members:
            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,
                )
                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
                )

    def generate_fallback_config(self):
        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"}],
                }
            )
        return nconf

    def install_packages(self, pkglist: PackageList):
        self.update_package_sources()
        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")

    def package_command(self, command, args=None, pkgs=None):
        if pkgs is None:
            pkgs = []

        if command == "install":
            cmd = self.pkg_cmd_install_prefix
        elif command == "remove":
            cmd = self.pkg_cmd_remove_prefix
        elif command == "update":
            if not self.pkg_cmd_update_prefix:
                return
            cmd = self.pkg_cmd_update_prefix
        elif command == "upgrade":
            if not self.pkg_cmd_upgrade_prefix:
                return
            cmd = self.pkg_cmd_upgrade_prefix
        else:
            cmd = []

        if args and isinstance(args, str):
            cmd.append(args)
        elif args and isinstance(args, list):
            cmd.extend(args)

        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, update_env=self._get_pkg_cmd_environ(), capture=False)

    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.")

    def chpasswd(self, plist_in: list, hashed: bool):
        for name, password in plist_in:
            self.set_passwd(name, password, hashed=hashed)

    @staticmethod
    def get_proc_ppid(pid):
        """
        Return the parent pid of a process by checking ps
        """
        ppid, _ = subp.subp(["ps", "-oppid=", "-p", str(pid)])
        return int(ppid.strip())

    @staticmethod
    def get_mapped_device(blockdev: str) -> Optional[str]:
        return None

    @staticmethod
    def device_part_info(devpath: str) -> tuple:
        # FreeBSD doesn't know of sysfs so just get everything we need from
        # the device, like /dev/vtbd0p2.
        part = util.find_freebsd_part(devpath)
        if part:
            fpart = f"/dev/{part}"
            # Handle both GPT partitions and MBR slices with partitions
            m = re.search(
                r"^(?P<dev>/dev/.+)[sp](?P<part_slice>\d+[a-z]*)$", fpart
            )
            if m:
                return m["dev"], m["part_slice"]

        # the input is bogus and we need to bail
        raise ValueError(f"Invalid value for devpath: '{devpath}'")