diff --git a/src/python/jw/pkg/cmds/secrets/lib/FilesContext.py b/src/python/jw/pkg/cmds/secrets/lib/FilesContext.py index 7f79afce..0be62b5d 100644 --- a/src/python/jw/pkg/cmds/secrets/lib/FilesContext.py +++ b/src/python/jw/pkg/cmds/secrets/lib/FilesContext.py @@ -138,7 +138,13 @@ class FilesContext: for key, val in replace.items(): 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: raise Exception(f'Unresolved macros in {src}: {", ".join(unresolved)}') diff --git a/test/integration/jw_pkg/secrets/test.py b/test/integration/jw_pkg/secrets/test.py index a8706bd2..97c431e2 100644 --- a/test/integration/jw_pkg/secrets/test.py +++ b/test/integration/jw_pkg/secrets/test.py @@ -195,6 +195,14 @@ def main() -> None: with open(target + '/etc/corge.conf.jw-tmpl', 'w') as f: f.write(f'# conf: owner={USER}, group={GROUP}, mode=640\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 # compile must fail rather than emit a literal macro 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' baz_secret = target + '/etc/baz.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' bar_out = target + '/etc/bar.conf' baz_out = target + '/etc/baz.conf' corge_out = target + '/etc/corge.conf' + quux_out = target + '/etc/quux.conf' # -- The GPG-encrypted tar store in canonical layout. Member # names are / @@ -239,6 +249,8 @@ def main() -> None: 'PLAIN=plain-value\n', f'default/{trel}/etc/corge.conf.jw-secret': \ '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 # package: install must not extract it f'default/{trel}/etc/cert.pem': 'decoy certificate\n', @@ -256,6 +268,7 @@ def main() -> None: bar_tmpl, baz_tmpl, target + '/etc/corge.conf.jw-tmpl', + target + '/etc/quux.conf.jw-tmpl', target + '/etc/readme.txt', ], PKG2: [qux_tmpl], @@ -266,7 +279,8 @@ def main() -> None: out, _ = run_cli(distro, ['secrets', 'list-templates', PKG]) 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 @@ -274,7 +288,8 @@ def main() -> None: assert out == [], out 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 @@ -282,7 +297,7 @@ def main() -> None: assert out == [], out 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 # 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 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: # -- 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 with open(corge_out) as f: 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 @@ -428,7 +448,7 @@ def main() -> None: assert not os.path.exists(qux_out) 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 @@ -438,6 +458,7 @@ def main() -> None: assert not os.path.exists(bar_out) assert not os.path.exists(baz_out) assert not os.path.exists(corge_out) + assert not os.path.exists(quux_out) print('All secrets command tests passed') finally: