Implement supported ingress and TLS profiles
This commit is contained in:
@@ -30,7 +30,9 @@ from .bundle import (
|
||||
initial_secrets,
|
||||
read_env,
|
||||
reconcile_runtime_environment,
|
||||
render_caddy_config,
|
||||
render_compose,
|
||||
render_existing_proxy_contract,
|
||||
render_garage_config,
|
||||
render_load_balancer_config,
|
||||
service_names,
|
||||
@@ -54,7 +56,9 @@ from .distribution import (
|
||||
from .model import (
|
||||
ComponentConfig,
|
||||
DEFAULT_GARAGE_IMAGE,
|
||||
DEFAULT_INGRESS_IMAGE,
|
||||
DEFAULT_LOAD_BALANCER_IMAGE,
|
||||
IngressConfig,
|
||||
InstallationSpec,
|
||||
ListenConfig,
|
||||
ReplicaConfig,
|
||||
@@ -320,6 +324,26 @@ def _configuration_arguments(
|
||||
default=default(DEFAULT_LOAD_BALANCER_IMAGE),
|
||||
help="HAProxy image used by the managed local load balancer.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ingress",
|
||||
choices=("local", "existing-proxy", "managed", "unconfigured"),
|
||||
default=default(None),
|
||||
help="Public route boundary; self-hosted requires existing-proxy or managed.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ingress-image",
|
||||
default=default(DEFAULT_INGRESS_IMAGE),
|
||||
help="Caddy image used by managed ingress.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--trusted-proxy-cidr",
|
||||
action="append",
|
||||
default=None,
|
||||
help="Exact source CIDR trusted to supply forwarded headers; repeatable.",
|
||||
)
|
||||
parser.add_argument("--acme-email", default=default(""))
|
||||
parser.add_argument("--ingress-http-port", type=int, default=default(80))
|
||||
parser.add_argument("--ingress-https-port", type=int, default=default(443))
|
||||
parser.add_argument(
|
||||
"--api-replicas",
|
||||
type=int,
|
||||
@@ -409,6 +433,12 @@ def _init(args: argparse.Namespace) -> int:
|
||||
storage_mode=args.storage,
|
||||
garage_image=args.garage_image,
|
||||
load_balancer_image=args.load_balancer_image,
|
||||
ingress_mode=args.ingress,
|
||||
ingress_image=args.ingress_image,
|
||||
trusted_proxy_cidrs=tuple(args.trusted_proxy_cidr or ()),
|
||||
ingress_http_port=args.ingress_http_port,
|
||||
ingress_https_port=args.ingress_https_port,
|
||||
acme_email=args.acme_email,
|
||||
api_replicas=args.api_replicas,
|
||||
web_replicas=args.web_replicas,
|
||||
worker_replicas=args.worker_replicas,
|
||||
@@ -530,6 +560,7 @@ def _apply(args: argparse.Namespace) -> int:
|
||||
"components.mail.image.",
|
||||
"components.storage.image.",
|
||||
"components.load_balancer.image.",
|
||||
"ingress.image.",
|
||||
"release.manifest",
|
||||
"modules.image_composition",
|
||||
)
|
||||
@@ -600,7 +631,14 @@ def _apply(args: argparse.Namespace) -> int:
|
||||
_run([*compose, "stop", "web"], cwd=paths.root)
|
||||
runtime_services = [
|
||||
name
|
||||
for name in ("api", "web", "load-balancer", "worker", "scheduler")
|
||||
for name in (
|
||||
"api",
|
||||
"web",
|
||||
"load-balancer",
|
||||
"worker",
|
||||
"scheduler",
|
||||
"ingress",
|
||||
)
|
||||
if name in service_names(spec)
|
||||
]
|
||||
_run(
|
||||
@@ -794,7 +832,22 @@ def _verify_release(args: argparse.Namespace) -> int:
|
||||
image=str(dependency_images["load_balancer"]),
|
||||
),
|
||||
)
|
||||
adopted = parse_spec(replace(spec, release=release, components=components).to_dict())
|
||||
ingress = replace(
|
||||
spec.ingress,
|
||||
image=(
|
||||
str(dependency_images["managed_ingress"])
|
||||
if spec.ingress.mode == "managed"
|
||||
else spec.ingress.image
|
||||
),
|
||||
)
|
||||
adopted = parse_spec(
|
||||
replace(
|
||||
spec,
|
||||
release=release,
|
||||
components=components,
|
||||
ingress=ingress,
|
||||
).to_dict()
|
||||
)
|
||||
ensure_private_directory(paths.root)
|
||||
atomic_write(paths.manifest, encoded_manifest, mode=0o644)
|
||||
atomic_write(
|
||||
@@ -862,11 +915,12 @@ def _selected_dependency_images(
|
||||
names.append("test_mail")
|
||||
if spec.components.storage.mode == "garage":
|
||||
names.append("garage")
|
||||
if spec.ingress.mode == "managed":
|
||||
names.append("managed_ingress")
|
||||
missing = [name for name in names if not isinstance(available.get(name), str)]
|
||||
if missing:
|
||||
raise DistributionError(
|
||||
"distribution is missing selected dependency images: "
|
||||
+ ", ".join(missing)
|
||||
"distribution is missing selected dependency images: " + ", ".join(missing)
|
||||
)
|
||||
return {name: str(available[name]) for name in names}
|
||||
|
||||
@@ -997,6 +1051,11 @@ def _deployment_receipt(
|
||||
"address": spec.listen.address,
|
||||
"port": spec.listen.port,
|
||||
},
|
||||
"ingress": {
|
||||
"mode": spec.ingress.mode,
|
||||
"http_port": spec.ingress.http_port,
|
||||
"https_port": spec.ingress.https_port,
|
||||
},
|
||||
"management": {
|
||||
"mode": "govoplan-deploy",
|
||||
"agent": "cli",
|
||||
@@ -1080,6 +1139,41 @@ def _updated_spec(
|
||||
)
|
||||
),
|
||||
)
|
||||
ingress_mode = args.ingress or current.ingress.mode
|
||||
ingress = IngressConfig(
|
||||
mode=ingress_mode,
|
||||
image=(args.ingress_image or current.ingress.image or DEFAULT_INGRESS_IMAGE)
|
||||
if ingress_mode == "managed"
|
||||
else "",
|
||||
trusted_proxy_cidrs=tuple(
|
||||
(
|
||||
args.trusted_proxy_cidr
|
||||
if args.trusted_proxy_cidr is not None
|
||||
else current.ingress.trusted_proxy_cidrs
|
||||
)
|
||||
if ingress_mode == "existing-proxy"
|
||||
else ()
|
||||
),
|
||||
http_port=(
|
||||
args.ingress_http_port
|
||||
if args.ingress_http_port is not None
|
||||
else current.ingress.http_port
|
||||
),
|
||||
https_port=(
|
||||
args.ingress_https_port
|
||||
if args.ingress_https_port is not None
|
||||
else current.ingress.https_port
|
||||
),
|
||||
acme_email=(
|
||||
(
|
||||
args.acme_email
|
||||
if args.acme_email is not None
|
||||
else current.ingress.acme_email
|
||||
)
|
||||
if ingress_mode == "managed"
|
||||
else ""
|
||||
),
|
||||
)
|
||||
value = replace(
|
||||
current,
|
||||
installation_id=args.installation_id or current.installation_id,
|
||||
@@ -1092,6 +1186,7 @@ def _updated_spec(
|
||||
release=release,
|
||||
components=components,
|
||||
replicas=replicas,
|
||||
ingress=ingress,
|
||||
enabled_modules=modules,
|
||||
)
|
||||
return parse_spec(value.to_dict())
|
||||
@@ -1137,6 +1232,16 @@ def _write_bundle(
|
||||
render_load_balancer_config(spec).encode("utf-8"),
|
||||
mode=0o644,
|
||||
)
|
||||
atomic_write(
|
||||
paths.caddy_config,
|
||||
render_caddy_config(spec).encode("utf-8"),
|
||||
mode=0o644,
|
||||
)
|
||||
atomic_write(
|
||||
paths.existing_proxy,
|
||||
canonical_json(render_existing_proxy_contract(spec)),
|
||||
mode=0o644,
|
||||
)
|
||||
atomic_write(
|
||||
paths.garage_config,
|
||||
render_garage_config().encode("utf-8"),
|
||||
@@ -1169,6 +1274,24 @@ def _prompt_configuration(args: argparse.Namespace) -> None:
|
||||
if args.profile == "self-hosted" and args.public_url.startswith("http://"):
|
||||
args.public_url = "https://govoplan.example.org"
|
||||
args.public_url = _prompt("Public URL", args.public_url)
|
||||
if args.profile == "self-hosted":
|
||||
args.ingress = _prompt_choice(
|
||||
"Public ingress",
|
||||
args.ingress or "existing-proxy",
|
||||
("existing-proxy", "managed"),
|
||||
)
|
||||
if args.ingress == "existing-proxy":
|
||||
current = (args.trusted_proxy_cidr or ["127.0.0.1/32"])[0]
|
||||
args.trusted_proxy_cidr = [
|
||||
_prompt("Trusted reverse-proxy source CIDR", current)
|
||||
]
|
||||
else:
|
||||
args.acme_email = args.acme_email or _prompt(
|
||||
"ACME account email",
|
||||
"admin@example.org",
|
||||
)
|
||||
else:
|
||||
args.ingress = args.ingress or "local"
|
||||
args.postgres = _prompt_choice("PostgreSQL", args.postgres, ("managed", "external"))
|
||||
if args.postgres == "external" and not args.database_url:
|
||||
args.database_url = getpass.getpass(
|
||||
|
||||
Reference in New Issue
Block a user