172 lines
6.5 KiB
Text
172 lines
6.5 KiB
Text
|
|
# Service Concerns Umbrella — mandatory declarative surface in ComponentDef.
|
||
|
|
# Every component must declare what it does (or doesn't do) for each concern:
|
||
|
|
# tls, dns, certs, backup, observability, security. Each is one of:
|
||
|
|
# 'enabled <impl> — concern is implemented; impl carries the configuration
|
||
|
|
# 'disabled — explicitly opt-out, with a stated reason
|
||
|
|
# 'pending — implementation deferred, with a backlog reference
|
||
|
|
# 'inherited — copied from a parent component (e.g. odoo profile)
|
||
|
|
#
|
||
|
|
# The umbrella absorbs the loose fields that components carry today
|
||
|
|
# (tls_secret, cluster_issuer, cert{}, dns_internal, dns_records, …) into
|
||
|
|
# typed variants. Existing 'extensions/components/<x>/nickel/main.ncl helpers
|
||
|
|
# may continue to read the loose fields for backwards compatibility while
|
||
|
|
# also emitting `concerns` for new consumers.
|
||
|
|
|
||
|
|
let bp = import "backup_policy.ncl" in
|
||
|
|
|
||
|
|
{
|
||
|
|
# === Concern state ========================================================
|
||
|
|
|
||
|
|
# Discriminated union of concern states. Encoded as a record with a `kind`
|
||
|
|
# tag so multiple concerns can coexist in a single ServiceConcerns record
|
||
|
|
# (Nickel does not support algebraic data types directly).
|
||
|
|
ConcernState = {
|
||
|
|
kind | [| 'enabled, 'disabled, 'pending, 'inherited |],
|
||
|
|
|
||
|
|
# 'enabled — payload depends on the concern (tls.impl, dns.impl, …);
|
||
|
|
# callers thread the right impl type via the wrapper records below.
|
||
|
|
|
||
|
|
# 'disabled
|
||
|
|
reason | String | optional,
|
||
|
|
since | String | optional | doc "ISO date when concern was explicitly disabled",
|
||
|
|
|
||
|
|
# 'pending
|
||
|
|
backlog_ref | String | optional | doc "Identifier of the backlog/issue tracking the implementation",
|
||
|
|
target_iteration | String | optional,
|
||
|
|
|
||
|
|
# 'inherited
|
||
|
|
from | String | optional | doc "Name of the parent ComponentDef the concern is inherited from",
|
||
|
|
|
||
|
|
# 'enabled payload — exactly one of these is populated based on the concern
|
||
|
|
tls_impl | { .. } | optional,
|
||
|
|
dns_impl | { .. } | optional,
|
||
|
|
certs_impl | { .. } | optional,
|
||
|
|
backup_impl | { .. } | optional,
|
||
|
|
observability_impl | { .. } | optional,
|
||
|
|
security_impl | { .. } | optional,
|
||
|
|
},
|
||
|
|
|
||
|
|
# === Concern impl types ===================================================
|
||
|
|
|
||
|
|
# TLS implementation. Absorbs `tls_secret`, `cluster_issuer`, `tls_hostnames`.
|
||
|
|
TlsImpl = {
|
||
|
|
secret_name | String | doc "K8s Secret name where cert-manager stores the cert (was tls_secret)",
|
||
|
|
issuer_ref | String | doc "ClusterIssuer name (was cluster_issuer)",
|
||
|
|
hostnames | Array String | doc "Additional SANs (was tls_hostnames)" | default = [],
|
||
|
|
},
|
||
|
|
|
||
|
|
# DNS implementation. Absorbs `dns_internal` (private routes via gateway),
|
||
|
|
# `dns_records` (public records: domain/mx/spf/dmarc/dkim_selector/autoconfig),
|
||
|
|
# `dns_zone`, `acme_email`.
|
||
|
|
DnsRoute = {
|
||
|
|
name | String,
|
||
|
|
zone | String,
|
||
|
|
gateway | String | optional,
|
||
|
|
target | String | optional,
|
||
|
|
},
|
||
|
|
|
||
|
|
DnsRecordSpec = {
|
||
|
|
domain | String | optional,
|
||
|
|
hostname | String | optional,
|
||
|
|
mx | Array { priority | Number, value | String } | default = [],
|
||
|
|
spf | String | optional,
|
||
|
|
dmarc | { policy | [| 'none, 'quarantine, 'reject |], rua | String | optional, ruf | String | optional } | optional,
|
||
|
|
autoconfig | String | optional,
|
||
|
|
dkim_selector | String | optional,
|
||
|
|
extra | { .. } | doc "Free-form provider-specific records" | default = {},
|
||
|
|
},
|
||
|
|
|
||
|
|
DnsImpl = {
|
||
|
|
internal | Array DnsRoute | doc "Was dns_internal (dns_private.via_gateway/make_route)" | default = [],
|
||
|
|
public | DnsRecordSpec | optional | doc "Was dns_records",
|
||
|
|
zone | String | optional | doc "Was dns_zone",
|
||
|
|
acme_email | String | optional | doc "Was acme_email (only used when certs concern derives from this email)",
|
||
|
|
},
|
||
|
|
|
||
|
|
# Certificates implementation. Absorbs `cert = { acme_server, email, secret_ref, provider }`.
|
||
|
|
# Distinct from TLS: TLS = pedido al issuer; Certs = config del ACME issuer.
|
||
|
|
CertsImpl = {
|
||
|
|
acme_server | String,
|
||
|
|
email | String,
|
||
|
|
secret_ref | String | doc "DNS provider credentials secret reference",
|
||
|
|
provider | [| 'cloudflare, 'hetzner, 'aws, 'route53, 'digitalocean, 'gcp, 'azure |],
|
||
|
|
},
|
||
|
|
|
||
|
|
# Observability implementation. Surface only — deeper schemas land in a
|
||
|
|
# later iteration. Components most commonly declare 'pending here.
|
||
|
|
ObservabilityImpl = {
|
||
|
|
metrics | { enabled | Bool, port | Number | optional, path | String | default = "/metrics" } | default = { enabled = false },
|
||
|
|
logs | { enabled | Bool, sink | [| 'stdout, 'loki, 'journald |] | default = 'stdout } | default = { enabled = false },
|
||
|
|
traces | { enabled | Bool, otlp_endpoint | String | optional } | default = { enabled = false },
|
||
|
|
alerts | Array { name | String, expr | String, severity | [| 'info, 'warning, 'critical |] | default = 'warning } | default = [],
|
||
|
|
},
|
||
|
|
|
||
|
|
# Security implementation. Surface only.
|
||
|
|
SecurityImpl = {
|
||
|
|
network_policy | String | optional | doc "Reference to a NetworkPolicy resource",
|
||
|
|
pod_security | [| 'restricted, 'baseline, 'privileged |] | optional,
|
||
|
|
rbac | String | optional | doc "Reference to RBAC bundle",
|
||
|
|
},
|
||
|
|
|
||
|
|
# === Builders =============================================================
|
||
|
|
# Helper functions for components and migrations to construct ConcernState
|
||
|
|
# values without repeating the discriminated-union plumbing.
|
||
|
|
|
||
|
|
enabled_tls = fun impl => {
|
||
|
|
kind = 'enabled,
|
||
|
|
tls_impl = impl,
|
||
|
|
},
|
||
|
|
|
||
|
|
enabled_dns = fun impl => {
|
||
|
|
kind = 'enabled,
|
||
|
|
dns_impl = impl,
|
||
|
|
},
|
||
|
|
|
||
|
|
enabled_certs = fun impl => {
|
||
|
|
kind = 'enabled,
|
||
|
|
certs_impl = impl,
|
||
|
|
},
|
||
|
|
|
||
|
|
enabled_backup = fun impl => {
|
||
|
|
kind = 'enabled,
|
||
|
|
backup_impl = impl,
|
||
|
|
},
|
||
|
|
|
||
|
|
enabled_observability = fun impl => {
|
||
|
|
kind = 'enabled,
|
||
|
|
observability_impl = impl,
|
||
|
|
},
|
||
|
|
|
||
|
|
enabled_security = fun impl => {
|
||
|
|
kind = 'enabled,
|
||
|
|
security_impl = impl,
|
||
|
|
},
|
||
|
|
|
||
|
|
disabled = fun reason_text => {
|
||
|
|
kind = 'disabled,
|
||
|
|
reason = reason_text,
|
||
|
|
},
|
||
|
|
|
||
|
|
pending = fun reason_text backlog => {
|
||
|
|
kind = 'pending,
|
||
|
|
reason = reason_text,
|
||
|
|
backlog_ref = backlog,
|
||
|
|
},
|
||
|
|
|
||
|
|
inherited = fun parent_name => {
|
||
|
|
kind = 'inherited,
|
||
|
|
from = parent_name,
|
||
|
|
},
|
||
|
|
|
||
|
|
# === Top-level umbrella ===================================================
|
||
|
|
|
||
|
|
ServiceConcerns = {
|
||
|
|
tls | ConcernState,
|
||
|
|
dns | ConcernState,
|
||
|
|
certs | ConcernState,
|
||
|
|
backup | ConcernState,
|
||
|
|
observability | ConcernState,
|
||
|
|
security | ConcernState,
|
||
|
|
},
|
||
|
|
}
|