Skip to content
Open
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
10 changes: 5 additions & 5 deletions src/pyinfra/facts/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -820,11 +820,11 @@ class Locales(FactBase[List[str]]):

@override
def command(self) -> str:
return "locale -a"

@override
def requires_command(self) -> str:
return "locale"
return (
"(command -v locale >/dev/null && locale -a) "
"|| (command -v localectl >/dev/null && localectl list-locales) "
"|| true"
)

default = list

Expand Down
77 changes: 50 additions & 27 deletions src/pyinfra/operations/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from pyinfra.api import FunctionCommand, OperationError, StringCommand, operation
from pyinfra.api.util import try_int
from pyinfra.connectors.util import remove_any_sudo_askpass_file
from pyinfra.facts.files import Directory, FindInFile, Link
from pyinfra.facts.files import Directory, File, Link
from pyinfra.facts.server import (
Groups,
Home,
Expand All @@ -30,7 +30,6 @@
Which,
)
from pyinfra.operations import crontab as crontab_

from . import (
apk,
apt,
Expand Down Expand Up @@ -997,12 +996,14 @@ def user(
def locale(
locale: str,
present=True,
is_default=False,
):
"""
Enable/Disable locale.

+ locale: name of the locale to enable/disable
+ present: whether this locale should be present or not
+ is_default: whether to set this locale as the system default

**Examples:**

Expand All @@ -1017,50 +1018,72 @@ def locale(
server.locale(
name="Ensure en_GB.UTF-8 locale is present",
locale="en_GB.UTF-8",
is_default=True,
)

"""

# if present is False and is_default is True, raise an error
if not present and is_default:
raise OperationError("Setting a locale as not present requires is_default=False")

locales = host.get_fact(Locales)

logger.debug("Enabled locales: {0}".format(locales))

locales_definitions_file = "/etc/locale.gen"
default_locale_file = "/etc/locale.conf"
locale_gen_exists = host.get_fact(File, path=locales_definitions_file)

# Find the matching line in /etc/locale.gen
matching_lines = host.get_fact(
FindInFile, path=locales_definitions_file, pattern=rf"^.*{locale}[[:space:]]\+.*$"
)
if present and locale in locales:
host.noop(f"Locale {locale} already enabled")
return

if not present and locale not in locales:
host.noop(f"Locale {locale} already disabled")
return

if locale_gen_exists:
has_locale_gen = host.get_fact(Which, command="locale-gen")
else:
has_locale_gen = None

if not matching_lines:
raise OperationError(f"Locale {locale} not found in {locales_definitions_file}")
if locale_gen_exists and has_locale_gen:
# Remove locale
if not present and locale in locales:
logger.debug(f"Removing locale {locale}")

if len(matching_lines) > 1:
raise OperationError(f"Multiple locales matches for {locale} in {locales_definitions_file}")
yield from files.line._inner(
path=locales_definitions_file,
line=f"^{locale}$",
present=False,
)

matching_line = matching_lines[0]
yield "locale-gen"

# Remove locale
if not present and locale in locales:
logger.debug(f"Removing locale {locale}")
# Add locale
if present and locale not in locales:
logger.debug(f"Adding locale {locale}")

yield from files.line._inner(
path=locales_definitions_file, line=f"^{matching_line}$", replace=f"# {matching_line}"
)
yield from files.line._inner(
path=locales_definitions_file,
line=f".*{locale}.*",
replace=f"{locale}",
present=True,
)

yield "locale-gen"
if is_default:
yield from files.line._inner(
line="LANG=.*",
replace=f"LANG={locale}",
path=default_locale_file,
)

# Add locale
if present and locale not in locales:
logger.debug(f"Adding locale {locale}")
yield "locale-gen"

yield from files.replace._inner(
path=locales_definitions_file,
text=f"^{matching_line}$",
replace=f"{matching_line}".replace("# ", ""),
)
return

yield "locale-gen"
raise OperationError("Locale management requires locale-gen on this system.")


@operation()
Expand Down
18 changes: 10 additions & 8 deletions tests/operations/server.locale/add.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
"fr_FR.UTF-8"
],
"facts": {
"files.File": {
"path=/etc/locale.gen": {
"mode": 644
}
},
"server.Which": {
"command=locale-gen": "/usr/sbin/locale-gen"
},
"files.FindInFile": {
"interpolate_variables=False, path=/etc/locale.gen, pattern=^.*fr_FR.UTF-8[[:space:]]\\+.*$": [
"# fr_FR.UTF-8 UTF-8"
],
"interpolate_variables=False, path=/etc/locale.gen, pattern=^.*fr_FR.UTF-8[[:space:]]\\+.*": [
"# fr_FR.UTF-8 UTF-8"
],
"interpolate_variables=False, path=/etc/locale.gen, pattern=^# fr_FR.UTF-8 UTF-8$": [
"interpolate_variables=False, path=/etc/locale.gen, pattern=^.*.*fr_FR.UTF-8.*.*$": [
"# fr_FR.UTF-8 UTF-8"
]
},
Expand All @@ -22,7 +24,7 @@
]
},
"commands": [
"sed -i.a-timestamp 's/^# fr_FR.UTF-8 UTF-8$/fr_FR.UTF-8 UTF-8/' /etc/locale.gen && rm -f /etc/locale.gen.a-timestamp",
"sed -i.a-timestamp 's/^.*.*fr_FR.UTF-8.*.*$/fr_FR.UTF-8/' /etc/locale.gen && rm -f /etc/locale.gen.a-timestamp",
"locale-gen"
],
"second_output_commands": [
Expand Down
40 changes: 40 additions & 0 deletions tests/operations/server.locale/add_and_set_as_default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"args": [
"fr_FR.UTF-8"
],
"kwargs": {"is_default": true},
"facts": {
"files.File": {
"path=/etc/locale.gen": {
"mode": 644
}
},
"server.Which": {
"command=locale-gen": "/usr/sbin/locale-gen"
},
"files.FindInFile": {
"interpolate_variables=False, path=/etc/locale.conf, pattern=^.*LANG=.*.*$": [
"LANG=en_US.UTF-8"
],
"interpolate_variables=False, path=/etc/locale.gen, pattern=^.*.*fr_FR.UTF-8.*.*$": [
"# fr_FR.UTF-8 UTF-8"
]
},
"server.Locales": [
"C",
"C.UTF-8",
"en_US.utf8",
"POSIX"
]
},
"commands": [
"sed -i.a-timestamp 's/^.*.*fr_FR.UTF-8.*.*$/fr_FR.UTF-8/' /etc/locale.gen && rm -f /etc/locale.gen.a-timestamp",
"sed -i.a-timestamp 's/^.*LANG=.*.*$/LANG=fr_FR.UTF-8/' /etc/locale.conf && rm -f /etc/locale.conf.a-timestamp",
"locale-gen"
],
"second_output_commands": [
"locale-gen"
],
"idempotent": false,
"disable_idempotent_warning_reason": "localgen is always executed"
}
25 changes: 15 additions & 10 deletions tests/operations/server.locale/remove.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,33 @@
"args": [
"fr_FR.UTF-8"
],
"kwargs": {
"present": false
},
"facts": {
"files.File": {
"path=/etc/locale.gen": {
"mode": 644
}
},
"server.Which": {
"command=locale-gen": "/usr/sbin/locale-gen"
},
"files.FindInFile": {
"interpolate_variables=False, path=/etc/locale.gen, pattern=^.*fr_FR.UTF-8[[:space:]]\\+.*$": [
"# fr_FR.UTF-8 UTF-8"
],
"interpolate_variables=False, path=/etc/locale.gen, pattern=^.*fr_FR.UTF-8[[:space:]]\\+.*": [
"# fr_FR.UTF-8 UTF-8"
],
"interpolate_variables=False, path=/etc/locale.gen, pattern=^# fr_FR.UTF-8 UTF-8$": [
"# fr_FR.UTF-8 UTF-8"
"interpolate_variables=False, path=/etc/locale.gen, pattern=^fr_FR.UTF-8$": [
"fr_FR.UTF-8"
]
},
"server.Locales": [
"C",
"C.UTF-8",
"fr_FR.utf8",
"fr_FR.UTF-8",
"en_US.utf8",
"POSIX"
]
},
"commands": [
"sed -i.a-timestamp 's/^# fr_FR.UTF-8 UTF-8$/fr_FR.UTF-8 UTF-8/' /etc/locale.gen && rm -f /etc/locale.gen.a-timestamp",
"sed -i.a-timestamp '/^fr_FR.UTF-8$/d' /etc/locale.gen && rm -f /etc/locale.gen.a-timestamp",
"locale-gen"
],
"second_output_commands": [
Expand Down