summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cloudinit/config/cc_keyboard.py125
-rwxr-xr-xcloudinit/distros/__init__.py15
-rw-r--r--cloudinit/distros/debian.py7
-rw-r--r--config/cloud.cfg.tmpl1
-rw-r--r--doc/rtd/topics/modules.rst1
-rw-r--r--tests/integration_tests/modules/test_keyboard.py17
-rw-r--r--tests/unittests/config/test_schema.py1
-rw-r--r--tools/.github-cla-signers1
8 files changed, 168 insertions, 0 deletions
diff --git a/cloudinit/config/cc_keyboard.py b/cloudinit/config/cc_keyboard.py
new file mode 100644
index 00000000..17eb9a54
--- /dev/null
+++ b/cloudinit/config/cc_keyboard.py
@@ -0,0 +1,125 @@
+# Copyright (c) 2022 Floris Bos
+#
+# Author: Floris Bos <bos@je-eigen-domein.nl>
+#
+# This file is part of cloud-init. See LICENSE file for license information.
+
+"""keyboard: set keyboard layout"""
+
+from textwrap import dedent
+
+from cloudinit import distros
+from cloudinit import log as logging
+from cloudinit.config.schema import get_meta_doc, validate_cloudconfig_schema
+from cloudinit.settings import PER_INSTANCE
+
+frequency = PER_INSTANCE
+
+# FIXME: setting keyboard layout should be supported by all OSes.
+# But currently only implemented for Linux distributions that use systemd.
+osfamilies = ["arch", "debian", "redhat", "suse"]
+distros = distros.Distro.expand_osfamily(osfamilies)
+
+DEFAULT_KEYBOARD_MODEL = "pc105"
+
+meta = {
+ "id": "cc_keyboard",
+ "name": "Keyboard",
+ "title": "Set keyboard layout",
+ "description": dedent(
+ """\
+ Handle keyboard configuration.
+ """
+ ),
+ "distros": distros,
+ "examples": [
+ dedent(
+ """\
+ # Set keyboard layout to "us"
+ keyboard:
+ layout: us
+ """
+ ),
+ dedent(
+ """\
+ # Set specific keyboard layout, model, variant, options
+ keyboard:
+ layout: de
+ model: pc105
+ variant: nodeadkeys
+ options: compose:rwin
+ """
+ ),
+ ],
+ "frequency": frequency,
+}
+
+
+schema = {
+ "type": "object",
+ "properties": {
+ "keyboard": {
+ "type": "object",
+ "properties": {
+ "layout": {
+ "type": "string",
+ "description": dedent(
+ """\
+ Required. Keyboard layout. Corresponds to XKBLAYOUT.
+ """
+ ),
+ },
+ "model": {
+ "type": "string",
+ "default": DEFAULT_KEYBOARD_MODEL,
+ "description": dedent(
+ """\
+ Optional. Keyboard model. Corresponds to XKBMODEL.
+ """
+ ),
+ },
+ "variant": {
+ "type": "string",
+ "description": dedent(
+ """\
+ Optional. Keyboard variant. Corresponds to XKBVARIANT.
+ """
+ ),
+ },
+ "options": {
+ "type": "string",
+ "description": dedent(
+ """\
+ Optional. Keyboard options. Corresponds to XKBOPTIONS.
+ """
+ ),
+ },
+ },
+ "required": ["layout"],
+ "additionalProperties": False,
+ }
+ },
+}
+
+__doc__ = get_meta_doc(meta, schema)
+
+LOG = logging.getLogger(__name__)
+
+
+def handle(name, cfg, cloud, log, args):
+ if "keyboard" not in cfg:
+ LOG.debug(
+ "Skipping module named %s, no 'keyboard' section found", name
+ )
+ return
+ validate_cloudconfig_schema(cfg, schema)
+ kb_cfg = cfg["keyboard"]
+ layout = kb_cfg["layout"]
+ model = kb_cfg.get("model", DEFAULT_KEYBOARD_MODEL)
+ variant = kb_cfg.get("variant", "")
+ options = kb_cfg.get("options", "")
+ LOG.debug("Setting keyboard layout to '%s'", layout)
+ cloud.distro.set_keymap(layout, model, variant, options)
+
+
+# vi: ts=4 expandtab
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index de000b52..a261c16e 100755
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -881,6 +881,21 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
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):
"""
diff --git a/cloudinit/distros/debian.py b/cloudinit/distros/debian.py
index 9effa0a0..6dc1ad40 100644
--- a/cloudinit/distros/debian.py
+++ b/cloudinit/distros/debian.py
@@ -300,6 +300,13 @@ class Distro(distros.Distro):
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):
diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl
index 741b23d5..37c223f3 100644
--- a/config/cloud.cfg.tmpl
+++ b/config/cloud.cfg.tmpl
@@ -107,6 +107,7 @@ cloud_config_modules:
{% endif %}
{% if variant not in ["photon"] %}
- ssh-import-id
+ - keyboard
- locale
{% endif %}
- set-passwords
diff --git a/doc/rtd/topics/modules.rst b/doc/rtd/topics/modules.rst
index 3ca6b9e3..8ee4fe57 100644
--- a/doc/rtd/topics/modules.rst
+++ b/doc/rtd/topics/modules.rst
@@ -23,6 +23,7 @@ Modules
.. automodule:: cloudinit.config.cc_growpart
.. automodule:: cloudinit.config.cc_grub_dpkg
.. automodule:: cloudinit.config.cc_install_hotplug
+.. automodule:: cloudinit.config.cc_keyboard
.. automodule:: cloudinit.config.cc_keys_to_console
.. automodule:: cloudinit.config.cc_landscape
.. automodule:: cloudinit.config.cc_locale
diff --git a/tests/integration_tests/modules/test_keyboard.py b/tests/integration_tests/modules/test_keyboard.py
new file mode 100644
index 00000000..7db35014
--- /dev/null
+++ b/tests/integration_tests/modules/test_keyboard.py
@@ -0,0 +1,17 @@
+import pytest
+
+USER_DATA = """\
+#cloud-config
+keyboard:
+ layout: de
+ model: pc105
+ variant: nodeadkeys
+ options: compose:rwin
+"""
+
+
+class TestKeyboard:
+ @pytest.mark.user_data(USER_DATA)
+ def test_keyboard(self, client):
+ lc = client.execute("localectl")
+ assert "X11 Layout: de" in lc
diff --git a/tests/unittests/config/test_schema.py b/tests/unittests/config/test_schema.py
index 822efe5a..93206bdd 100644
--- a/tests/unittests/config/test_schema.py
+++ b/tests/unittests/config/test_schema.py
@@ -87,6 +87,7 @@ class GetSchemaTest(CiTestCase):
"cc_apk_configure",
"cc_apt_configure",
"cc_bootcmd",
+ "cc_keyboard",
"cc_locale",
"cc_ntp",
"cc_resizefs",
diff --git a/tools/.github-cla-signers b/tools/.github-cla-signers
index e4b64b0d..62e6017a 100644
--- a/tools/.github-cla-signers
+++ b/tools/.github-cla-signers
@@ -52,6 +52,7 @@ mamercad
manuelisimo
marlluslustosa
matthewruffell
+maxnet
mitechie
nazunalika
nicolasbock