cmds.secrets.CompileTemplates: Fix conf line handling #138

Merged
Jan Lindemann merged 1 commit from jan/fix/20261012-cmds-secrets-compiletemplates-fix-conf-line-handling into master 2026-10-12 16:43:17 +02:00 AGit
4 changed files with 38 additions and 22 deletions

View file

@ -1,12 +1,16 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, override from argparse import ArgumentParser, ArgumentTypeError, Namespace
from typing import override
from .Cmd import Cmd, Parent from .Cmd import Cmd, Parent
from .lib.base import Attrs from .lib.base import Attrs
if TYPE_CHECKING: def octal_mode(s: str) -> int:
from argparse import ArgumentParser, Namespace try:
return int(s, 8)
except ValueError:
raise ArgumentTypeError(f'Not an octal mode: {s}')
class CmdCompileTemplates(Cmd): # export class CmdCompileTemplates(Cmd): # export
@ -30,5 +34,9 @@ class CmdCompileTemplates(Cmd): # export
'--group', '-g', default = None, help = 'Default output file group' '--group', '-g', default = None, help = 'Default output file group'
) )
parser.add_argument( parser.add_argument(
'--mode', '-m', default = None, help = 'Default output file mode' '--mode',
'-m',
default = None,
type = octal_mode,
help = 'Default output file mode (octal)',
) )

View file

@ -76,12 +76,10 @@ class FilesContext:
elif key == 'group': elif key == 'group':
ret.group = val or None ret.group = val or None
elif key == 'mode': elif key == 'mode':
# -- octal, with or without a leading zero
if val: if val:
try: try:
if re.fullmatch(r'0[0-7]+', val):
ret.mode = int(val, 8) ret.mode = int(val, 8)
else:
ret.mode = int(val, 0)
except ValueError: except ValueError:
ret.mode = None ret.mode = None
@ -126,6 +124,11 @@ class FilesContext:
attrs = self._parse_attributes(new_content) attrs = self._parse_attributes(new_content)
if attrs is None: if attrs is None:
attrs = default_attrs attrs = default_attrs
else:
# -- A conf line is the attribute spec of the output
# file, not part of its content
lines = new_content.splitlines(keepends = True)
new_content = ''.join(lines[1:])
if attrs is not None: if attrs is not None:
if attrs.owner is not None: if attrs.owner is not None:

View file

@ -846,7 +846,7 @@ options:
-h, --help show this help message and exit -h, --help show this help message and exit
--owner, -o OWNER Default output file owner (default: None) --owner, -o OWNER Default output file owner (default: None)
--group, -g GROUP Default output file group (default: None) --group, -g GROUP Default output file group (default: None)
--mode, -m MODE Default output file mode (default: None) --mode, -m MODE Default output file mode (octal) (default: None)
============= Running: jw-pkg.py -t ../../../.. --log-level info secrets install --help ============= Running: jw-pkg.py -t ../../../.. --log-level info secrets install --help
usage: jw-pkg.py secrets install [-h] [--only-missing] src [packages ...] usage: jw-pkg.py secrets install [-h] [--only-missing] src [packages ...]

View file

@ -175,7 +175,7 @@ def main() -> None:
os.makedirs(target + '/etc') os.makedirs(target + '/etc')
with open(target + '/etc/foo.conf.jw-tmpl', 'w') as f: with open(target + '/etc/foo.conf.jw-tmpl', 'w') as f:
f.write( f.write(
f'# conf: owner={USER}, group={GROUP}, mode=0640\n' f'# conf: owner={USER}, group={GROUP}, mode=640\n'
'host=HOSTNAME\n' 'host=HOSTNAME\n'
'token=TOKEN\n' 'token=TOKEN\n'
) )
@ -297,8 +297,15 @@ def main() -> None:
assert not os.path.exists(baz_secret) assert not os.path.exists(baz_secret)
assert stat.S_IMODE(os.stat(foo_secret).st_mode) == 0o400 assert stat.S_IMODE(os.stat(foo_secret).st_mode) == 0o400
# -- compile-templates: substitute the keys, apply the metadata # -- The file attributes can also be specified atop the
# .jw-secret; bar takes its mode from there
os.unlink(bar_secret)
with open(bar_secret, 'w') as f:
f.write(f'# conf: owner={USER}, group={GROUP}, mode=640\n')
f.write('PLAIN=plain-value\n')
# -- compile-templates: substitute the keys, apply the
# metadata; the conf lines win over the --mode default
out, _ = run_cli( out, _ = run_cli(
distro, [ distro, [
'secrets', 'secrets',
@ -307,6 +314,8 @@ def main() -> None:
USER, USER,
'--group', '--group',
GROUP, GROUP,
'--mode',
'400',
PKG, PKG,
] ]
) )
@ -314,13 +323,10 @@ def main() -> None:
st = os.stat(foo_out) st = os.stat(foo_out)
assert (st.st_uid, stat.S_IMODE(st.st_mode)) == (os.getuid(), 0o640), st assert (st.st_uid, stat.S_IMODE(st.st_mode)) == (os.getuid(), 0o640), st
with open(foo_out) as f: with open(foo_out) as f:
assert f.read() == ( assert f.read() == ('host=jannet\n'
f'# conf: owner={USER}, group={GROUP}, mode=0640\n' 'token=host-token\n')
'host=jannet\n'
'token=host-token\n'
)
st = os.stat(bar_out) st = os.stat(bar_out)
assert (st.st_uid, stat.S_IMODE(st.st_mode)) == (os.getuid(), 0o400), st assert (st.st_uid, stat.S_IMODE(st.st_mode)) == (os.getuid(), 0o640), st
with open(bar_out) as f: with open(bar_out) as f:
assert f.read() == 'plain=plain-value\n' assert f.read() == 'plain=plain-value\n'
@ -334,16 +340,15 @@ def main() -> None:
USER, USER,
'--group', '--group',
GROUP, GROUP,
'--mode',
'400',
PKG, PKG,
] ]
) )
assert out == [], out assert out == [], out
with open(foo_out) as f: with open(foo_out) as f:
assert f.read() == ( assert f.read() == ('host=jannet\n'
f'# conf: owner={USER}, group={GROUP}, mode=0640\n' 'token=host-token\n')
'host=jannet\n'
'token=host-token\n'
)
with open(bar_out) as f: with open(bar_out) as f:
assert f.read() == 'plain=plain-value\n' assert f.read() == 'plain=plain-value\n'