Coverage for frappe_manager / commands / ssl / add.py: 38%
42 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-07-02 18:13 +0530
« prev ^ index » next coverage.py v7.13.5, created at 2026-07-02 18:13 +0530
1"""Add SSL certificate command."""
3from typing import Annotated
5import typer
6from typer_examples import example
8from frappe_manager.logger.context import LoggerContext
9from frappe_manager.output_manager import temporary_stop
10from frappe_manager.ssl_manager import LETSENCRYPT_PREFERRED_CHALLENGE
11from frappe_manager.utils.callbacks import prompt_for_bench_selection, sites_autocompletion_callback
13from .bench_helpers import _add_bench_certificate
14from .external_helpers import _add_external_certificate
15from .helpers import get_output_handler
18@example(
19 "Add SSL certificate with HTTP-01 challenge",
20 "{benchname} example.com --challenge http01",
21 detail="Requests a certificate using an HTTP-01 challenge and installs it into the bench's nginx configuration.",
22 benchname="mybench",
23)
24@example(
25 "Add SSL certificate with DNS-01 challenge (Cloudflare)",
26 "{benchname} example.com --challenge dns01",
27 detail="Requests a certificate using DNS-01 validation; configure DNS provider credentials first when required.",
28 benchname="mybench",
29)
30@example(
31 "Add for external Docker project (standalone mode)",
32 "example.com --standalone",
33 detail="Manage SSL for an external Docker project by using standalone mode and FM's nginx-proxy.",
34)
35@example(
36 "Test with Let's Encrypt staging (dry-run)",
37 "{benchname} example.com --dry-run",
38 detail="Performs a dry-run against Let's Encrypt staging environment to validate configuration without issuing production certs.",
39 benchname="mybench",
40)
41@example(
42 "Add with CNAME delegation for DNS validation",
43 "{benchname} example.com --challenge dns01 --cname delegated.example.com",
44 detail="Uses a CNAME delegation target for DNS-01 validation when the DNS zone is delegated to another provider.",
45 benchname="mybench",
46)
47def add_certificate(
48 ctx: typer.Context,
49 benchname: Annotated[
50 str | None,
51 typer.Argument(
52 help="Name of the bench (omit for standalone mode).",
53 autocompletion=sites_autocompletion_callback,
54 ),
55 ] = None,
56 domain: Annotated[str | None, typer.Argument(help="Domain name for the certificate")] = None,
57 challenge: Annotated[
58 LETSENCRYPT_PREFERRED_CHALLENGE,
59 typer.Option("--challenge", "-c", help="Challenge type"),
60 ] = LETSENCRYPT_PREFERRED_CHALLENGE.http01,
61 cname: Annotated[
62 str | None,
63 typer.Option("--cname", help="CNAME delegation record for DNS-01 challenge (requires dns01)"),
64 ] = None,
65 dry_run: Annotated[
66 bool,
67 typer.Option(
68 "--dry-run",
69 help="Test certificate generation using Let's Encrypt staging server without adding it to the system.",
70 ),
71 ] = False,
72 standalone: Annotated[
73 bool,
74 typer.Option(
75 "--standalone",
76 help="Manage SSL for external (non-bench) Docker project. Use with docker network 'fm-global-frontend-network'.",
77 ),
78 ] = False,
79 skip_dns_check: Annotated[
80 bool,
81 typer.Option(
82 "--skip-dns-check",
83 help="Skip DNS validation before certificate generation (use if DNS will be configured later).",
84 ),
85 ] = False,
86 wait_for_dns: Annotated[
87 bool,
88 typer.Option(
89 "--wait-for-dns",
90 help="Wait for DNS propagation (polls every 30s for up to 5 minutes).",
91 ),
92 ] = False,
93):
94 """
95 Add an SSL certificate for a domain.
97 Supports bench mode (adds certificate to a bench) and standalone mode for external Docker projects using FM nginx-proxy.
98 Use --dry-run to validate issuance against Let's Encrypt staging first.
99 """
101 if standalone:
102 # Standalone mode: domain can be first arg (as benchname) or second arg
103 actual_domain = domain if domain else benchname
105 if not actual_domain:
106 context = LoggerContext(operation="ssl-add-external")
107 output = get_output_handler(ctx, context=context)
108 output.display_error("Domain is required in standalone mode")
109 with temporary_stop(output):
110 typer.echo(ctx.get_help())
111 raise typer.Exit(1)
113 if benchname and domain:
114 context = LoggerContext(operation="ssl-add-external")
115 output = get_output_handler(ctx, context=context)
116 output.display_error("Cannot specify both benchname and domain in standalone mode")
117 with temporary_stop(output):
118 typer.echo(ctx.get_help())
119 raise typer.Exit(1)
121 _add_external_certificate(ctx, actual_domain, challenge, cname, dry_run, skip_dns_check, wait_for_dns)
122 else:
123 benchname = prompt_for_bench_selection(benchname)
125 if not benchname or not domain:
126 context = LoggerContext(operation="ssl-add")
127 output = get_output_handler(ctx, context=context)
128 output.display_error("Both benchname and domain are required in bench mode")
129 with temporary_stop(output):
130 typer.echo(ctx.get_help())
131 raise typer.Exit(1)
133 _add_bench_certificate(ctx, benchname, domain, challenge, cname, dry_run)