diff --git a/openvpncertupdate.py b/openvpncertupdate.py index 7f734c7..79654e2 100644 --- a/openvpncertupdate.py +++ b/openvpncertupdate.py @@ -984,13 +984,13 @@ class CursesApp: class CliRunner: """Non-interactive runner for scripting / cron use.""" - def create(self, cn: str, email_addr: str) -> None: - self._issue(cn, email_addr, is_renewal=False) + def create(self, cn: str, email_addr: str, send_email_flag: bool = True) -> None: + self._issue(cn, email_addr, is_renewal=False, send_email_flag=send_email_flag) - def reissue(self, cn: str, email_addr: str) -> None: + def reissue(self, cn: str, email_addr: str, send_email_flag: bool = True) -> None: # Prefer the caller-supplied email; fall back to stored metadata. final_email = email_addr or get_email(EASYRSA_PKI_DIR, cn) - self._issue(cn, final_email, is_renewal=True) + self._issue(cn, final_email, is_renewal=True, send_email_flag=send_email_flag) def revoke(self, cn: str) -> None: print(f"Revoking {cn}…", file=sys.stderr) @@ -1013,7 +1013,8 @@ class CliRunner: sys.exit(1) print(f"CRL updated → {CRL_DEST_PATH}") - def _issue(self, cn: str, email_addr: str, is_renewal: bool) -> None: + def _issue(self, cn: str, email_addr: str, is_renewal: bool, + send_email_flag: bool = True) -> None: password = generate_password() if is_renewal: @@ -1055,7 +1056,7 @@ class CliRunner: except CryptgeonError as exc: print(f"warning: Cryptgeon failed: {exc}", file=sys.stderr) - if email_addr: + if email_addr and send_email_flag: try: send_email(email_addr, cn, one_time_url or password, ovpn_path, MAIL_FROM, MAIL_SUBJECT, EMAIL_TEMPLATE_PATH, MAIL_BINARY) @@ -1082,6 +1083,10 @@ def _build_parser() -> argparse.ArgumentParser: group.add_argument("--gen-crl", action="store_true", help="Regenerate and copy CRL") parser.add_argument("--email", metavar="EMAIL", help="Email address (required for --create; optional for --reissue)") + parser.add_argument("--send-email", dest="send_email", action="store_true", + default=True, help="Send email after issuing cert (default)") + parser.add_argument("--no-send-email", dest="send_email", action="store_false", + help="Skip email delivery; print URL to stdout instead") return parser @@ -1097,9 +1102,9 @@ def main() -> None: if args.create: if not args.email: parser.error("--email is required with --create") - CliRunner().create(args.create, args.email) + CliRunner().create(args.create, args.email, send_email_flag=args.send_email) elif args.reissue: - CliRunner().reissue(args.reissue, args.email or "") + CliRunner().reissue(args.reissue, args.email or "", send_email_flag=args.send_email) elif args.revoke: CliRunner().revoke(args.revoke) elif args.gen_crl: diff --git a/tests/test_cli.py b/tests/test_cli.py index a87f63e..3364d64 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -165,7 +165,7 @@ def test_main_dispatches_create(monkeypatch): runner = MagicMock() monkeypatch.setattr("openvpncertupdate.CliRunner", lambda: runner) main() - runner.create.assert_called_once_with("alice", "a@b.com") + runner.create.assert_called_once_with("alice", "a@b.com", send_email_flag=True) def test_main_dispatches_reissue(monkeypatch): @@ -173,7 +173,7 @@ def test_main_dispatches_reissue(monkeypatch): runner = MagicMock() monkeypatch.setattr("openvpncertupdate.CliRunner", lambda: runner) main() - runner.reissue.assert_called_once_with("bob", "") + runner.reissue.assert_called_once_with("bob", "", send_email_flag=True) def test_main_dispatches_revoke(monkeypatch): @@ -198,3 +198,25 @@ def test_main_no_args_launches_tui(monkeypatch): monkeypatch.setattr("openvpncertupdate.CursesApp", lambda: app) main() app.run.assert_called_once_with() + + +# --------------------------------------------------------------------------- +# --send-email / --no-send-email +# --------------------------------------------------------------------------- + +def test_no_send_email_skips_delivery(monkeypatch, capsys): + mocks = _patch_issue(monkeypatch) + monkeypatch.setattr("openvpncertupdate.get_email", MagicMock(return_value="")) + CliRunner().create("alice", "alice@example.com", send_email_flag=False) + mocks["send_email"].assert_not_called() + out = capsys.readouterr().out + assert "config:" in out + + +def test_main_no_send_email_flag(monkeypatch): + monkeypatch.setattr(sys, "argv", + ["prog", "--reissue", "bob", "--no-send-email"]) + runner = MagicMock() + monkeypatch.setattr("openvpncertupdate.CliRunner", lambda: runner) + main() + runner.reissue.assert_called_once_with("bob", "", send_email_flag=False)