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
|
# 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 import log as logging
from cloudinit import subp, temp_utils, type_utils, util
from cloudinit.config.schema import get_meta_doc, validate_cloudconfig_schema
from cloudinit.settings import PER_INSTANCE
LOG = logging.getLogger(__name__)
frequency = PER_INSTANCE
distros = ["ubuntu"]
meta = {
"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": frequency,
}
schema = {
"type": "object",
"properties": {
"drivers": {
"type": "object",
"additionalProperties": False,
"properties": {
"nvidia": {
"type": "object",
"additionalProperties": False,
"required": ["license-accepted"],
"properties": {
"license-accepted": {
"type": "boolean",
"description": (
"Do you accept the NVIDIA driver license?"
),
},
"version": {
"type": "string",
"description": (
"The version of the driver to install (e.g."
' "390", "410"). Defaults to the latest'
" version."
),
},
},
},
},
},
},
}
OLD_UBUNTU_DRIVERS_STDERR_NEEDLE = (
"ubuntu-drivers: error: argument <command>: invalid choice: 'install'"
)
__doc__ = get_meta_doc(meta, schema) # Supplement python help()
# 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.
# EOL_XENIAL: can then drop this script and use python3-debconf which is only
# available in Bionic and later. Can't use python3-debconf currently as it
# isn't in Xenial and doesn't yet support X_LOADTEMPLATEFILE debconf command.
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.
"""
NVIDIA_DRIVER_LATELINK_DEBCONF_SCRIPT = """\
#!/bin/sh
# Allow cloud-init to trigger EULA acceptance via registering a debconf
# template to set linux/nvidia/latelink true
. /usr/share/debconf/confmodule
db_x_loadtemplatefile "$1" cloud-init
"""
def install_drivers(cfg, pkg_install_func):
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(needs_exe=True)
debconf_file = os.path.join(tdir, "nvidia.template")
debconf_script = os.path.join(tdir, "nvidia-debconf.sh")
try:
util.write_file(debconf_file, NVIDIA_DEBCONF_CONTENT)
util.write_file(
debconf_script,
util.encode_text(NVIDIA_DRIVER_LATELINK_DEBCONF_SCRIPT),
mode=0o755,
)
subp.subp([debconf_script, 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, cfg, cloud, log, _args):
if "drivers" not in cfg:
log.debug("Skipping module named %s, no 'drivers' key in config", name)
return
validate_cloudconfig_schema(cfg, schema)
install_drivers(cfg["drivers"], cloud.distro.install_packages)
|