cmds.secrets.FilesContext: Fix attrs precedence and macro scanning #155
2 changed files with 33 additions and 6 deletions
cmds.secrets.FilesContext: Ignore commented-out macros
All checks were successful
CI / Packaging - Kali Linux (pull_request) Successful in 4m37s
CI / Packaging - OpenSUSE Tumbleweed (pull_request) Successful in 5m6s
CI / Packaging test (pull_request) Successful in 0s
CI / Packaging - Kali Linux (push) Successful in 4m39s
CI / Packaging - OpenSUSE Tumbleweed (push) Successful in 4m54s
CI / Packaging test (push) Successful in 0s
All checks were successful
CI / Packaging - Kali Linux (pull_request) Successful in 4m37s
CI / Packaging - OpenSUSE Tumbleweed (pull_request) Successful in 5m6s
CI / Packaging test (pull_request) Successful in 0s
CI / Packaging - Kali Linux (push) Successful in 4m39s
CI / Packaging - OpenSUSE Tumbleweed (push) Successful in 4m54s
CI / Packaging test (push) Successful in 0s
The unresolved-macro check scans the substituted content for __MACRO__ names, and a commented-out reference fails the compile like a live one. A disabled config section keeps its macros in the comments as documentation, and the matching store keys are retired along with it, so compiling the template fails on lines that no longer take effect. Skip comment lines when scanning for unresolved macros. The integration test gains a target whose template carries a commented-out macro without a store entry. Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v1.4.0 Signed-off-by: Jan Lindemann <jan@janware.com>
commit
6398406a5f
|
|
@ -138,7 +138,13 @@ class FilesContext:
|
||||||
for key, val in replace.items():
|
for key, val in replace.items():
|
||||||
new_content = new_content.replace(key, val)
|
new_content = new_content.replace(key, val)
|
||||||
|
|
||||||
unresolved = sorted(set(re.findall(r'__[A-Z0-9_]+__', new_content)))
|
# -- A commented-out macro is documentation, not a reference;
|
||||||
|
# it must not fail the compile
|
||||||
|
live = '\n'.join(
|
||||||
|
line for line in new_content.splitlines()
|
||||||
|
if not line.lstrip().startswith('#')
|
||||||
|
)
|
||||||
|
unresolved = sorted(set(re.findall(r'__[A-Z0-9_]+__', live)))
|
||||||
if unresolved:
|
if unresolved:
|
||||||
raise Exception(f'Unresolved macros in {src}: {", ".join(unresolved)}')
|
raise Exception(f'Unresolved macros in {src}: {", ".join(unresolved)}')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,14 @@ def main() -> None:
|
||||||
with open(target + '/etc/corge.conf.jw-tmpl', 'w') as f:
|
with open(target + '/etc/corge.conf.jw-tmpl', 'w') as f:
|
||||||
f.write(f'# conf: owner={USER}, group={GROUP}, mode=640\n'
|
f.write(f'# conf: owner={USER}, group={GROUP}, mode=640\n'
|
||||||
'key=KEY\n')
|
'key=KEY\n')
|
||||||
|
# -- quux carries a commented-out macro without a store entry:
|
||||||
|
# the compile must succeed, the comment stays verbatim
|
||||||
|
with open(target + '/etc/quux.conf.jw-tmpl', 'w') as f:
|
||||||
|
f.write(
|
||||||
|
f'# conf: owner={USER}, group={GROUP}, mode=640\n'
|
||||||
|
'# retired=__RETIRED__\n'
|
||||||
|
'live=LIVE\n'
|
||||||
|
)
|
||||||
# -- qux references a macro that has no store entry: the
|
# -- qux references a macro that has no store entry: the
|
||||||
# compile must fail rather than emit a literal macro
|
# compile must fail rather than emit a literal macro
|
||||||
with open(target + '/etc/qux.conf.jw-tmpl', 'w') as f:
|
with open(target + '/etc/qux.conf.jw-tmpl', 'w') as f:
|
||||||
|
|
@ -214,10 +222,12 @@ def main() -> None:
|
||||||
bar_secret = target + '/etc/bar.conf.jw-secret'
|
bar_secret = target + '/etc/bar.conf.jw-secret'
|
||||||
baz_secret = target + '/etc/baz.conf.jw-secret'
|
baz_secret = target + '/etc/baz.conf.jw-secret'
|
||||||
corge_secret = target + '/etc/corge.conf.jw-secret'
|
corge_secret = target + '/etc/corge.conf.jw-secret'
|
||||||
|
quux_secret = target + '/etc/quux.conf.jw-secret'
|
||||||
foo_out = target + '/etc/foo.conf'
|
foo_out = target + '/etc/foo.conf'
|
||||||
bar_out = target + '/etc/bar.conf'
|
bar_out = target + '/etc/bar.conf'
|
||||||
baz_out = target + '/etc/baz.conf'
|
baz_out = target + '/etc/baz.conf'
|
||||||
corge_out = target + '/etc/corge.conf'
|
corge_out = target + '/etc/corge.conf'
|
||||||
|
quux_out = target + '/etc/quux.conf'
|
||||||
|
|
||||||
# -- The GPG-encrypted tar store in canonical layout. Member
|
# -- The GPG-encrypted tar store in canonical layout. Member
|
||||||
# names are <prefix>/<target path without leading slash>
|
# names are <prefix>/<target path without leading slash>
|
||||||
|
|
@ -239,6 +249,8 @@ def main() -> None:
|
||||||
'PLAIN=plain-value\n',
|
'PLAIN=plain-value\n',
|
||||||
f'default/{trel}/etc/corge.conf.jw-secret': \
|
f'default/{trel}/etc/corge.conf.jw-secret': \
|
||||||
'KEY=corge-value\n',
|
'KEY=corge-value\n',
|
||||||
|
f'default/{trel}/etc/quux.conf.jw-secret': \
|
||||||
|
'LIVE=live-value\n',
|
||||||
# A member that is not a secret of the fixture
|
# A member that is not a secret of the fixture
|
||||||
# package: install must not extract it
|
# package: install must not extract it
|
||||||
f'default/{trel}/etc/cert.pem': 'decoy certificate\n',
|
f'default/{trel}/etc/cert.pem': 'decoy certificate\n',
|
||||||
|
|
@ -256,6 +268,7 @@ def main() -> None:
|
||||||
bar_tmpl,
|
bar_tmpl,
|
||||||
baz_tmpl,
|
baz_tmpl,
|
||||||
target + '/etc/corge.conf.jw-tmpl',
|
target + '/etc/corge.conf.jw-tmpl',
|
||||||
|
target + '/etc/quux.conf.jw-tmpl',
|
||||||
target + '/etc/readme.txt',
|
target + '/etc/readme.txt',
|
||||||
],
|
],
|
||||||
PKG2: [qux_tmpl],
|
PKG2: [qux_tmpl],
|
||||||
|
|
@ -266,7 +279,8 @@ def main() -> None:
|
||||||
|
|
||||||
out, _ = run_cli(distro, ['secrets', 'list-templates', PKG])
|
out, _ = run_cli(distro, ['secrets', 'list-templates', PKG])
|
||||||
assert out == [foo_tmpl, bar_tmpl, baz_tmpl,
|
assert out == [foo_tmpl, bar_tmpl, baz_tmpl,
|
||||||
target + '/etc/corge.conf.jw-tmpl'], out
|
target + '/etc/corge.conf.jw-tmpl',
|
||||||
|
target + '/etc/quux.conf.jw-tmpl'], out
|
||||||
|
|
||||||
# -- list-secrets: nothing installed yet
|
# -- list-secrets: nothing installed yet
|
||||||
|
|
||||||
|
|
@ -274,7 +288,8 @@ def main() -> None:
|
||||||
assert out == [], out
|
assert out == [], out
|
||||||
|
|
||||||
out, _ = run_cli(distro, ['secrets', 'list-secrets', '--all', PKG])
|
out, _ = run_cli(distro, ['secrets', 'list-secrets', '--all', PKG])
|
||||||
assert out == [foo_secret, bar_secret, baz_secret, corge_secret], out
|
assert out == [foo_secret, bar_secret, baz_secret,
|
||||||
|
corge_secret, quux_secret], out
|
||||||
|
|
||||||
# -- list-compilation-output: nothing compiled yet
|
# -- list-compilation-output: nothing compiled yet
|
||||||
|
|
||||||
|
|
@ -282,7 +297,7 @@ def main() -> None:
|
||||||
assert out == [], out
|
assert out == [], out
|
||||||
|
|
||||||
out, _ = run_cli(distro, ['secrets', 'list-compilation-output', '--all', PKG])
|
out, _ = run_cli(distro, ['secrets', 'list-compilation-output', '--all', PKG])
|
||||||
assert out == [foo_out, bar_out, baz_out, corge_out], out
|
assert out == [foo_out, bar_out, baz_out, corge_out, quux_out], out
|
||||||
|
|
||||||
# -- install: decrypt the tarball, keep the host-specific entry
|
# -- install: decrypt the tarball, keep the host-specific entry
|
||||||
# over the default one, extract only the needed secrets
|
# over the default one, extract only the needed secrets
|
||||||
|
|
@ -302,7 +317,7 @@ def main() -> None:
|
||||||
assert stat.S_IMODE(os.stat(foo_secret).st_mode) == 0o400
|
assert stat.S_IMODE(os.stat(foo_secret).st_mode) == 0o400
|
||||||
|
|
||||||
out, _ = run_cli(distro, ['secrets', 'list-secrets', PKG])
|
out, _ = run_cli(distro, ['secrets', 'list-secrets', PKG])
|
||||||
assert out == [foo_secret, bar_secret, corge_secret], out
|
assert out == [foo_secret, bar_secret, corge_secret, quux_secret], out
|
||||||
|
|
||||||
if HAVE_TARGPG:
|
if HAVE_TARGPG:
|
||||||
# -- a rooted targpg:// URI is rejected: install addresses
|
# -- a rooted targpg:// URI is rejected: install addresses
|
||||||
|
|
@ -373,6 +388,11 @@ def main() -> None:
|
||||||
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(), 0o400), st
|
||||||
with open(corge_out) as f:
|
with open(corge_out) as f:
|
||||||
assert f.read() == 'key=corge-value\n'
|
assert f.read() == 'key=corge-value\n'
|
||||||
|
# -- the commented-out macro does not fail the compile and
|
||||||
|
# stays in the output verbatim
|
||||||
|
with open(quux_out) as f:
|
||||||
|
assert f.read() == ('# retired=__RETIRED__\n'
|
||||||
|
'live=live-value\n')
|
||||||
|
|
||||||
# -- compile-templates is idempotent
|
# -- compile-templates is idempotent
|
||||||
|
|
||||||
|
|
@ -428,7 +448,7 @@ def main() -> None:
|
||||||
assert not os.path.exists(qux_out)
|
assert not os.path.exists(qux_out)
|
||||||
|
|
||||||
out, _ = run_cli(distro, ['secrets', 'list-compilation-output', PKG])
|
out, _ = run_cli(distro, ['secrets', 'list-compilation-output', PKG])
|
||||||
assert out == [foo_out, bar_out, baz_out, corge_out], out
|
assert out == [foo_out, bar_out, baz_out, corge_out, quux_out], out
|
||||||
|
|
||||||
# -- rm-compilation-output: drop the compiled files
|
# -- rm-compilation-output: drop the compiled files
|
||||||
|
|
||||||
|
|
@ -438,6 +458,7 @@ def main() -> None:
|
||||||
assert not os.path.exists(bar_out)
|
assert not os.path.exists(bar_out)
|
||||||
assert not os.path.exists(baz_out)
|
assert not os.path.exists(baz_out)
|
||||||
assert not os.path.exists(corge_out)
|
assert not os.path.exists(corge_out)
|
||||||
|
assert not os.path.exists(quux_out)
|
||||||
|
|
||||||
print('All secrets command tests passed')
|
print('All secrets command tests passed')
|
||||||
finally:
|
finally:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue