ingress-annotations-compat library¶
This library is a set of shared internal macros consumed by HAPTIC's native haptic-annotations library — its heaviest consumer, and the only annotation library on by default — and by the three vendor annotation libraries (haproxytech, haproxy-ingress, nginx-ingress) — it emits nothing on its own, and there is nothing in it to configure. Leave it enabled: the vendor libraries import its macros, so disabling it while any of them is enabled breaks their template compilation.
Overview¶
ingress-annotations-compat.yaml is a scaffold library at hierarchy level 2.5 that holds parameterized macros consumed by the three Ingress vendor annotation libraries. The scaffold either walks resources.ingresses.List() itself (for example, BuildAnnotationSSLPassthrough) or takes a typed *resources.ingresses.T parameter (for example, EmitAnnotationAccessControl) — that's correct, because all three vendor libraries process the same resource and differ only by annotation namespace.
The scaffold exists to concentrate behaviour that would otherwise be duplicated three times. Each vendor library still owns its annotation extraction (the keys differ per vendor: haproxy.org/*, haproxy-ingress.github.io/*, nginx.ingress.kubernetes.io/*); the shared output emission lives in this library.
Scope: Ingress only. A vendor library operating on a non-Ingress CRD (HTTPRoute, GRPCRoute, custom CRDs) doesn't use these macros — it writes its own equivalents.
Configuration¶
controller:
templateLibraries:
ingressAnnotationsCompat:
enabled: true # Default; required by haptic-annotations (on by default) and by haproxytech, haproxy-ingress, and nginx-ingress
Disabling the scaffold while haptic-annotations or any of the three vendor annotation libraries stays enabled produces an invalid configuration: their snippets import macros defined here, and the controller's template validation rejects the merged config when those imports don't resolve. Disable it only together with all three vendor libraries.
Hierarchy¶
Level 0: base
Level 1: ssl
Level 2: ingress, gateway
Level 2.5: ingress-annotations-compat <-- this library
Level 3: haptic-annotations, haproxytech, haproxy-ingress, nginx-ingress
The level-3 libraries import macros from this scaffold; the scaffold knows about Ingress but not about any specific annotation vocabulary.
Available macros¶
BuildAnnotationSSLPassthrough¶
Loops over Ingress resources and registers SSL passthrough backends. Each Ingress with a truthy SSL-passthrough annotation contributes one backend per host rule.
Signature:
BuildAnnotationSSLPassthrough(
sslData map[string]any, // accumulator
annotationKey string, // e.g. "haproxy.org/ssl-passthrough"
namePrefix string, // e.g. "ssl-passthrough-"
firstSeenKey string, // e.g. "haproxytech_sslPassthrough_host"
)
Backend entry shape (don't change without updating ssl.yaml consumers):
{
"name": namePrefix + ns + "-" + name,
"sni": host,
"namespace": ns,
"ingress": name,
"svcName": <first path's service name>,
"svcPort": <first path's service port (int, resolved from port.number or port.name)>,
"svcPortName": <first path's service port name ("" if the port is referenced by number)>,
}
The service fields (svcName, svcPort, svcPortName) are captured at scan time rather than re-fetched by the consumer's backends-*-ssl-passthrough snippet — a second GetSingle() lookup could disagree with the use_backend pass under concurrent Ingress deletion. The macro comment in the source has the full rationale.
Used by:
haproxytech/→util-haproxytech-ssl-passthrough(annotation:haproxy.org/ssl-passthrough)haproxy-ingress/→util-haproxy-ingress-ssl-passthrough(annotation:haproxy-ingress.github.io/ssl-passthrough)nginx-ingress/→util-nginx-ingress-ssl-passthrough(annotation:nginx.ingress.kubernetes.io/ssl-passthrough)haptic-annotations/→util-haptic-ssl-passthrough(annotation:haproxy-haptic.org/ssl-passthrough)
The vendor library still owns the per-library ComputeIfAbsent cache key, so the data slots stay distinct.
EmitAnnotationAccessControl¶
Emits CIDR-based source-range access control: acl <name> src <cidrs> plus http-request deny if <host-match> [!]<acl>, scoped to the ingress's hosts.
Signature:
EmitAnnotationAccessControl(
ingress *resources.ingresses.T,
allowAnnotation string, // e.g. "nginx.ingress.kubernetes.io/whitelist-source-range"
denyAnnotation string, // e.g. "nginx.ingress.kubernetes.io/denylist-source-range"
aclPrefix string, // e.g. "ni" — produces ACL names like "ni_allowlist_<ns>_<name>"
allowCommentFmt string, // comment template, may contain {KEY} for "<ns>/<name>"
denyCommentFmt string,
)
Used by:
haproxytech/→frontend-filters-200-haproxytech-access-controlhaproxy-ingress/→frontend-filters-610-haproxy-ingress-access-controlnginx-ingress/→frontend-filters-700-nginx-ingress-access-controlhaptic-annotations/→frontend-filters-810-haptic-access-control(aclPrefix: "haptic")
Validation tests assert on the names of the generated ACLs (ni_allowlist_*, hi_allowlist_*, haproxytech_allowlist_*, haptic_allowlist_*), so each library passes its distinct aclPrefix.
WebhookRejectOrWarn¶
WebhookRejectOrWarn(resource, reason, message) (from the util-webhook-reject-or-warn snippet) is the shared way to reject a misconfigured watched resource. It branches on the renderMode global: under the admission webhook it fail()s (so the API server denies the proposed resource), and on a live reconcile or the daemon load gate it records a Warning Event against resource and returns, so one already-present bad resource can't abort the whole render. The vendor libraries and the native haptic-annotations library import it for per-resource routing/presentation validation.
Callers must skip the offending resource's output in the warn path ({% continue %} in the Ingress loop). Use it only where skipping the feature is safe to serve without — routing/presentation guards — and keep a plain fail() for security features (skipping those would be fail-open) and for guards where a clean skip isn't possible. The full decision rule lives in the chart development guide (charts/CLAUDE.md).
Other exported macros¶
The scaffold exports six more macros, all imported the same way:
| Macro | Signature | What it does |
|---|---|---|
RenderAnnotationSSLPassthroughBackends |
(cacheKey, firstSeenKey, commentLabel string) |
Emits the mode tcp backends for the entries a matching BuildAnnotationSSLPassthrough scan cached under cacheKey — the emission half of the passthrough pair |
RegisterAnnotationHSTS |
(ingress *resources.ingresses.T, enabledAnn, maxAgeAnn, subdomainsAnn, preloadAnn, defaultMaxAge string) |
Registers per-host HSTS settings for the SSL library's global HSTS snippet to emit |
EmitAnnotationCORS |
(ingress *resources.ingresses.T, prefix, enabledAnn, defaultMaxAge, commentLabel string) |
Emits the CORS response headers and preflight handling for one Ingress (the ingress-nginx model) |
ValidateCidrList |
(rawList, annotation, key string) |
Rejects a malformed CIDR list before it reaches the config, naming the annotation and the resource |
ValidateConfigValue |
(value, annotation, key string, allowSpaces bool) |
Rejects annotation values carrying characters that would break out of the rendered directive |
WafGovernance |
(gov map[string]any) |
Applies the shared Web Application Firewall governance rules to an annotation-derived policy selection |
Design rationale¶
Why the scaffold sits at level 2.5, which patterns were extracted, which were surveyed and deliberately left duplicated (cookie-based session affinity, backend timeouts, header manipulation), and the rename from annotation-compat to ingress-annotations-compat: see Architecture Decision Record (ADR) ADR-0003.
Adding a new macro¶
A new macro earns its keep when:
- Two or more vendor libraries already implement nearly the same emission logic.
- The differences can be expressed as a small fixed set of string parameters (annotation keys, naming prefixes, comment formats).
- Validation-test assertions can survive the move (or can be updated cheaply).
If any of those fail, leave the duplication in place. Forced abstractions over genuinely different behaviour are worse than direct duplication — the future reader has to follow the parameters back to figure out what each library actually does.
See also¶
- Template Libraries Overview
- Base Library — provides
util-ingress-helpers(HostMatchCondition) used inside scaffold macros - ADR-0003 — the decision record for this library's placement and scope