Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions cloudinit/config/cc_keyboard.py
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions cloudinit/distros/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,21 @@ def manage_service(self, action, service):
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):
"""
Expand Down
7 changes: 7 additions & 0 deletions cloudinit/distros/debian.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,13 @@ def update_package_sources(self):
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):
Expand Down
1 change: 1 addition & 0 deletions config/cloud.cfg.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ cloud_config_modules:
{% endif %}
{% if variant not in ["photon"] %}
- ssh-import-id
- keyboard
- locale
{% endif %}
- set-passwords
Expand Down
1 change: 1 addition & 0 deletions doc/rtd/topics/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions tests/integration_tests/modules/test_keyboard.py
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions tests/unittests/config/test_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def test_get_schema_coalesces_known_schema(self):
"cc_apk_configure",
"cc_apt_configure",
"cc_bootcmd",
"cc_keyboard",
"cc_locale",
"cc_ntp",
"cc_resizefs",
Expand Down
1 change: 1 addition & 0 deletions tools/.github-cla-signers
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ mamercad
manuelisimo
marlluslustosa
matthewruffell
maxnet
mitechie
nazunalika
nicolasbock
Expand Down