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
|
# This file is part of cloud-init. See LICENSE file for license information.
"""Ubuntu Drivers: Interact with third party drivers in Ubuntu."""
import os
from textwrap import dedent
from cloudinit.cloud import Cloud
from cloudinit.distros import Distro
try:
import debconf
HAS_DEBCONF = True
except ImportError:
debconf = None
HAS_DEBCONF = False
from logging import Logger
from cloudinit import log as logging
from cloudinit import subp, temp_utils, type_utils, util
from cloudinit.config import Config
from cloudinit.config.schema import MetaSchema, get_meta_doc
from cloudinit.settings import PER_INSTANCE
LOG = logging.getLogger(__name__)
distros = ["ubuntu"]
meta: MetaSchema = {
"id": "cc_ubuntu_drivers",
"name": "Ubuntu Drivers",
"title": "Interact with third party drivers in Ubuntu.",
"description": dedent(
"""\
This module interacts with the 'ubuntu-drivers' command to install
third party driver packages."""
),
"distros": distros,
"examples": [
dedent(
"""\
drivers:
nvidia:
license-accepted: true
"""
)
],
"frequency": PER_INSTANCE,
"activate_by_schema_keys": ["drivers"],
}
__doc__ = get_meta_doc(meta)
OLD_UBUNTU_DRIVERS_STDERR_NEEDLE = (
"ubuntu-drivers: error: argument <command>: invalid choice: 'install'"
)
# Use a debconf template to configure a global debconf variable
# (linux/nvidia/latelink) setting this to "true" allows the
# 'linux-restricted-modules' deb to accept the NVIDIA EULA and the package
# will automatically link the drivers to the running kernel.
NVIDIA_DEBCONF_CONTENT = """\
Template: linux/nvidia/latelink
Type: boolean
Default: true
Description: Late-link NVIDIA kernel modules?
Enable this to link the NVIDIA kernel modules in cloud-init and
make them available for use.
"""
X_LOADTEMPLATEFILE = "X_LOADTEMPLATEFILE"
def install_drivers(cfg, pkg_install_func, distro: Distro):
if not isinstance(cfg, dict):
raise TypeError(
"'drivers' config expected dict, found '%s': %s"
% (type_utils.obj_name(cfg), cfg)
)
cfgpath = "nvidia/license-accepted"
# Call translate_bool to ensure that we treat string values like "yes" as
# acceptance and _don't_ treat string values like "nah" as acceptance
# because they're True-ish
nv_acc = util.translate_bool(util.get_cfg_by_path(cfg, cfgpath))
if not nv_acc:
LOG.debug("Not installing NVIDIA drivers. %s=%s", cfgpath, nv_acc)
return
if not subp.which("ubuntu-drivers"):
LOG.debug(
"'ubuntu-drivers' command not available. "
"Installing ubuntu-drivers-common"
)
pkg_install_func(["ubuntu-drivers-common"])
driver_arg = "nvidia"
version_cfg = util.get_cfg_by_path(cfg, "nvidia/version")
if version_cfg:
driver_arg += ":{}".format(version_cfg)
LOG.debug(
"Installing and activating NVIDIA drivers (%s=%s, version=%s)",
cfgpath,
nv_acc,
version_cfg if version_cfg else "latest",
)
# Register and set debconf selection linux/nvidia/latelink = true
tdir = temp_utils.mkdtemp(dir=distro.get_tmp_exec_path(), needs_exe=True)
debconf_file = os.path.join(tdir, "nvidia.template")
try:
util.write_file(debconf_file, NVIDIA_DEBCONF_CONTENT)
with debconf.DebconfCommunicator("cloud-init") as dc:
dc.command(X_LOADTEMPLATEFILE, debconf_file)
except Exception as e:
util.logexc(
LOG, "Failed to register NVIDIA debconf template: %s", str(e)
)
raise
finally:
if os.path.isdir(tdir):
util.del_dir(tdir)
try:
subp.subp(["ubuntu-drivers", "install", "--gpgpu", driver_arg])
except subp.ProcessExecutionError as exc:
if OLD_UBUNTU_DRIVERS_STDERR_NEEDLE in exc.stderr:
LOG.warning(
"the available version of ubuntu-drivers is"
" too old to perform requested driver installation"
)
elif "No drivers found for installation." in exc.stdout:
LOG.warning("ubuntu-drivers found no drivers for installation")
raise
def handle(
name: str, cfg: Config, cloud: Cloud, log: Logger, args: list
) -> None:
if "drivers" not in cfg:
log.debug("Skipping module named %s, no 'drivers' key in config", name)
return
if not HAS_DEBCONF:
log.warning(
"Skipping module named %s, 'python3-debconf' is not installed",
name,
)
return
install_drivers(
cfg["drivers"], cloud.distro.install_packages, cloud.distro
)
|