summaryrefslogtreecommitdiff
path: root/cloudinit/distros/opensuse.py
blob: 38307c9134f3f79cbbde1912ba45351d74eaa4c8 (plain)
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
#    Copyright (C) 2017 SUSE LLC
#    Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
#
#    Author: Robert Schweikert <rjschwei@suse.com>
#    Author: Juerg Haefliger <juerg.haefliger@hp.com>
#
#    Leaning very heavily on the RHEL and Debian implementation
#
# This file is part of cloud-init. See LICENSE file for license information.

import os

from cloudinit import distros, helpers
from cloudinit import log as logging
from cloudinit import subp, util
from cloudinit.distros import rhel_util as rhutil
from cloudinit.distros.parsers.hostname import HostnameConf
from cloudinit.settings import PER_INSTANCE

LOG = logging.getLogger(__name__)


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"
    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",
            },
        }
    }

    def __init__(self, name, cfg, paths):
        distros.Distro.__init__(self, name, cfg, paths)
        self._runner = helpers.Runners(paths)
        self.osfamily = "suse"
        self.update_method = None
        self.read_only_root = False
        cfg["ssh_svcname"] = "sshd"
        if self.uses_systemd():
            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}
        else:
            if not out_fn:
                out_fn = self.locale_conf_fn
            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
        )

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

        self._set_update_method()
        if self.read_only_root and not self.update_method == "transactional":
            LOG.error(
                "Package operation requested but read only root "
                "without btrfs and transactional-updata"
            )
            return

        # No user interaction possible, enable non-interactive mode
        if self.update_method == "zypper":
            cmd = ["zypper", "--non-interactive"]
        else:
            cmd = [
                "transactional-update",
                "--non-interactive",
                "--drop-if-no-change",
                "pkg",
            ]

        # Command is the operation, such as install
        if command == "upgrade":
            command = "update"
        if (
            not pkgs
            and self.update_method == "transactional"
            and command == "update"
        ):
            command = "up"
            cmd = [
                "transactional-update",
                "--non-interactive",
                "--drop-if-no-change",
            ]
        # Repo refresh only modifies data in the read-write path,
        # always uses zypper
        if command == "refresh":
            # Repo refresh is a zypper only option, ignore the t-u setting
            cmd = ["zypper", "--non-interactive"]
        cmd.append(command)

        # args are the arguments to the command, not global options
        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, capture=False)

        if self.update_method == "transactional":
            LOG.info(
                "To use/activate the installed packages reboot the system"
            )

    def set_timezone(self, tz):
        tz_file = self._find_tz_file(tz)
        if self.uses_systemd():
            # Currently, timedatectl complains if invoked during startup
            # so for compatibility, create the link manually.
            util.del_file(self.tz_local_fn)
            util.sym_link(tz_file, self.tz_local_fn)
        else:
            # Adjust the sysconfig clock zone setting
            clock_cfg = {
                "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 _read_hostname(self, filename, default=None):
        if self.uses_systemd() and filename.endswith("/previous-hostname"):
            return util.load_file(filename).strip()
        elif self.uses_systemd():
            (out, _err) = subp.subp(["hostname"])
            if len(out):
                return out
            else:
                return default
        else:
            try:
                conf = self._read_hostname_conf(filename)
                hostname = conf.hostname
            except IOError:
                pass
            if not hostname:
                return default
            return hostname

    def _get_localhost_ip(self):
        return "127.0.1.1"

    def _read_hostname_conf(self, filename):
        conf = HostnameConf(util.load_file(filename))
        conf.parse()
        return conf

    def _read_system_hostname(self):
        if self.uses_systemd():
            host_fn = self.systemd_hostname_conf_fn
        else:
            host_fn = self.hostname_conf_fn
        return (host_fn, self._read_hostname(host_fn))

    def _set_update_method(self):
        """Decide if we want to use transactional-update or zypper"""
        if self.update_method is None:
            result = util.get_mount_info("/")
            fs_type = ""
            if result:
                (devpth, fs_type, mount_point) = result
                # Check if the file system is read only
                mounts = util.load_file("/proc/mounts").split("\n")
                for mount in mounts:
                    if mount.startswith(devpth):
                        mount_info = mount.split()
                        if mount_info[1] != mount_point:
                            continue
                        self.read_only_root = mount_info[3].startswith("ro")
                        break
                if fs_type.lower() == "btrfs" and os.path.exists(
                    "/usr/sbin/transactional-update"
                ):
                    self.update_method = "transactional"
                else:
                    self.update_method = "zypper"
            else:
                LOG.info(
                    "Could not determine filesystem type of '/' using zypper"
                )
                self.update_method = "zypper"

    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)])
        else:
            conf = None
            try:
                # Try to update the previous one
                # so lets see if we can read it first.
                conf = self._read_hostname_conf(filename)
            except IOError:
                pass
            if not conf:
                conf = HostnameConf("")
            conf.set_hostname(hostname)
            util.write_file(filename, str(conf), 0o644)

    @property
    def preferred_ntp_clients(self):
        """The preferred ntp client is dependent on the version."""

        # Allow distro to determine the preferred ntp client list
        if not self._preferred_ntp_clients:
            distro_info = util.system_info()["dist"]
            name = distro_info[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
            ):
                self._preferred_ntp_clients = [
                    "chrony",
                    "systemd-timesyncd",
                    "ntp",
                ]
            else:
                self._preferred_ntp_clients = [
                    "ntp",
                    "systemd-timesyncd",
                    "chrony",
                ]

        return self._preferred_ntp_clients


# vi: ts=4 expandtab